首页 文章 精选 留言 我的

精选列表

搜索[递归超智能],共10000篇文章
优秀的个人博客,低调大师

Altman:2025 年底 OpenAI 将上线超 100 万块 GPU

OpenAI CEO 萨姆・奥尔特曼(Sam Altman)近日在社交媒体上宣布,该公司计划在2025年底前上线超过100万块 GPU。 据悉,OpenAI 的战略主要围绕三个核心领域展开:Stargate(星际之门)项目、芯片供应链重构以及能源挑战。Stargate 是 OpenAI 新成立的公司,目标是为 AI 基础设施建设注入巨额资金。未来四年,该项目预计将投资高达5000亿美元(约合3.59万亿元人民币),旨在在美国打造一座全新的 AI 基础设施。 Stargate 项目的首期工程设立在得克萨斯州的阿比林市,占地1000英亩,计划建造全球最大的 AI 训练集群。OpenAI 与软银、甲骨文等多家知名企业建立了紧密的合作关系。软银 CEO 孙正义将担任 Stargate 董事长,负责整体财务规划,而 OpenAI 则负责日常运营。 除了 Stargate 项目外,OpenAI 还将与 Arm、微软和英伟达等巨头合作,进一步推动 AI 技术的发展与应用。这一系列的举措显示了 OpenAI 在全球 AI 基础设施竞赛中的强烈竞争意识和技术雄心。 值得一提的是,随着 GPU 需求的激增,OpenAI 的计划将可能引发市场的剧烈反响。AI 行业的竞争正日益白热化,OpenAI 的 “百倍扩容” 愿景不仅是自身发展的重要里程碑,也将深刻影响整个行业的格局与未来。

优秀的个人博客,低调大师

8种超简单的Golang生成随机字符串方式

本文分享自华为云社区《Golang生成随机字符串的八种方式与性能测试》,作者: 张俭。 前言 这是**icza**在StackOverflow上的一篇高赞回答,质量很高,翻译一下,大家一起学习 问题是:go语言中,有没有什么最快最简单的方法,用来生成只包含英文字母的随机字符串 icza给出了8个方案,最简单的方法并不是最快的方法,它们各有优劣,末尾附上性能测试结果: 1. Runes 比较简单的答案,声明一个rune数组,通过随机数选取rune字符,拼接成结果 package approach1 import ( "fmt" "math/rand" "testing" "time" ) var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") func randStr(n int) string { b := make([]rune, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func TestApproach1(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr(10)) } func BenchmarkApproach1(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr(10) } } 2. Bytes 如果随机挑选的字符只包含英文字母,我们可以直接使用bytes,因为在UTF-8编码模式下,英文字符和Bytes是一对一的(Go正是使用UTF-8模式编码) 所以可以把 var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 用这个替代 var letters = []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 或者更好 const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 现在我们有很大的进展了,我们把它变为了一个常数,在go里面,只有string常数,可并没有slice常数。额外的收获,表达式len(letters)也变为了一个常数(如果s为常数,那么len(s)也将是常数) 我们没有付出什么代码,现在letters可以通过下标访问其中的bytes了,这正是我们需要的。 package approach2 import ( "fmt" "math/rand" "testing" "time" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randStr(n int) string { b := make([]byte, n) for i := range b { b[i] = letters[rand.Intn(len(letters))] } return string(b) } func TestApproach2(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr(10)) } func BenchmarkApproach2(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr(10) } } 3. Remainder 余数 上面的解决方法通过rand.Intn()来获得一个随机字母,这个方法底层调用了Rand.Intn(),然后调用了Rand.Int31n() 相比于生成63个随机bits的函数rand.Int63()来说,Rand.Int31n()很慢。 我们可以简单地调用rand.Int63()然后除以len(letterBytes),使用它的余数来生成字母 package approach3 import ( "fmt" "math/rand" "testing" "time" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func randStr(n int) string { b := make([]byte, n) for i := range b { b[i] = letters[rand.Int63() % int64(len(letters))] } return string(b) } func TestApproach3(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr(10)) } func BenchmarkApproach3(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr(10) } } 这个算法能正常工作并且非常快,不过它牺牲了部分精确性,字母出现的概率并不是精确一样的(假设rand.Int63()生成63比特的数字是等概率的)。由于字母总共才52个,远小于 1<<63 - 1,因此失真非常小,因此实际上这完全没问题。 解释: 假设你想要0~5的随机数,如果使用3位的bit,3位的bit等概率出现0~7,所以出现0和1的概率是出现2、3、4概率的两倍。使用5位的 bit,0和1出现的概率是6/32,2、3、4出现的概率是5/32。现在接近了一些了,是吧?不断地增加比特位,这个差距就会变得越小,当你有63位地时候,这差别已经可忽略不计。 4. Masking 掩码 在上一个方案的基础上,我们通过仅使用随机数的最低n位保持均匀分布,n表示所有字符的数量。比如我们有52个字母,我们需要6位(52 = 110100b)。所以我们仅仅使用了rand.Int63()的最后6位。并且,为了保持所有字符的均匀分布,我们决定只接受在0..len(letterBytes)-1的数字即0~51。(译者注:这里已经没有第三个方案的不准确问题了) 最低几位大于等于len(letterBytes)的概率一般小于0.5(平均值为0.25),这意味着出现这种情况,只要重试就好。重试n次之后,我们仍然需要丢弃这个数字的概率远小于0.5的n次方(这是上界了,实际会低于这个值)。以本文的52个字母为例,最低6位需要丢弃的概率只有(64-52)/64=0.19。这意味着,重复10次,仍然没有数字的概率是1*10^-8。 package approach4 import ( "fmt" "math/rand" "testing" "time" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( // 6 bits to represent a letters index letterIdBits = 6 // All 1-bits as many as letterIdBits letterIdMask = 1 <<letterIdBits - 1 ) func randStr(n int) string { b := make([]byte, n) for i := range b { if idx := int(rand.Int63() & letterIdMask); idx < len(letters) { b[i] = letters[idx] i++ } } return string(b) } func TestApproach4(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr(10)) } func BenchmarkApproach4(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr(10) } } 5. Masking Improved 第4节的方案只使用了rand.Int63()方法返回的64个随机字节的后6位。这实在是太浪费了,因为rand.Int63()是我们算法中最耗时的部分了。 如果我们有52个字母,6位就能生成一个随机字符串。所以63个随机字节,可以利用63/6=10次。 译者注:使用了缓存,缓存了rand.Int63()方法返回的内容,使用10次,不过已经并不是协程安全的了。 package approach5 import ( "fmt" "math/rand" "testing" "time" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( // 6 bits to represent a letter index letterIdBits = 6 // All 1-bits as many as letterIdBits letterIdMask = 1<<letterIdBits - 1 letterIdMax = 63 / letterIdBits ) func randStr(n int) string { b := make([]byte, n) // A rand.Int63() generates 63 random bits, enough for letterIdMax letters! for i, cache, remain := n-1, rand.Int63(), letterIdMax; i >= 0; { if remain == 0 { cache, remain = rand.Int63(), letterIdMax } if idx := int(cache & letterIdMask); idx < len(letters) { b[i] = letters[idx] i-- } cache >>= letterIdBits remain-- } return string(b) } func TestApproach5(t *testing.T) { rand.Seed(time.Now().UnixNano()) fmt.Println(randStr(10)) } func BenchmarkApproach5(b *testing.B) { rand.Seed(time.Now().UnixNano()) for i := 0; i < b.N; i++ { _ = randStr(10) } } 6. Source 第5个方案非常好,能改进的点并不多。我们可以但不值得搞得很复杂。 让我们来找可以改进的点:随机数的生成源 crypto/rand的包提供了Read(b []byte)的函数,可以通过这个函数获得需要的随机比特数,只需要一次调用。不过并不能提升性能,因为crypto/rand实现了一个密码学上的安全伪随机数,所以速度比较慢。 所以让我们坚持使用math/rand包,rand.Rand使用rand.Source作为随机位的来源,rand.Source是一个声明了Int63() int64的接口:正是我们在最新解决方案中需要和使用的唯一方法。 所以我们不是真的需要rand.Rand,rand.Source包对于我们来说已经足够了 package approach6 import ( "fmt" "math/rand" "testing" "time" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var src = rand.NewSource(time.Now().UnixNano()) const ( // 6 bits to represent a letter index letterIdBits = 6 // All 1-bits as many as letterIdBits letterIdMask = 1<<letterIdBits - 1 letterIdMax = 63 / letterIdBits ) func randStr(n int) string { b := make([]byte, n) // A rand.Int63() generates 63 random bits, enough for letterIdMax letters! for i, cache, remain := n-1, src.Int63(), letterIdMax; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdMax } if idx := int(cache & letterIdMask); idx < len(letters) { b[i] = letters[idx] i-- } cache >>= letterIdBits remain-- } return string(b) } func TestApproach6(t *testing.T) { fmt.Println(randStr(10)) } func BenchmarkApproach6(b *testing.B) { for i := 0; i < b.N; i++ { _ = randStr(10) } } 注意到这里我们没有使用种子初始化rand了,取而代之的是初始化了rand.Source 还有一件需要注意的事,math/rand的文档指出 默认的Source是协程安全的 所以默认的Source比通过rand.NewSource()创建出来的Source要慢。不用处理协程并发场景,当然慢啦。 7. 使用 strings.Builder 之前的解决方案都返回了通过slice构造的字符串。最后的一次转换进行了一次拷贝,因为字符串是不可变的,如果转换的时候不进行拷贝,就无法保证转换完成之后,byte slice再被修改后,字符串仍能保持不变。 Go1.10引入了strings.Builder,这是一个新的类型,和bytes.Buffer类似,用来构造字符串。底层使用[]byte来构造内容,正是我们现在在做的,最后可以通过Builder.String()方法来获得最终的字符串值。但它很酷的地方在于,它无需执行刚才谈到的复制即可完成此操作。它敢这么做是因为它底层构造的[]byte从未暴露出来,所以仍然可以保证没有人可以无意地、恶意地来修改已经生成的不可变字符串。 所以我们的下一个想法不是在slice中构建随机字符串,而是使用 strings.Builder,结束building后,我们就可以获取并返回结果,而无需复制。 这可能在速度方面有所帮助,并且在内存使用和分配方面肯定会有所帮助(译者注:等会在benchmark中会清晰地看到)。 package approach7 import ( "fmt" "math/rand" "strings" "testing" "time" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var src = rand.NewSource(time.Now().UnixNano()) const ( // 6 bits to represent a letter index letterIdBits = 6 // All 1-bits as many as letterIdBits letterIdMask = 1<<letterIdBits - 1 letterIdMax = 63 / letterIdBits ) func randStr(n int) string { sb := strings.Builder{} sb.Grow(n) // A rand.Int63() generates 63 random bits, enough for letterIdMax letters! for i, cache, remain := n-1, src.Int63(), letterIdMax; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdMax } if idx := int(cache & letterIdMask); idx < len(letters) { sb.WriteByte(letters[idx]) i-- } cache >>= letterIdBits remain-- } return sb.String() } func TestApproach7(t *testing.T) { fmt.Println(randStr(10)) } func BenchmarkApproach7(b *testing.B) { for i := 0; i < b.N; i++ { _ = randStr(10) } } 在构造出builder之后,我们立刻调用了Builder.Grow()方法,使得它分配一个足够大的底层slice,避免在后续操作中再进行分配 8. “Mimicing” strings.Builder with package unsafe 模仿string.Builder使用unsafe包 string.Builder跟我们第六节地解法一样,都是用[]byte来构建字符串。切换到strings.Builder可能有一些太重了,我们使用strings.Builder只是想避免拷贝slice。 string.Builder使用unsafe包来避免最终的拷贝 // String returns the accumulated string. func (b *Builder) String() string { return *(*string)(unsafe.Pointer(&b.buf)) } 我们也可以自己完成这个流程。所以思路是我们通过unsafe包来返回一个字符串,来避免拷贝 package approach8 import ( "fmt" "math/rand" "testing" "time" "unsafe" ) const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" var src = rand.NewSource(time.Now().UnixNano()) const ( // 6 bits to represent a letter index letterIdBits = 6 // All 1-bits as many as letterIdBits letterIdMask = 1<<letterIdBits - 1 letterIdMax = 63 / letterIdBits ) func randStr(n int) string { b := make([]byte, n) // A rand.Int63() generates 63 random bits, enough for letterIdMax letters! for i, cache, remain := n-1, src.Int63(), letterIdMax; i >= 0; { if remain == 0 { cache, remain = src.Int63(), letterIdMax } if idx := int(cache & letterIdMask); idx < len(letters) { b[i] = letters[idx] i-- } cache >>= letterIdBits remain-- } return *(*string)(unsafe.Pointer(&b)) } func TestApproach8(t *testing.T) { fmt.Println(randStr(10)) } func BenchmarkApproach8(b *testing.B) { for i := 0; i < b.N; i++ { _ = randStr(10) } } Benchmark go test ./... -bench=. -benchmem 原作者测试的数据 (译者注:第三列代表操作一次需要多少纳秒) BenchmarkRunes-4 2000000 723 ns/op 96 B/op 2 allocs/op BenchmarkBytes-4 3000000 550 ns/op 32 B/op 2 allocs/op BenchmarkBytesRmndr-4 3000000 438 ns/op 32 B/op 2 allocs/op BenchmarkBytesMask-4 3000000 534 ns/op 32 B/op 2 allocs/op BenchmarkBytesMaskImpr-4 10000000 176 ns/op 32 B/op 2 allocs/op BenchmarkBytesMaskImprSrc-4 10000000 139 ns/op 32 B/op 2 allocs/op BenchmarkBytesMaskImprSrcSB-4 10000000 134 ns/op 16 B/op 1 allocs/op BenchmarkBytesMaskImprSrcUnsafe-4 10000000 115 ns/op 16 B/op 1 allocs/op 译者测试的数据 BenchmarkApproach1-12 3849038 299.5 ns/op 64 B/op 2 allocs/op BenchmarkApproach2-12 5545350 216.4 ns/op 32 B/op 2 allocs/op BenchmarkApproach3-12 7003654 169.7 ns/op 32 B/op 2 allocs/op BenchmarkApproach4-12 7164259 168.7 ns/op 32 B/op 2 allocs/op BenchmarkApproach5-12 13205474 89.06 ns/op 32 B/op 2 allocs/op BenchmarkApproach6-12 13665636 84.41 ns/op 32 B/op 2 allocs/op BenchmarkApproach7-12 17213431 70.37 ns/op 16 B/op 1 allocs/op BenchmarkApproach8-12 19756956 61.41 ns/op 16 B/op 1 allocs/op 现在跑出来的数据,相原作者时候,已经有了一些变化,不过并不妨碍我们看出来各个方法的趋势: 仅仅只是把rune切换到byte,就获得了性能的大幅度提升(大于百分之20) 使用rand.Int63()代替rand.Intn()也获得大幅度提升(大于百分之20) 使用Masking并没有提升性能,相反在原作者哪里,反而性能下降了 不过使用了一次rand.Int63()返回的全部字符后,性能提升了3倍 使用rand.Source替代rand.Rand,性能提升了21% 使用strings.Builder,我们在速度上提升了3.5%,并且把原本2次的内存分配,降低到了一次! 使用unsafe包来代替strings.Builder,性能提升了14% 将第八个方案和第一个方案比较,第八个方案比第一个方案快6.3倍,仅仅使用六分之一的内存,分配次数也只有原来的一半。 点击关注,第一时间了解华为云新鲜技术~

优秀的个人博客,低调大师

Java 11 和 Java 17 使用率均超 Java 8

Java 软件供应商Azul 发布了首份年度 Java 现状调查报告,基于对全球 2062 名 Java 专业人士和基于 Java 的应用程序用户进行的调查。调查探讨的领域包括 Java 采用趋势、Oracle 最新 Java 定价变化的影响、Java 应用程序向云的迁移以及公司如何优化云成本,以及常见漏洞和暴露 (CVE) 的安全注意事项。 结果表明,Java 的采用依然强劲,98% 的受访者表示在他们的软件应用程序或基础架构中使用了 Java。其中 57% 的受访者表示,他们至少 60% 的应用程序是基于 Java 的;有66% 的公司为 Java 支持付费。 2018 年 9 月发布的 Java 11 和 2020 年 9 月发布的 Java 17 是使用最广泛的 Java 版本,使用率分别为 48% 和 45%。其次是 2014 年 3 月发布的 Java8,使用率为 40%。85% 的受访者使用的是 LTS 版本的 Java,64% 的受访者使用了多个 Java 版本。 Oracle 的 Java 市场份额正在下降。在使用 Oracle Java 的受访者中,82% 的人表示对 1 月份推出的新 Java SE 通用订阅定价模式感到担忧。受 Oracle 最新的定价政策影响,72% 的受访者表示他们正在考虑使用 OpenJDK 等开源替代品;而在没有考虑采用开源替代方案的受访者中,有 14% 的人表示,是因为他们没有想到可以这样做。 但仅管如此,Oracle 仍然是 Java 市场的强大参与者。42% 的受访者表示他们仍然使用至少一个 Oracle Java 实例,不过其中 74% 的组织表示他们还使用至少一个 OpenJDK 供应商的 JDK。大约 60% 的公司选择了 OpenJDK 发行版而不是 Oracle Java SE。 90% 的受访者在云环境中使用 Java:公有 (48%)、私有 (47%) 或混合 (40%)。云格局正在迅速转变,组织不断向云迈进,以实现可扩展性、灵活性、生产力和敏捷性,但成本和安全性仍然是两个主要挑战。 近 70% 的公司表示,他们正在为至少 20% 的未使用云容量付费,“这是过度配置云资源的明显迹象”。95% 的公司在过去一年中采取了降低云成本的措施,46% 的企业正在利用高性能 Java 平台更有效地使用云资源。 Log4Shell 漏洞对组织产生了广泛的安全影响。近 80% 的受访者表示受到了 2021 年 Log4J 库漏洞的影响。近一半的公司在该漏洞出现后不得不分配额外的工程时间,30% 的公司受到尝试利用此漏洞的影响。 近三分之二的调查受访者明确表示,第三方和开源应用程序及库是最令人担忧的 CVE 来源。其中 57% 的受访者将开源库和应用程序列为最令人担忧的 CVE 来源,51% 的受访者认为第三方库和应用程序是最令人担忧的 CVE 来源。 更多详情可查看完整报告。

优秀的个人博客,低调大师

Wow 2.6.12 发布,API 写性能轻松超 2W TPS

基于 DDD、EventSourcing 的现代响应式 CQRS 架构微服务开发框架 领域驱动|事件驱动|测试驱动|声明式设计|响应式编程|命令查询职责分离|事件溯源 更新内容 🎉 🎉 🎉 依赖: 更新org.springframework.boot:spring-boot-dependencies版本v3.1.5 依赖: 更新com.google.guava:guava版本v32.1.3-jre 依赖: 更新io.swagger.core.v3:swagger-core-jakarta版本v2.2.17 依赖: 更新io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom版本v1.31.0 依赖: 更新org.jetbrains.dokka版本v1.9.10 修复: 路由路径参数(tenantId/id)冗余定义 文档: 新增 Example API 性能测试报告(Example) 性能测试报告 加入购物车 WaitStrategy:SENT WaitStrategy:PROCESSED 下单 WaitStrategy:SENT WaitStrategy:PROCESSED 架构图 事件源 可观测性 OpenAPI (Spring WebFlux 集成) 自动注册命令路由处理函数 (HandlerFunction) ,开发人员仅需编写领域模型,即可完成服务开发。 测试套件:80%+ 的测试覆盖率轻而易举 Given -> When -> Expect . 前置条件 理解领域驱动设计:《实现领域驱动设计》、《领域驱动设计:软件核心复杂性应对之道》 理解命令查询职责分离(CQRS) 理解事件源架构 理解响应式编程 特性 Aggregate Modeling Single Class Inheritance Pattern Aggregation Pattern Saga Modeling StatelessSaga Test Suite 兼容性测试规范(TCK) AggregateVerifier SagaVerifier EventSourcing EventStore MongoDB (Recommend) R2dbc Database Sharding Table Sharding Redis Snapshot MongoDB R2dbc Database Sharding Table Sharding ElasticSearch Redis (Recommend) 命令等待策略(WaitStrategy) SENT: 命令发送成功后发送完成信号 PROCESSED: 命令处理完成后发送完成信号 SNAPSHOT: 快照生成完成后发送完成信号 PROJECTED: 命令产生的事件被投影后发送完成信号 CommandBus InMemoryCommandBus KafkaCommandBus(Recommend) RedisCommandBus LocalFirstCommandBus DomainEventBus InMemoryDomainEventBus KafkaDomainEventBus(Recommend) RedisDomainEventBus LocalFirstDomainEventBus StateEventBus InMemoryStateEventBus KafkaStateEventBus(Recommend) RedisStateEventBus LocalFirstStateEventBus Spring 集成 Spring Boot Auto Configuration Automatically registerCommandAggregatetoRouterFunction 可观测性 OpenTelemetry OpenAPI WowMetadataGenerator wow-compiler Example Example 单元测试套件 80%+ 的测试覆盖率轻而易举。 Given -> When -> Expect . Aggregate Unit Test (AggregateVerifier) Aggregate Test internal class OrderTest { private fun mockCreateOrder(): VerifiedStage<OrderState> { val tenantId = GlobalIdGenerator.generateAsString() val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } return aggregateVerifier<Order, OrderState>(tenantId = tenantId) .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) .expectEventCount(1) .expectEventType(OrderCreated::class.java) .expectStateAggregate { assertThat(it.aggregateId.tenantId, equalTo(tenantId)) } .expectState { assertThat(it.id, notNullValue()) assertThat(it.customerId, equalTo(customerId)) assertThat(it.address, equalTo(SHIPPING_ADDRESS)) assertThat(it.items, equalTo(orderItems)) assertThat(it.status, equalTo(OrderStatus.CREATED)) } .verify() } /** * 创建订单 */ @Test fun createOrder() { mockCreateOrder() } @Test fun createOrderGivenEmptyItems() { val customerId = GlobalIdGenerator.generateAsString() aggregateVerifier<Order, OrderState>() .inject(mockk<CreateOrderSpec>(), "createOrderSpec") .given() .`when`(CreateOrder(customerId, listOf(), SHIPPING_ADDRESS, false)) .expectErrorType(IllegalArgumentException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-库存不足 */ @Test fun createOrderWhenInventoryShortage() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId } /* * 模拟库存不足 */ .map { it.quantity - 1 }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId }.map { it.price }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:库存不足异常. */ .expectErrorType(InventoryShortageException::class.java) .expectStateAggregate { /* * 该聚合对象处于未初始化状态,即该聚合未创建成功. */ assertThat(it.initialized, equalTo(false)) }.verify() } /** * 创建订单-下单价格与当前价格不一致 */ @Test fun createOrderWhenPriceInconsistency() { val customerId = GlobalIdGenerator.generateAsString() val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) val orderItems = listOf(orderItem) val inventoryService = object : InventoryService { override fun getInventory(productId: String): Mono<Int> { return orderItems.filter { it.productId == productId }.map { it.quantity }.first().toMono() } } val pricingService = object : PricingService { override fun getProductPrice(productId: String): Mono<BigDecimal> { return orderItems.filter { it.productId == productId } /* * 模拟下单价格、商品定价不一致 */ .map { it.price.plus(BigDecimal.valueOf(1)) }.first().toMono() } } aggregateVerifier<Order, OrderState>() .inject(DefaultCreateOrderSpec(inventoryService, pricingService)) .given() .`when`(CreateOrder(customerId, orderItems, SHIPPING_ADDRESS, false)) /* * 期望:价格不一致异常. */ .expectErrorType(PriceInconsistencyException::class.java).verify() } } Saga Unit Test (SagaVerifier) Saga Test class CartSagaTest { @Test fun onOrderCreated() { val orderItem = OrderItem( GlobalIdGenerator.generateAsString(), GlobalIdGenerator.generateAsString(), BigDecimal.valueOf(10), 10, ) sagaVerifier<CartSaga>() .`when`( mockk<OrderCreated> { every { customerId } returns "customerId" every { items } returns listOf(orderItem) every { fromCart } returns true }, ) .expectCommandBody<RemoveCartItem> { assertThat(it.id, equalTo("customerId")) assertThat(it.productIds, hasSize(1)) assertThat(it.productIds.first(), equalTo(orderItem.productId)) } .verify() } } 设计 聚合建模 Single Class Inheritance Pattern Aggregation Pattern 加载聚合 聚合状态流 发送命令 命令与事件流 Saga - OrderProcessManager (Demo) 性能测试 (Example) 测试代码:Example 测试场景:加入购物车、下单 命令发送等待模式(WaitStrategy):SENT、PROCESSED 部署 Redis MongoDB Kafka Application-Config Application-Deployment 测试报告 加入购物车 请求 详细报告 (PDF)-SENT 详细报告 (PDF)-PROCESSED WaitStrategy:SENT WaitStrategy:PROCESSED 下单 请求 详细报告 (PDF)-SENT 详细报告 (PDF)-PROCESSED WaitStrategy:SENT WaitStrategy:PROCESSED

优秀的个人博客,低调大师

超 10 万个被黑的 ChatGPT 帐户在暗网出售

威胁情报机构 Group-IB 最新发布的一份报告指出,在 2022 年 6 月至今年 5 月的一年时间中,已有超过 10 万名 ChatGPT 用户的个人信息在非法暗网市场被泄露交易。其中大多数 logs (78348 条) 都是使用 Raccon 信息窃取程序侵入的,其次分别是 Vidar (12984 条) 和 Redline (6773 条)。 数据表明,随着时间的推移,被盗账户数量从 2022 年 6 月的 74 个稳步攀升至 2023 年 5 月 26902 个的峰值。意味着随着越来越多的人创建帐户并使用 GPT 来创作内容,盗窃行为或将进一步发展。 信息窃取程序是一种恶意软件,它从安装在受感染计算机上的浏览器中收集保存在浏览器中的凭据、银行卡详细信息、加密钱包信息、cookies、浏览历史和其他信息,然后将所有这些数据发送给恶意软件运营商。窃取者还可以从即时通讯工具和电子邮件中收集数据,以及有关受害者设备的详细信息。 按照地区划分的话,亚太地区的 ChatGPT 帐户被盗用数量最多达 40999 个,占比 40.5%;其中尤以印度遭受的帐户泄露最多,为 12632 个。其次为中东和非洲地区受影响账户达 24925 个、欧洲 16951 个。 Group-IB 专家强调,越来越多的员工正在利用聊天机器人来优化他们的工作,无论是软件开发还是业务沟通。默认情况下,ChatGPT 存储用户查询和 AI 响应的历史记录。因此,未经授权访问 ChatGPT 帐户可能会暴露机密或敏感信息,这些信息可能会被用来对公司及其员工进行有针对性的攻击。 “许多企业正在将 ChatGPT 集成到他们的运营流程中。员工输入机密信件或使用机器人优化专有代码。鉴于 ChatGPT 的标准配置保留了所有对话,这可能会在威胁行为者获得帐户凭据的情况下无意中向他们提供大量敏感情报。” 正是出于这些安全担忧,目前三星、苹果等科技巨头都已经禁止员工在工作中使用 ChatGPT。为降低与受感染的 ChatGPT 帐户相关的风险,Group-IB 建议用户定期更新密码并实施双因素身份验证(2FA)。 相关阅读:谷歌建议员工不要将内部信息输入 AI 聊天机器人

优秀的个人博客,低调大师

国产超好用的规则引擎框架

LiteFlow介绍 LiteFlow是一个开源编排式规则引擎,能够让你的系统逻辑任意编排,可选用脚本书写逻辑,支持多达5种脚本语言,支持丰富的第三方存储的支持,所有的逻辑和规则均可热变更。设计系统和重构系统的神器。 LiteFlow是国内优秀的社区驱动型开源项目,开源2年多,目前已经被各大公司应用在核心系统上。特性以及支持度都非常好。 如果你是第一次知道这个项目,可以去官网或相关的主页进行了解: 项目官网: https://liteflow.yomahub.com gitee托管仓库: https://gitee.com/dromara/liteFlow github托管仓库: https://github.com/dromara/liteflow v2.10.2介绍 我们为每个迭代版本都定了一个版本特性。 LiteFlow 2.10.2的版本特性就是与或非表达式。 除此之外,我们还增强了一些内容,修复了社区提出的bug。一共5个issue,作为此次小版本迭代的组成部分。 与或非表达式 社区里一直有人反应,条件编排能否在EL上写表达式,例如a==5 && b>0这种。 其实编排EL语法一切的操作对象都是组件,所以EL编排语法不能像逻辑代码一样来写很多逻辑过程。 我一直建议逻辑过程,通过java代码或者脚本组件来完成。而脚本组件是可以热更新热替换的。更加灵活。 但是在实际应用中,的确有人需要在条件编排里判断多个条件,而每个条件又是互相独立的组件。那么按照以前的写法,你只能把多个条件的逻辑塞到一个组件里,返回统一的true或者false。 这次我们新增了组件编排层面的与或非表达式,就是AND,OR,NOT表达式。 用法为方法模式:AND(a, b, c)。 可能有些社区里的同学会问,为什么不设计成a && b && c呢,或者是a AND b AND c呢。 我来解释一下,首先这种用法模式和之前的语法呼应,都是方法模式,其次操作符的模式就有点像逻辑了,而这里突出的是编排。再者操作符的模式的几个关键字都被底层占用了。 综上所述,所以延续了之前的EL表述方式。 具体文档在官网EL规则语法大章的与或非表达式小章中。 脚本新增了一些元数据 脚本中现在也可以拿到循环下标了,在元数据里加入了loopIndex和loopObject2个属性。 可以通过_meta.loopIndex和_meta.loopObject来获取到。 所有的脚本元数据可以参照官网的脚本组件大章中的与Java进行交互小章节。 选择表达式的增强和一些bug的修复 现在在选择编排语法上,之前tag属性只能添加到组件上,现在对任何的表达式后面都可以添加tag属性了。 在选择节点的返回上,更加灵活了。 具体见官网的常规组件大章中的选择组件小章节。 此次我们还另外修复了2个bug。 完整更新列表 特性 #I6RF8Y EL表达式里支持并或非操作符 https://gitee.com/dromara/liteFlow/issues/I6RF8Y 增强 #I6QOFJ groovy无法支持#循环下标获取获取,希望脚本可以支持获取循环下标 https://gitee.com/dromara/liteFlow/issues/I6QOFJ 增强 #I6RFOE LiteFlow能否在流程(表达式)添加类似tag字段的属性,提高选择组件的复用率呢? https://gitee.com/dromara/liteFlow/issues/I6RFOE 修复 #I6TRT2 EL表达式里的//被过滤掉了 https://gitee.com/dromara/liteFlow/issues/I6TRT2 修复 #I6URNQ 在CATCH表达中写单独的组件,SLOT中会拿不到异常 https://gitee.com/dromara/liteFlow/issues/I6URNQ 支持和赞助LiteFlow 开源一个项目并坚持2年并不容易,所以我也需要一点赞助来给自己充能,如果各位对LiteFlow这个项目有信心并且愿意支持我的的话,可以在官网首先点击给LiteFlow发电按钮。 但不管你是否选择赞助,我仍然会在社区里尽可能的解决你们的问题。 如何加群 LiteFlow的社区群已经有大约2500人以上了。你有任何问题,都可以在群里问。 关于加群的方式,请参考:https://liteflow.yomahub.com/pages/73c2c3/

优秀的个人博客,低调大师

NuGet 下载超 700万,.NET 框架 Furion v4.8.7.17 发布

这一个月内 Furion 解决了 12 个 Bug~,新增了 16 项特性,调整了 5 处代码,关闭了 113 个 Issues,框架越来越完善,越来越稳定。 2023 年 03 月 15 日,Furion在NuGet平台突破700万下载量。https://www.nuget.org/profiles/monk.soul 0-100万,用时 12个月 100-200万,用时 10个月 200-300万,用时 3个月 300-400万,用时 2个月 400-500万,用时 1个月 500-600万,用时 2个月 600-700万,用时 1个月 查看完整发展事记:https://furion.baiqian.ltd/docs/course 项目信息 Gitee:https://gitee.com/dotnetchina/Furion Github:https://github.com/MonkSoul/Furion 文档:https://dotnetchina.gitee.io/furion 本期亮点 1. 日志输出改进,支持配置日志输出所在程序集、类型和方法签名 旧版本 info: 2023-03-17 18:25:06.7988349 +08:00 星期五 L System.Logging.EventBusService[0] #1 EventBus hosted service is running. info: 2023-03-17 18:25:08.1393952 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1 Now listening on: https://localhost:5001 info: 2023-03-17 18:25:08.1620391 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1 Now listening on: http://localhost:5000 info: 2023-03-17 18:25:08.1972456 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1 Application started. Press Ctrl+C to shut down. info: 2023-03-17 18:25:08.2456579 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1 Hosting environment: Development info: 2023-03-17 18:25:08.2746134 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1 Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry info: 2023-03-17 18:25:18.1917784 +08:00 星期五 L Furion.Application.TestLoggerServices[0] #16 我是一个日志 20 新版本 info: 2023-03-17 18:25:06.7988349 +08:00 星期五 L System.Logging.EventBusService[0] #1 [Furion.dll] async Task Furion.EventBus.EventBusHostedService.ExecuteAsync(CancellationToken stoppingToken) EventBus hosted service is running. info: 2023-03-17 18:25:08.1393952 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1 [System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine) Now listening on: https://localhost:5001 info: 2023-03-17 18:25:08.1620391 +08:00 星期五 L Microsoft.Hosting.Lifetime[14] #1 [System.Private.CoreLib.dll] void System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start<TStateMachine>(ref TStateMachine stateMachine) Now listening on: http://localhost:5000 info: 2023-03-17 18:25:08.1972456 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1 [Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted() Application started. Press Ctrl+C to shut down. info: 2023-03-17 18:25:08.2456579 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1 [Microsoft.Extensions.Hosting.dll] void Microsoft.Extensions.Hosting.Internal.ConsoleLifetime.OnApplicationStarted() Hosting environment: Development info: 2023-03-17 18:25:08.2746134 +08:00 星期五 L Microsoft.Hosting.Lifetime[0] #1 [System.Private.CoreLib.dll] void System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(bool throwOnFirstException) Content root path: D:\Workplaces\OpenSources\Furion\samples\Furion.Web.Entry info: 2023-03-17 18:25:18.1917784 +08:00 星期五 L Furion.Application.TestLoggerServices[0] #16 [Furion.Application.dll] void Furion.Application.TestLoggerServices.测试日志() 我是一个日志 20 本期更新 新特性 [新增]Crontab.IsValid(...)静态方法,判断Cron表达式是否有效4.8.7.17⏱️2023.03.20#I6OHO4 [新增]日志配置WithStackFrame,可控制是否输出产生日志的程序集,类型和具体方法4.8.7.16⏱️2023.03.195ad6ae2 [新增] 定时任务看板UI作业列表最近执行时间列和优化显示效果4.8.7.12⏱️2023.03.1526462a8cb5dd17 [新增] 定时任务作业计划/工厂立即执行RunJob方法4.8.7.11⏱️2023.03.15#I6LD9X [新增] 定时任务看板UI提供立即执行功能4.8.7.11⏱️2023.03.15#I6LD9X [新增] 远程请求HttpRequestMessage拓展方法AppendHeaders4.8.7.10⏱️2023.03.14#I6MVHT [新增] 定时任务作业执行上下文JobExecutionContext服务提供器ServiceProvider属性4.8.7.10⏱️2023.03.1402586f8 [新增]定时任务HTTP作业,支持定时请求互联网URL地址4.8.7.7⏱️2023.03.1101d4466 [新增]定时任务作业触发器Trigger执行结果Result和执行耗时ElapsedTime属性4.8.7.7⏱️2023.03.1101d4466 [新增]定时任务作业看板支持查看作业触发器执行结果Result和执行耗时ElapsedTime属性4.8.7.7⏱️2023.03.1101d4466 [新增] 定时任务休眠时长和唤醒时机日志输出4.8.7.6⏱️2023.03.08#I6LANE [新增]Sql高级拦截支持返回IEnumerable<T>和T[]类型值4.8.7.5⏱️2023.03.07f2ca2d3 [新增].m3u8和.ts文件类型MIME支持4.8.7.5⏱️2023.03.07#I6KKEM [新增] 审计日志LoggingMonitor支持对参数贴[SuppressMonitor]特性跳过记录4.8.7.3⏱️2023.03.01#I6IVGW [新增] 审计日志LoggingMonitor监听TraceId、ThreadId、Accept-Language4.8.7.1⏱️2023.02.27df35201 [新增] 规范化结果UnifyContext.GetSerializerSettings(string)静态方法4.8.7.1⏱️2023.02.27#I6HM7T 突破性变化 [调整]定时任务动态作业DynamicJob委托/方法签名4.8.7.10⏱️2023.03.146d56b53 [升级]适配.NET8 Preview.14.8.7⏱️2023.02.22 [升级]脚手架支持创建.NET8 Preview.1项目4.8.7⏱️2023.02.22 问题修复 [修复] 使用达梦数据库执行sql不能自动修复命令参数前缀4.8.7.18⏱️2023.03.20#I6OK4T [修复]Cron表达式*符号解析器不够严谨,如:*1111aaaaa也被解析为*4.8.7.17⏱️2023.03.20#I6OHO4 [修复] 定时任务更新作业null值默认被跳过问题4.8.7.17⏱️2023.03.20#I6OHO4 [修复] 视图引擎不支持强制转换的(object)model类型4.8.7.16⏱️2023.03.19#I6O3BD [修复] 启用请求Body重复读且在授权之前读取导致非GET/HEAD/OPTION请求异常4.8.7.15⏱️2023.03.19#I6NX9E [修复] 定时任务生成SQL语句没有处理'转义问题4.8.7.15⏱️2023.03.19#I6NXKA [修复] 数据验证ValiationTypes.GUID_OR_UUID不支持大写问题4.8.7.14⏱️2023.03.16#I6NP22 [修复]Blazor脚手架出现blazor.server.js不能加载问题(404)4.8.7.13⏱️2023.03.16#I6NOBQ [修复] 定时任务服务在停止进程时会卡住30秒问题4.8.7.8⏱️2023.03.13#I6MI9I#I6MHOU [修复] 定时任务看板删除不存在的作业触发器出现空异常4.8.7.7⏱️2023.03.1101d4466 [修复] 日志消息没有处理\n换行符对齐问题4.8.7.6⏱️2023.03.10759bcc5 [修复] 审计日志LoggingMonitor对特定参数贴有[FromServices]特性依旧记录问题4.8.7.3⏱️2023.03.0117b134e [修复]Swagger接口排序同时指定Tag和Order之后无效4.8.7.2⏱️2023.03.01#I6IQDI#I6IP66 其他更改 [调整] 视图引擎默认程序集,追加System.Collections程序集4.8.7.16⏱️2023.03.18#I6O3BD [调整] 定时任务配置选项BuilSqlType属性命为BuildSqlType4.8.7.11⏱️2023.03.1592117b8 [调整]定时任务查看作业触发器运行记录由保存10条改为5条4.8.7.7⏱️2023.03.0701d4466 [调整] 脚手架模板,默认启用主流文件类型MIME支持4.8.7.5⏱️2023.03.07e35cdab [调整] 审计日志LoggingMonitor返回值泛型字符串显示格式4.8.7.1⏱️2023.02.27df35201 文档 [新增]ASP.NET 8 集成文档 [新增].NET7 升级 .NET8文档 [更新] 定时任务文档、中间件文档、规范化结果文档、动态WebAPI文档、日志记录文档、事件总线文档、虚拟文件系统文档、Sql高级代理文档、数据库实体文档、任务队列文档、跨域文档、配置选项文档、安全授权、脚手架文档 贡献者 lampon (@lampon)!740 family520 (@family520)!739 kingling (@kinglinglive)!732!729 ksmy (@ksmy)!731 handsome_by (@handsomeboyyl)!727

优秀的个人博客,低调大师

MakuGenerator v2.1.1 发布,超好用的代码生成器

介绍 maku-generator 是一款低代码生成器,可根据自定义模板内容,快速生成代码,可实现项目的快速开发、上线,减少重复的代码编写,开发人员只需专注业务逻辑即可。采用 MIT 开源协议,完全免费开源,可免费用于商业项目等场景。 开发文档:https://maku.net/docs/maku-generator 演示环境:https://demo.maku.net/maku-generator 官网地址:https://maku.net 更新日志 升级element-plus到2.2.28 升级vite到3.2.5 升级springboot到2.7.7 优化日志打印处理 修复列表查询,页面下拉异常 项目特点 友好的代码结构及注释,便于阅读及二次开发 支持 spring boot starter,能很方便集成到第三方项目 支持通过配置数据源,快速生成 CRUD 代码,减少重复工作 支持 MySQL、Oracle、SQLServer、PostgreSQL、达梦 8 等主流的数据库 支持第三方 Java 项目包名修改,修改包名变得简单快速 支持批量导入表、批量生成代码以及同步表结构等功能 Git 仓库 Gitee 仓库:https://gitee.com/makunet/maku-generator Github 仓库:https://github.com/makunet/maku-generator 效果图

优秀的个人博客,低调大师

带你认识JDK8中超nice的Native Memory Tracking

摘要:从 OpenJDK8 起有了一个很 nice 的虚拟机内部功能: Native Memory Tracking (NMT)。 本文分享自华为云社区《Native Memory Tracking 详解(1):基础介绍》,作者:毕昇小助手。 0.引言 我们经常会好奇,我启动了一个 JVM,他到底会占据多大的内存?他的内存都消耗在哪里?为什么 JVM 使用的内存比我设置的 -Xmx 大这么多?我的内存设置参数是否合理?为什么我的 JVM 内存一直缓慢增长?为什么我的 JVM 会被 OOMKiller 等等,这都涉及到 JAVA 虚拟机对内存的一个使用情况,不如让我们来一探其中究竟。 1.简介 除去大家都熟悉的可以使用 -Xms、-Xmx 等参数设置的堆(Java Heap),JVM 还有所谓的非堆内存(Non-Heap Memory)。 可以通过一张图来简单看一下 Java 进程所使用的内存情况(简略情况): 非堆内存包括方法区和Java虚拟机内部做处理或优化所需的内存。 方法区:在所有线程之间共享,存储每个类的结构,如运行时常量池、字段和方法数据,以及方法和构造函数的代码。方法区在逻辑上(虚拟机规范)是堆的一部分,但规范并不限定实现方法区的内存位置和编译代码的管理策略,所以不同的 Java 虚拟机可能有不同的实现方式,此处我们仅讨论 HotSpot。 除了方法区域外,Java 虚拟机实现可能需要内存用于内部的处理或优化。例如,JIT编译器需要内存来存储从Java虚拟机代码转换的本机代码(储存在CodeCache中),以获得高性能。 从 OpenJDK8 起有了一个很 nice 的虚拟机内部功能: Native Memory Tracking (NMT) 。我们可以使用 NMT 来追踪了解 JVM 的内存使用详情(即上图中的 JVM Memory 部分),帮助我们排查内存增长与内存泄漏相关的问题。 2.如何使用 2.1 开启 NMT 默认情况下,NMT是处于关闭状态的,我们可以通过设置 JVM 启动参数来开启:-XX:NativeMemoryTracking=[off | summary | detail]。 注意:启用NMT会导致5% -10%的性能开销。 NMT 使用选项如下表所示: 我们注意到,如果想使用 NMT 观察 JVM 的内存使用情况,我们必须重启 JVM 来设置XX:NativeMemoryTracking 的相关选项,但是重启会使得我们丢失想要查看的现场,只能等到问题复现时才能继续观察。 笔者试图通过一种不用重启 JVM 的方式来开启 NMT ,但是很遗憾目前没有这样的功能。 JVM 启动后只有被标记为 manageable 的参数才可以动态修改或者说赋值,我们可以通过 JDK management interface (com.sun.management.HotSpotDiagnosticMXBean API) 或者 jinfo -flag 命令来进行动态修改的操作,让我们看下所有可以被修改的参数值(JDK8): java -XX:+PrintFlagsFinal | grep manageable intx CMSAbortablePrecleanWaitMillis = 100 {manageable} intx CMSTriggerInterval = -1 {manageable} intx CMSWaitDuration = 2000 {manageable} bool HeapDumpAfterFullGC = false {manageable} bool HeapDumpBeforeFullGC = false {manageable} bool HeapDumpOnOutOfMemoryError = false {manageable} ccstr HeapDumpPath = {manageable} uintx MaxHeapFreeRatio = 100 {manageable} uintx MinHeapFreeRatio = 0 {manageable} bool PrintClassHistogram = false {manageable} bool PrintClassHistogramAfterFullGC = false {manageable} bool PrintClassHistogramBeforeFullGC = false {manageable} bool PrintConcurrentLocks = false {manageable} bool PrintGC = false {manageable} bool PrintGCDateStamps = false {manageable} bool PrintGCDetails = false {manageable} bool PrintGCID = false {manageable} bool PrintGCTimeStamps = false {manageable} 很显然,其中不包含 NativeMemoryTracking 。 2.2 使用 jcmd 访问 NMT 数据 我们可以通过 jcmd 命令来很方便的查看 NMT 相关的数据: jcmd VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] [scale= KB | MB | GB] jcmd 操作 NMT 选项如下表所示: NMT 默认打印的报告是 KB 来进行呈现的,为了满足我们不同的需求,我们可以使用scale=MB | GB来更加直观的打印数据。 创建 baseline 之后使用 diff 功能可以很直观地对比出两次 NMT 数据之间的差距。 看到 shutdown 选项,笔者本能的一激灵,既然我们可以通过 shutdown 来关闭 NMT ,那为什么不能通过逆向 shutdown 功能来动态的开启 NMT 呢?笔者找到 shutdown 相关源码(以下都是基于 OpenJDK 8): # hotspot/src/share/vm/services/nmtDCmd.cpp void NMTDCmd::execute(DCmdSource source, TRAPS) { // Check NMT state // native memory tracking has to be on if (MemTracker::tracking_level() == NMT_off) { output()->print_cr("Native memory tracking is not enabled"); return; } else if (MemTracker::tracking_level() == NMT_minimal) { output()->print_cr("Native memory tracking has been shutdown"); return; } ...... //执行 shutdown 操作 else if (_shutdown.value()) { MemTracker::shutdown(); output()->print_cr("Native memory tracking has been turned off"); } ...... } # hotspot/src/share/vm/services/memTracker.cpp // Shutdown can only be issued via JCmd, and NMT JCmd is serialized by lock void MemTracker::shutdown() { // We can only shutdown NMT to minimal tracking level if it is ever on. if (tracking_level () > NMT_minimal) { transition_to(NMT_minimal); } } # hotspot/src/share/vm/services/nmtCommon.hpp // Native memory tracking level //NMT的追踪等级 enum NMT_TrackingLevel { NMT_unknown = 0xFF, NMT_off = 0x00, NMT_minimal = 0x01, NMT_summary = 0x02, NMT_detail = 0x03 }; 遗憾的是通过源码我们发现,shutdown 操作只是将 NMT 的追踪等级 tracking_level 变成了 NMT_minimal 状态(而并不是直接变成了 off 状态),注意注释:We can only shutdown NMT to minimal tracking level if it is ever on(即我们只能将NMT关闭到最低跟踪级别,如果它曾经打开)。 这就导致了如果我们没有开启过 NMT ,那就没办法通过魔改 shutdown 操作逆向打开 NMT ,因为 NMT 追踪的部分内存只在 JVM 启动初始化的阶段进行记录(如在初始化堆内存分配的过程中通过 NMT_TrackingLevel level = MemTracker::tracking_level(); 来获取 NMT 的追踪等级,视等级来记录内存使用情况),JVM 启动之后再开启 NMT 这部分内存的使用情况就无法记录,所以目前来看,还是只能在重启 JVM 后开启 NMT。 至于提供 shutdown 功能的原因,应该就是让用户在开启 NMT 功能之后如果想要关闭,不用再次重启 JVM 进程。shutdown 会清理虚拟内存用来追踪的数据结构,并停止一些追踪的操作(如记录 malloc 内存的分配)来降低开启 NMT 带来的性能耗损,并且通过源码可以发现 tracking_level 变成 NMT_minimal 状态后也不会再执行 jcmd VM.native_memory 命令相关的操作。 2.3 虚拟机退出时获取 NMT 数据 除了在虚拟机运行时获取 NMT 数据,我们还可以通过两个参数:-XX:+UnlockDiagnosticVMOptions和-XX:+PrintNMTStatistics ,来获取虚拟机退出时内存使用情况的数据(输出数据的详细程度取决于你设定的跟踪级别,如 summary/detail 等)。 -XX:+UnlockDiagnosticVMOptions:解锁用于诊断 JVM 的选项,默认关闭。 -XX:+PrintNMTStatistics:当启用 NMT 时,在虚拟机退出时打印内存使用情况,默认关闭,需要开启前置参数 -XX:+UnlockDiagnosticVMOptions才能正常使用。 3.NMT 内存 & OS 内存概念差异性 我们可以做一个简单的测试,使用如下参数启动 JVM : -Xmx1G -Xms1G -XX:+UseG1GC -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=256m -XX:ReservedCodeCacheSize=256M -XX:NativeMemoryTracking=detail 然后使用 NMT 查看内存使用情况(因各环境资源参数不一样,部分未明确设置数据可能由虚拟机根据资源自行计算得出,以下数据仅供参考): jcmd VM.native_memory detail NMT 会输出如下日志: Native Memory Tracking: Total: reserved=2813709KB, committed=1497485KB - Java Heap (reserved=1048576KB, committed=1048576KB) (mmap: reserved=1048576KB, committed=1048576KB) - Class (reserved=1056899KB, committed=4995KB) (classes #442) (malloc=131KB #259) (mmap: reserved=1056768KB, committed=4864KB) - Thread (reserved=258568KB, committed=258568KB) (thread #127) (stack: reserved=258048KB, committed=258048KB) (malloc=390KB #711) (arena=130KB #234) - Code (reserved=266273KB, committed=4001KB) (malloc=33KB #309) (mmap: reserved=266240KB, committed=3968KB) - GC (reserved=164403KB, committed=164403KB) (malloc=92723KB #6540) (mmap: reserved=71680KB, committed=71680KB) - Compiler (reserved=152KB, committed=152KB) (malloc=4KB #36) (arena=148KB #21) - Internal (reserved=14859KB, committed=14859KB) (malloc=14827KB #3632) (mmap: reserved=32KB, committed=32KB) - Symbol (reserved=1423KB, committed=1423KB) (malloc=936KB #111) (arena=488KB #1) - Native Memory Tracking (reserved=330KB, committed=330KB) (malloc=118KB #1641) (tracking overhead=211KB) - Arena Chunk (reserved=178KB, committed=178KB) (malloc=178KB) - Unknown (reserved=2048KB, committed=0KB) (mmap: reserved=2048KB, committed=0KB) ...... 大家可能会发现 NMT 所追踪的内存(即 JVM 中的 Reserved、Committed)与操作系统 OS (此处指Linux)的内存概念存在一定的差异性。 首先按我们理解的操作系统的概念: 操作系统对内存的分配管理典型地分为两个阶段:保留(reserve)和提交(commit)。保留阶段告知系统从某一地址开始到后面的dwSize大小的连续虚拟内存需要供程序使用,进程其他分配内存的操作不得使用这段内存;提交阶段将虚拟地址映射到对应的真实物理内存中,这样这块内存就可以正常使用 [1]。 如果使用 top 或者 smem 等命令查看刚才启动的 JVM 进程会发现: top PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 36257 dou+ 20 0 10.8g 54200 17668 S 99.7 0.0 13:04.15 java 此时疑问就产生了,为什么 NMT 中的 committed ,即日志详情中 Total: reserved=2813709KB, committed=1497485KB 中的 1497485KB 与 top 中 RES 的大小54200KB 存在如此大的差异? 使用 man 查看 top 中 RES 的概念(不同版本 Linux 可能不同): RES -- Resident Memory Size (KiB) A subset of the virtual address space (VIRT) representing the non-swapped physical memory a task is currently using. It is also the sum of the RSan, RSfd and RSsh fields. It can include private anonymous pages, private pages mapped to files (including program images and shared libraries) plus shared anonymous pages. All such memory is backed by the swap file represented separately under SWAP. Lastly, this field may also include shared file-backed pages which, when modified, act as a dedicated swap file and thus will never impact SWAP. RES 表示任务当前使用的非交换物理内存(此时未发生swap),那按对操作系统 commit 提交内存的理解,这两者貌似应该对上,为何现在差距那么大呢? 笔者一开始猜测是 JVM 的 uncommit 机制(如 JEP 346[2],支持 G1 在空闲时自动将 Java 堆内存返回给操作系统,BiSheng JDK 对此做了增强与改进[3])造成的,JVM 在 uncommit 将内存返还给 OS 之后,NMT 没有除去返还的内存导致统计错误。 但是在翻阅了源码之后发现,G1 在 shrink 缩容的时候,通常调用链路如下: G1CollectedHeap::shrink-> G1CollectedHeap::shrink_helper-> HeapRegionManager::shrink_by-> HeapRegionManager::uncommit_regions-> G1PageBasedVirtualSpace::uncommit-> G1PageBasedVirtualSpace::uncommit_internal-> os::uncommit_memory 忽略细节,uncommit 会在最后调用 os::uncommit_memory ,查看 os::uncommit_memory 源码: bool os::uncommit_memory(char* addr, size_t bytes) { bool res; if (MemTracker::tracking_level() > NMT_minimal) { Tracker tkr = MemTracker::get_virtual_memory_uncommit_tracker(); res = pd_uncommit_memory(addr, bytes); if (res) { tkr.record((address)addr, bytes); } } else { res = pd_uncommit_memory(addr, bytes); } return res; } 可以发现在返还 OS 内存之后,MemTracker 是进行了统计的,所以此处的误差不是由 uncommit 机制造成的。 既然如此,那又是由什么原因造成的呢?笔者在追踪 JVM 的内存分配逻辑时发现了一些端倪,此处以Code Cache(存放 JVM 生成的 native code、JIT编译、JNI 等都会编译代码到 native code,其中 JIT 生成的 native code 占用了 Code Cache 的绝大部分空间)的初始化分配为例,其大致调用链路为下: InitializeJVM-> Thread::vreate_vm-> init_globals-> codeCache_init-> CodeCache::initialize-> CodeHeap::reserve-> VirtualSpace::initialize-> VirtualSpace::initialize_with_granularity-> VirtualSpace::expand_by-> os::commit_memory 查看 os::commit_memory 相关源码: bool os::commit_memory(char* addr, size_t size, size_t alignment_hint, bool executable) { bool res = os::pd_commit_memory(addr, size, alignment_hint, executable); if (res) { MemTracker::record_virtual_memory_commit((address)addr, size, CALLER_PC); } return res; } 我们发现 MemTracker 在此记录了 commit 的内存供 NMT 用以统计计算,继续查看 os::pd_commit_memory 源码,可以发现其调用了 os::Linux::commit_memory_impl 函数。 查看 os::Linux::commit_memory_impl 源码: int os::Linux::commit_memory_impl(char* addr, size_t size, bool exec) { int prot = exec ? PROT_READ|PROT_WRITE|PROT_EXEC : PROT_READ|PROT_WRITE; uintptr_t res = (uintptr_t) ::mmap(addr, size, prot, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0); if (res != (uintptr_t) MAP_FAILED) { if (UseNUMAInterleaving) { numa_make_global(addr, size); } return 0; } int err = errno; // save errno from mmap() call above if (!recoverable_mmap_error(err)) { warn_fail_commit_memory(addr, size, exec, err); vm_exit_out_of_memory(size, OOM_MMAP_ERROR, "committing reserved memory."); } return err; } 问题的原因就在 uintptr_t res = (uintptr_t) ::mmap(addr, size, prot, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0); 这段代码上。 我们发现,此时申请内存执行的是 mmap 函数,并且传递的 port 参数是 PROT_READ|PROT_WRITE|PROT_EXEC 或 PROT_READ|PROT_WRITE ,使用 man 查看 mmap ,其中相关描述为: The prot argument describes the desired memory protection of the mapping (and must not conflict with the open mode of the file). It is either PROT_NONE or the bitwise OR of one or more of the following flags: PROT_EXEC Pages may be executed. PROT_READ Pages may be read. PROT_WRITE Pages may be written. PROT_NONE Pages may not be accessed. 由此我们可以看出,JVM 中所谓的 commit 内存,只是将内存mmaped映射为可读可写可执行的状态!而在 Linux 中,在分配内存时又是 lazy allocation 的机制,只有在进程真正访问时才分配真实的物理内存。所以 NMT 中所统计的 committed 并不是对应的真实的物理内存,自然与 RES 等统计方式无法对应起来。 所以 JVM 为我们提供了一个参数 -XX:+AlwaysPreTouch,使我们可以在启动之初就按照内存页粒度都访问一遍 Heap,强制为其分配物理内存以减少运行时再分配内存造成的延迟(但是相应的会影响 JVM 进程初始化启动的时间),查看相关代码: void os::pretouch_memory(char* start, char* end) { for (volatile char *p = start; p < end; p += os::vm_page_size()) { *p = 0; } } 让我们来验证下,开启 -XX:+AlwaysPreTouch 前后的效果。 NMT 的 heap 地址范围: Virtual memory map: [0x00000000c0000000 - 0x0000000100000000] reserved 1048576KB for Java Heap from [0x0000ffff93ea36d8] ReservedHeapSpace::ReservedHeapSpace(unsigned long, unsigned long, bool, char*)+0xb8 [0x0000ffff93e67f68] Universe::reserve_heap(unsigned long, unsigned long)+0x2d0 [0x0000ffff93898f28] G1CollectedHeap::initialize()+0x188 [0x0000ffff93e68594] Universe::initialize_heap()+0x15c [0x00000000c0000000 - 0x0000000100000000] committed 1048576KB from [0x0000ffff938bbe8c] G1PageBasedVirtualSpace::commit_internal(unsigned long, unsigned long)+0x14c [0x0000ffff938bc08c] G1PageBasedVirtualSpace::commit(unsigned long, unsigned long)+0x11c [0x0000ffff938bf774] G1RegionsLargerThanCommitSizeMapper::commit_regions(unsigned int, unsigned long)+0x5c [0x0000ffff93943f54] HeapRegionManager::commit_regions(unsigned int, unsigned long)+0x7c 对应该地址的/proc/{pid}/smaps: //开启前 //开启后 c0000000-100080000 rw-p 00000000 00:00 0 c0000000-100080000 rw-p 00000000 00:00 0 Size: 1049088 kB Size: 1049088 kB KernelPageSize: 4 kB KernelPageSize: 4 kB MMUPageSize: 4 kB MMUPageSize: 4 kB Rss: 792 kB Rss: 1049088 kB Pss: 792 kB Pss: 1049088 kB Shared_Clean: 0 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Clean: 0 kB Private_Dirty: 792 kB Private_Dirty: 1049088 kB Referenced: 792 kB Referenced: 1048520 kB Anonymous: 792 kB Anonymous: 1049088 kB LazyFree: 0 kB LazyFree: 0 kB AnonHugePages: 0 kB AnonHugePages: 0 kB ShmemPmdMapped: 0 kB ShmemPmdMapped: 0 kB Shared_Hugetlb: 0 kB Shared_Hugetlb: 0 kB Private_Hugetlb: 0 kB Private_Hugetlb: 0 kB Swap: 0 kB Swap: 0 kB SwapPss: 0 kB SwapPss: 0 kB Locked: 0 kB Locked: 0 kB VmFlags: rd wr mr mw me ac VmFlags: rd wr mr mw me ac 对应的/proc/{pid}/status: //开启前 //开启后 ... ... VmHWM: 54136 kB VmHWM: 1179476 kB VmRSS: 54136 kB VmRSS: 1179476 kB ... ... VmSwap: 0 kB VmSwap: 0 kB ... 开启参数后的 top: PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 85376 dou+ 20 0 10.8g 1.1g 17784 S 99.7 0.4 14:56.31 java 观察对比我们可以发现,开启 AlwaysPreTouch 参数后,NMT 统计的 commited 已经与 top 中的 RES 差不多了,之所以不完全相同是因为该参数只能 Pre-touch 分配 Java heap 的物理内存,至于其他的非 heap 的内存,还是受到 lazy allocation 机制的影响。 同理我们可以简单看下 JVM 的 reserve 机制: # hotspot/src/share/vm/runtime/os.cpp char* os::reserve_memory(size_t bytes, char* addr, size_t alignment_hint, MEMFLAGS flags) { char* result = pd_reserve_memory(bytes, addr, alignment_hint); if (result != NULL) { MemTracker::record_virtual_memory_reserve((address)result, bytes, CALLER_PC); MemTracker::record_virtual_memory_type((address)result, flags); } return result; } # hotspot/src/os/linux/vm/os_linux.cpp char* os::pd_reserve_memory(size_t bytes, char* requested_addr, size_t alignment_hint) { return anon_mmap(requested_addr, bytes, (requested_addr != NULL)); } static char* anon_mmap(char* requested_addr, size_t bytes, bool fixed) { ...... addr = (char*)::mmap(requested_addr, bytes, PROT_NONE, flags, -1, 0); ...... } reserve 通过 mmap(requested_addr, bytes, PROT_NONE, flags, -1, 0); 来将内存映射为 PROT_NONE,这样其他的 mmap/malloc 等就不能调用使用,从而达到了 guard memory 或者说 guard pages 的目的。 OpenJDK 社区其实也注意到了 NMT 内存与 OS 内存差异性的问题,所以社区也提出了相应的 Enhancement 来增强功能: 1.JDK-8249666[4] : 目前 NMT 将分配的内存显示为 Reserved 或 Committed。而在 top 或 pmap 的输出中,首次使用(即 touch)之前 Reserved 和 Committed 的内存都将显示为 Virtual memory。只有在内存页(通常是4k)首次写入后,它才会消耗物理内存,并出现在 top/pmap 输出的 “常驻内存”(即 RSS)中。 当前NMT输出的主要问题是,它无法区分已 touch 和未 touch 的 Committed 内存。 该 Enhancement 提出可以使用 mincore() [5]来查找 NMT 的 Committed 中 RSS 的部分,mincore() 系统调用让一个进程能够确定一块虚拟内存区域中的分页是否驻留在物理内存中。mincore()已在JDK-8191369 NMT:增强线程堆栈跟踪中实现,需要将其扩展到所有其他类型的内存中(如 Java 堆)。 遗憾的是该 Enhancement 至今仍是 Unresolved 状态。 2.JDK-8191369[6] : 1 中提到的 NMT:增强线程堆栈跟踪。使用 mincore() 来追踪驻留在物理内存中的线程堆栈的大小,用以解决线程堆栈追踪时有时会夸大内存使用情况的痛点。 该 Enhancement 已经在 JDK11 中实现。 参考 https://weread.qq.com/web/reader/53032310717f44515302749k37632cd021737693cfc7149 http://openjdk.java.net/jeps/346 https://gitee.com/openeuler/bishengjdk-8/wikis/G1GC内存伸缩特性介绍?sort_id=3340035 https://bugs.openjdk.org/browse/JDK-8249666 https://man7.org/linux/man-pages/man2/mincore.2.html https://bugs.openjdk.org/browse/JDK-8191369 点击关注,第一时间了解华为云新鲜技术~

优秀的个人博客,低调大师

Github 出现大规模恶意提交,受影响仓库超 35000 个

推特用户@Stephen Lacy发现 GitHub 上存在大规模的混淆恶意软件攻击,目前有超过 35,000 个存储库受影响,包括crypto、golang、python、js、bash、docker、k8s 等知名项目。 这些恶意软件攻击伪装得非常好,看起来像人畜无害的提交,比如带着“bump version to 0.3.11”之类的消息: 其中一些被混淆成合法的 PR,但其实仓库没有收到任何 PR,反而仓库中的每个 go 文件都被感染了: 其中一些仓库的历史记录包括来自原作者的提交,但该提交未经 GPG 验证,这就意味着提交是攻击者伪装的。除了原作者,恶意软件也可能伪装成其他开发者,但点进去就会发现用户不存在。 这部分恶意攻击与 GiuHub 本身的漏洞相关,比如之前我们报道过的Linus利用 GitHub 漏洞发布恶作剧 README,用户可以 “通过 git 电子邮件地址冒充用户” ,然后利用 https://github.com/my/project/blob/<faked_commit> 这种 URL 发布任意提交。 这些攻击会将脚本、应用程序、笔记本电脑(电子应用程序)等包括安全密钥、AWS 访问密钥、加密密钥等帐户凭证整个 ENV 发送到攻击者的服务器。目前大部分恶意攻击提交都已被清理,但仍有新的在产生,建议大家使用 GPG 签署每个提交。

资源下载

更多资源
Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册