。定位到根本原因,每个表达式都是一个类中一个方法。随着运行时间越长,执行次数增加,这些方法会被JIT优化编译进入到Code Cache中,导致CodeCache越来越大。
解决方法:JIT有一些参数配置可以调整JIT编译的条件,但对于我们的问题都不太适用。我们最终通过业务优化解决,删除不需要执行的Aviator表达式,从而避免了大量Aviator方法进入CodeCache中。
值得一提的是,我们并不是在所有这些问题都解决后才全量部署所有集群。即使开始有各种各样的毛刺,但计算后发现,有各种问题的ZGC也比之前的CMS对服务可用性影响小。所以从开始准备使用ZGC到全量部署,大概用了2周的时间。在之后的3个月时间里,我们边做业务需求,边跟进这些问题,最终逐个解决了上述问题,从而使ZGC在各个集群上达到了一个更好表现。
升级ZGC效果
延迟降低
| TP(Top Percentile)是一项衡量系统延迟的指标:TP999表示99.9%请求都能被响应的最小耗时;TP99表示99%请求都能被响应的最小耗时。
在Zeus服务不同集群中,ZGC在低延迟(TP999 < 200ms)场景中收益较大:
TP999:下降12~142ms,下降幅度18%~74%。
TP99:下降5~28ms,下降幅度10%~47%。
超低延迟(TP999 < 20ms)和高延迟(TP999 > 200ms)服务收益不大,原因是这些服务的响应时间瓶颈不是GC,而是外部依赖的性能。
吞吐下降
对吞吐量优先的场景,ZGC可能并不适合。例如,Zeus某离线集群原先使用CMS,升级ZGC后,系统吞吐量明显降低。究其原因有二:第一,ZGC是单代垃圾回收器,而CMS是分代垃圾回收器。单代垃圾回收器每次处理的对象更多,更耗费CPU资源;第二,ZGC使用读屏障,读屏障操作需耗费额外的计算资源。
总结
ZGC作为下一代垃圾回收器,性能非常优秀。ZGC垃圾回收过程几乎全部是并发,实际STW停顿时间极短,不到10ms。这得益于其采用的着色指针和读屏障技术。
Zeus在升级JDK 11+ZGC中,通过将风险和问题分类,然后各个击破,最终顺利实现了升级目标,GC停顿也几乎不再影响系统可用性。
最后推荐大家升级ZGC,Zeus系统因为业务特点,遇到了较多问题,而风控其他团队在升级时都非常顺利。
参考文献
附录
如何使用新技术
在生产环境升级JDK 11,使用ZGC,大家最关心的可能不是效果怎么样,而是这个新版本用的人少,网上实践也少,靠不靠谱,稳不稳定。其次是升级成本会不会很大,万一不成功岂不是白白浪费时间。所以,在使用新技术前,首先要做的是评估收益、成本和风险。
评估收益
对于JDK这种世界关注的程序,大版本升级所引入的新技术一般已经在理论上经过验证。我们要做的事情就是确定当前系统的瓶颈是否是新版本JDK可解决的问题,切忌问题未诊断清楚就采取措施。评估完收益之后再评估成本和风险,收益过大或者过小,其他两项影响权重就会小很多。
以本文开头提到的案例为例,假设GC次数不变(10次/分钟),且单次GC时间从40ms降低10ms。通过计算,一分钟内有100/60000 = 0.17%的时间在进行GC,且期间所有请求仅停顿10ms,GC期间影响的请求数和因GC增加的延迟都有所减少。
评估成本
这里主要指升级所需要的人力成本。此项相对比较成熟,根据新技术的使用手册判断改动点。跟做其他项目区别不大,不再具体细说。
在我们的实践中,两周时间完成线上部署,达到安全稳定运行的状态。后续持续迭代3个月,根据业务场景对ZGC进行了更契合的优化适配。
评估风险
升级JDK的风险可以分为三类:
兼容性风险:Java程序JAR包依赖很多,升级JDK版本后程序是否能运行起来。例如我们的服务是从JDK 7升级到JDK 11,需要解决较多JAR包不兼容的问题。
功能风险:运行起来后,是否会有一些组件逻辑变更,影响现有功能的逻辑。
性能风险:功能如果没有问题,性能是否稳定,能稳定的在线上运行。
经过分类后,每类风险的应对转化成了常见的测试问题,不再属于未知风险。风险是指不确定的事情,如果不确定的事情都能转化成可确定的事情,意味着风险已消除。
升级JDK 11
选择JDK 11,是因为在JDK 11中首次支持ZGC,而且JDK 11属于长期支持(Long Term Support,LTS)版本,至少会被维护三年,普通版本(如JDK 12、JDK 13和JDK 14)只有6个月的维护周期,不建议使用。
本地测试环境安装
从两个源OpenJDK和OracleJDK下载JDK 11,二个版本的JDK主要区别是长时期的免费和付费,短期内都免费。注意JDK 11版本中的ZGC不支持mac OS系统,在Mac OS系统上使用JDK 11只能用其他垃圾回收器,如G1。
生产环境安装
升级JDK 11不仅仅是升级自己项目的JDK版本,还需要编译、发布部署、运行、监控、性能内存分析工具等项目支持。美团内部的实践:
编译打包:美团发布系统支持选择JDK 11进行编译打包。
线上运行 & 全量部署:要求线上机器已安装JDK 11,有3种方式:
新申请默认安装JDK 11的虚拟机:试用JDK 11时可用这种方式;全量部署时,如果新申请机器数量过多,可能没有足够机器资源。
通过手写脚本给存量虚拟机安装JDK 11:不推荐,业务同学过多参与到运维当中。
使用容器提供的镜像部署功能,在打包镜像时安装JDK 11:推荐方式,不需要新申请资源。
监控指标:主要是GC的时间和频率,我们通过美团的CAT监控系统支持ZGC数据的收集(CAT已开源)。
性能内存分析:线上遇到性能问题时,还需要借助Profiling工具,美团的性能诊断优化平台Scalpel已支持JDK 11的性能内存分析。如果你的公司没有相关工具,推荐使用JProfier。
解决组件兼容性
我们的项目包含二十多万行代码,需要从JDK 7升级到JDK 11,依赖组件众多。虽然看起来升级会比较复杂,但实际只花了两天时间即解决了兼容性问题。具体过程如下:
1. 编译,需要修改pom文件中的build配置,根据报错作修改,主要有两类:
a. 一些类被删除:比如“sun.misc.BASE64Encoder”,找到替换类java.util.Base64即可。
b. 组件依赖版本不兼容JDK 11问题:找到对应依赖组件,搜索最新版本,一般都支持JDK 11。
2. 编译成功后,启动运行,此时仍有可能组件依赖版本问题,按照编译时的方式处理即可。
升级所修改的依赖:
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.4</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator-parent</artifactId>
<version>6.0.16.Final</version>
</dependency>
<dependency>
<groupId>com.sankuai.inf</groupId>
<artifactId>patriot-sdk</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>org.Apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.39.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
JDK 11已经出来两年,常见的依赖组件都有兼容性版本。但是,如果是公司内部提供的公司级组件,可能会不兼容JDK 11,需要推动相关组件进行升级。如果对方升级较为困难,可以考虑拆分功能,将依赖这些组件的功能单独部署,继续使用低版本JDK。随着JDK 11的卓越性能被大家悉知,相信会有更多团队会用JDK 11解决GC问题,使用者越多,各个组件升级的动力也会越大。
验证功能正确性
通过完备的单测、集成和回归测试,保证功能正确性。
---------- END ----------
招聘信息
看完文章的你,如果内心燃起了想与笔者一起共事的冲动,不要犹豫,直接把简历砸过来!团队正在寻同道中人,详细岗位介绍如下,欢迎大家踊跃自荐、推荐!
资深Java工程师(风控方向)
工作城市:北京、上海
岗位职责:
1. 负责开发高并发高可用低延时的风控系统 ,对现有产品和系统进行改进和优化。从业务和技术出发,实现面向未来的系统规划、设计和落地。
2. 独立完成较复杂的系统分析、设计,并主导完成详细设计和编码的任务,确保项目的进度和质量。
3. 在团队中完成Code Review任务,确保相关代码的有效性和正确性,并能够通过Code Review提供相关性能以及稳定性的建议。
4. 技术预研和技术难点攻关,保障系统可用性、稳定性、和可扩展性。
任职要求:
1. 扎实的Java编程基础,良好的编程素养,对代码美感有追求。
2. 熟悉分布式系统和架构,对高性能、高可用架构的最佳实践以及设计原则有理解。
3. 精通微服务、一致性等分布式技术;对互联网常用技术有深入的了解,对开源产品本身有过开发经验者。
4. 对技术有激情,喜欢钻研,能快速接受和掌握新技术,有较强的独立、主动的学习能力,良好的沟通表达能力和团队协作能力。
5. 具备系统调试、性能调优等技能,对疑难技术问题具备较强的排查能力;有强烈的责任心和使命感。
6. 具备大规模、高吞吐量的系统开发实践经验。
欢迎发送简历至:sunny.fang@dianping.com。邮件主题请注明(城市-美团技术团队公众号)
此外,美团信息安全部更多职位持续开放中,虚位以待,就等你来!具体岗位介绍,欢迎大家点击【阅读原文】了解!
GIAC 全球互联网架构大会 2020 将于 8 月 14 - 15 日在深圳举行,本届 GIAC 议题共设置有 24 个专题,大会包含
技术原创及架构实践文章,欢迎通过公众号菜单「联系我们」进行投稿。
高可用架构
改变互联网的构建方