作者简介
NekoMatryoshka,携程酒店资深后端开发工程师,主要工作是缓存类组件的开发维护,并对业务应用的排障和优化有所关注。
一、背景和目标
在容器化部署成为主流的现在,降低集群中单个容器的资源需求的意义已经不只限于更少的硬件成本,同时也意味着整个集群更加轻量化,这通常会带来一系列其他优势:例如更短的恢复时间,更精确的资源控制和调度,和更快速的伸缩和部署等。但在另一方面,一味的追求压缩容器配置必然会严重影响应用在稳定性、响应耗时和吞吐量等方面的表现,所以轻量化的措施需要在多个性能维度上进行仔细的权衡取舍,以达到一个总体更优的结果。
作为携程计算量最大的接口之一,酒店查询服务一直承担着沉重的硬件成本压力,仅仅详情页集群就包含了千余台服务器实例和数十TB的Redis资源,因此对应用进行全面的轻量化有着很高的必要性和预期收益。在内存方向上,我们的主要目标是将单个容器的内存从32GB压缩到16GB,并在以下两个基本方向上进行了探索:
减少内存增长速度:压缩本地缓存,减少浮动内存的产生,并对线程,类库,参数和代码逻辑进行针对性的优化和调整。
提升内存管理效率:加强JVM等服务依赖的基础组件其本身的性能。
由于第一个方向需要根据应用的具体代码实现来分析和排查,普适性相对较差,所以本文将主要分享查询服务在轻量化中对于内存管理方向上的探索过程和实践经验。
二、堆内内存管理
我们的应用原本运行在JDK8的CMS收集器之上,但是在JDK11以后,CMS已经被完全淘汰。于是,要提高堆内的内存管理效率,我们首先尝试的便是对GC进行升级和调优。因此我们对G1、ZGC和ShenandoahGC等更现代的收集器进行了性能上的测试和对比,来尝试找出最合适的技术选型。
2.1 垃圾收集器的选型
首先,在JDK17上,ZGC第一次以生产环境可用的状态登陆了LTS版本,所以我们这次选型起初的目标也是尝试将应用迁移到ZGC之上。相对于大家熟悉的G1,ZGC最主要的优势在于其通过着色指针和读屏障两个特性,使得用户线程几乎可以全程与标记-复制算法并行,基本解决了YGC的STW问题。简单来说,ZGC在标记过程中会向64位指针的高位4bit中记录三色标记、重分配标记和可达性标记;当应用线程访问对象时,读屏障机制会依据指针状态和复制表信息去更新对象的地址和状态。
这样,即使GC线程正在后台转移、复制或清理对象,也可以保证前台线程能始终访问到正确的地址,这使得ZGC几乎可以做到无停顿回收。除此之外,ZGC还向用户承诺了可扩展性:由于ZGC的停顿时间基本只和初始扫描中GC Roots的数量相关,堆的大小和活跃对象的数量并不会导致停顿时间的增长。
其次,ShenandoahGC与ZGC同为新一代的零停顿收集器,总体来看,其内存布局非常类似于G1,而并发设计则与ZGC如出一辙,所以我们也将其作为一个可能的备选方案。
ShenandoahGC与ZGC的主要区别在于其使用的是Brook指针而非染色指针:即在对象头中额
外记录一个指向复制后正确地址的指针。但是由于额外信息记录在对象头中,Brook指针的读屏障无法在第一次访问后直接更新正确地址来自我恢复。另一方面,ShenandoahGC的区块布局和回收阶段则与G1非常相似,甚至部分代码都是直接复用的。其不同主要在于ShenandoahGC利用了一个被称为连接矩阵的二维数组来取代G1中开销巨大的记忆集,来解决跨区引用问题:例如区块N引用了区块M,则在数组的`[N][M]`坐标打上标记。
最后,作为现在最主流的收集器,同时也是CMS的取代者,G1理所应当的也被我们作为最成熟和稳妥的一个选择。G1本身的内存布局使得其对可控的停顿耗时和吞吐量的平衡上有较好的兼顾,在理论上使它更适合查询接口这种会短时间内突然生成大量临时对象的计算密集型应用。
综上所述,我们以原本在轻量化前的生产配置(16C32G+JDK1.8+CMS)作为基准,选取了以下几个组合作为测试方案:
其他各相关参数都为默认配置。
2.2 G1调优实践
在横向比较不同收集器的性能之前,我们首先需要按应用的需求对每个收集器做一些简单的适配和调整,以发挥这些收集器的全部性能。由于G1是为开箱即用而准备的默认收集器,使用起来相对简单,基本上只需要简单设置下堆大小和线程数等参数即可。然而在实际使用中,我们仍然遇到了一些小问题,需要对关键某些参数进行控制。
(1)现象1:某个高压力场景的计算集群的YGC频率相对较高,GC吞吐量不足,最终甚至会引发FGC。
由于轻量化配置的资源本身就比较紧张,很难通过增加`ConcGCThreads`线程数来提升吞吐量,于是我们尝试放宽了`MaxGCPauseMillis`来减少G1的回收压力。作为G1最核心的参数,当`MaxGCPauseMillis`过小时,G1会自动调整其他GC参数来尽可能满足该目标,进而导致YGC非常频繁并影响吞吐量。由于各个应用的情况不同,需要开发