简单的jvm调参,提升接口性能

背景

近期项目遇到性能(读)瓶颈,当并发量达到一定程度(150w/min),接口性能显著下滑(tp999=120ms),上游调用方无法忍受,要求我们接口性能必须达到(tp999=50ms)

基本信息

目前部署的Docker服务大概200台,机器配置4核8G
为了提升接口性能我们做了以下事情:
- 读请求配置jvm+redis缓存策略
- 采用LRU淘汰策略(容量50W条数据),这里直接使用google开源项目GUAVA实现
- 异步更新,更新数据时主动将数据刷新至jvm缓存,刷新时采用覆盖更新,禁止使用删除+新增的方式
- 高并发接口对应的Server只提供单一服务,防止其他接口影响性能(当然最好的方式是微服务的方式)
- 高并发接口只负责更新jvm缓存的MQ请求,其他MQ或者Worker独立部署
- redis同机房调用(多写)

综上所述,如果不设置jvm数据的过期时间且数据不超过50W时,一段时间后,guava的命中率能达到99.9%

接口监控图:

在这里插入图片描述

从自身接口的性能监控可以发现tp999已经达到2ms,完全能满足下游的要求。但是观测下游的对我们接口性能监控,居然达到了150ms。

一次RPC调用执行流程:在这里插入图片描述

自身接口的性能监控只能监控到”具体的读方法“
下游对我们接口性能监控包含了从”发起请求“到”请求结束“整个过程(还有TCP重传等问题)。
所以当初我们花费的大量时间死磕在网络传输、序列化等问题上,结果都收效甚微,一度陷入迷茫,一次
与一位有高并发经验的同事闲聊,他的回答是:“只要接口参数合理,机器各项指标稳定,大概率问题还是出现在自身的方法上”,于是我们整装待发,重新开始定位问题。

我们完善了以下监控:

  • 服务器:cpu使用率、系统负载、连通性
  • 线程:线程数
  • JVM young GC、oldGC

上面提到数据99.9%访问的是jvm缓存,几乎没有IO操作,线程池属于CPU密集型,性能瓶颈可能出现在cpu上,但目前看cpu使用率的情况,问题不大
在这里插入图片描述

我们机器的总核数是4,负载数量在2.75,理论负载在6以内都是正常的。
在这里插入图片描述

这里的线程数比较粗糙,目前RPC中Server端线程池采用的fix(默认cache线程池)方式,不会产生上下文切换,且线程数相对比较稳定,故也不会有瓶颈问题
在这里插入图片描述
JVM监控显示youngGC平均耗时约为106ms,fullGC几乎不会触发:
在这里插入图片描述
jstat:
在这里插入图片描述

JVM参数配置:
在这里插入图片描述

讲调优之前,我们先聊聊jvmGC常用机制:

年轻代(复制清除算法)GC:

  • Serial Copying(串行GC)
    1、Eden区域内存被占满时,将Eden和from s(S0)中经过GC未被回收的对象迁移到To S(S1),S1内存占满,数据直接转移到老年代
    2、from s(S0) 与To S(S1)进行角色互换
    3、多次youngGC后依然存活的对象(默认15次),迁移到老年代

  • Paraller Scavenge(并行GC)
    1、与串行GC原理相同,只是GC时采用多线程的方式进行GC,在多CPU的情况下,GC消耗的时间比串行短

  • ParNew(并行GC)
    1、与Paraller Scavenge收集器一个重要区别是它具有自适应调节策略,设置- XX:+UseAdaptiveSizePolicy参数,新生代大小,Eden和Survivor区的比例由虚拟机对当前JVM对内存监控的情况动态调节,目的是满足最大吞吐量
    2、与Paraller Scavenge收集器第二个区别是只有ParNew能与老年代的CMS收集器使用

在这里插入图片描述
老年代GC:

  • CMS GC(标记清除):
    1、初始标记阶段只标记GC Roots能达到(根搜索算法)的对象,此时会“stop the world”
    2、并发标记阶段会根据GC Roots递归找到其他对象,对它们进行标记
    3、重新标记阶段会标记一些变更的对象,因用户程序继续运作而导致,此时会“stop the world”
    4、并发清除标记对象

总结:CMS是有一种优秀的GC算法,CMS的出现提供了系统的吞吐能力,经过短暂的“stop the world”,即可完成一次GC,但CMS由于使用的标记清除策略,务必会带来一些碎片化的内存,且在GC过程中,程序自然而然会产生一些新的垃圾对象无法被GC掉,老年代GC前也需预留一些内存给到用户线程正常运作。

  • Serial Old收集器(标记清理压缩):
    1、标记阶段:扫描老年代存活的对象,将这些对象进行标记
    2、清理阶段:清理老年代中全部未被标记的对象
    3、压缩阶段:将所有内存存活的对象进行整理变成一个连续的内存块

  • Parallel Old收集器(标记清理压缩):
    1、标记阶段:按并行线程数划分多个趋于,并行扫描老年代存活的对象,将这些对象进行标记
    2、清理阶段:多线程并行清理老年代中全部未被标记的对象
    3、压缩阶段:将所有内存存活的对象进行整理变成一个连续的内存块
    在这里插入图片描述

问:如果判断一个对象从Roots触发不可达?
参考:https://blog.csdn.net/sinat_33087001/article/details/77987463

从JVM参数可看到,系统新生代GC使用的是Paraller Scavenge算法,Paraller Scavenge算法在GC时,会停止所有用户正在运行的线程,影响系统的吞吐能力,我们配置了以下参数进行调优:

-XX:+UseParallelGC -XX:MaxGCPauseMillis=50 -XX:ParallelGCThreads=4

由于我们内存是4核,所以XX:ParallelGCThreads数量配置成4

压测报告在这里插入图片描述
JVM监控

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值