记录一次压测性能调优

项目场景:

本项目涉及十几个微服务,其中核心服务四个。近期项目进入开发尾声,遂开始准备性能压测。由于是ToB项目,产品经理计算下来并发数定为880,基础业务数据中10w条为活跃数据,需维持websocket长连接生成任务包推送给指定设备,并执行回调业务,链路较长。测试环境申请了四台云服务器资源,均为4核16g。


压测前准备:

那当然是先review代码啦,还不是为了指标能好看点…

先记录几个简单的优化项

  1. 先将接口涉及的sql全部跑一遍,非必要查询字段去除,explain分析一下执行计划,缺失的索引加上,查看命中索引的类型,优化sql语句。 本项目创建配置类实现了mybatisPlus的扩展接口MetaObjectHandler,在插入和更新时会自动填充创建时间等字段
@ConditionalOnClass({MetaObjectHandler.class})
public class MybatisPlusMetaObjectHandler implements MetaObjectHandler {
    public MybatisPlusMetaObjectHandler() {
    }

    public void insertFill(MetaObject metaObject) {
        this.strictInsertFill(metaObject, "tenantId", () -> {
            return HttpHeaderUtil.getHeaderByName("tenantId");
        }, String.class);
        this.strictInsertFill(metaObject, "createTime", () -> {
            return new Date();
        }, Date.class);
        this.strictInsertFill(metaObject, "createUname", () -> {
            return HttpHeaderUtil.getHeaderByName("UserName");
        }, String.class);
    }
 }

同时查询的时候添加拦截器加上租户字段

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
            private String tenantId= null;

		.......

因此索引大部分为联合索引,需要注意联合索引的最左匹配原则,其余sql语句优化不再赘述。

  1. 代码检查是否有遍历循环数据库操作:提出循环外
  2. 是否循环内有重复定义对象、字符串拼接等
  3. list遍历循环写法比较:1万以内的数据,for循环的性能要高于foreach和stream;10万以内的数据明显可以看出stream效率最高,其次foreach,最后是for。
  4. hashMap三种遍历方式在Map容量10万、100万、1000万的情况下的耗时:
size10w100w1000w
entrySet18ms55ms511ms
keySet24ms116ms1483ms
Iterator entrySet12ms75ms684ms
  1. 合理使用缓存,由于业务涉及短暂操作(30分钟内),且数据库联表查询较大(千万级),缓存使用较多,减少与数据库的交互
  2. 一些不重要的同步回调业务丢kafka里异步执行去

以上基本在编码阶段就已达成,自认为小的优化点已经差不多了,让测试同事开始十几个接口和kafka性能压测


问题描述

结果想必都知道了,大跌眼镜。

初始压测
遂开始排查之路:

  1. 首当其冲想到的是代码是否仍有未优化的地方,由于该接口涉及四个微服务之间调用。当时未集成skyWalking,遂采取最土的方法,在每次微服务调用之前打印时间戳间隔毫秒数查看执行时间,结果发现,嗯,慢的就很平均,继续大略走读代码,未发现可优化的地方。
    不过后来压测接近尾声时运维集成了skyWalking,还有待研究。
    在这里插入图片描述

  2. ok,那咱来排查服务器的问题,让测试同事再压一次,实时监控服务器,cpu瞬间飙升到97%

jps

查看正在运行中的Java虚拟机进程,找到对应服务进程pid

jmap -heap pid

查看堆内存,好家伙,初始堆大小仅分配1024M,眼看着新生代分配到的可怜的273M内存刷刷刷一眨眼就没了

jstat -gc pid 1000

跟踪了一分钟,FGC维持在4次。

ps -ef|grep java

查看启动命令,好家伙,该服务的初始堆内存大小好歹还有1024M,其他服务初始堆内存大小就512M,遂开始调整初始堆内存

-Xms4096m -Xmx4096m

同时添加gc打印、内存溢出dump生成及导出

-XX:+PrintGCDateStamps 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/tmp
  1. 重新压测一遍,结果还是不尽如人意,平均响应时间仍高达8s,cpu消耗仍居高不下。
top -Hp pid

开始查看各个线程耗费cpu占比,发现较为平均,并无cpu消耗异常突出的线程,挑选一个线程打印堆栈信息,亦无发现异常。

printf "%x\n" tid
jstack pid| grep -A 10 十六进制tid

好吧,导出堆栈信息用jprofiler或者java visualVM分析看看
在这里插入图片描述
在这里插入图片描述
ok,解决了部分大字符串的问题,但仍然杯水车薪。

  1. 既然代码没有太大问题,那继续看服务器咯,四台云服务器挨个看,好家伙,突然发现四个核心微服务全部署在同一台服务器上,另有一台服务器处于空闲状态,难怪cpu消耗这么高,确实是有心无力,无可奈何啊。遂将四个核心微服务分开部署,堆内存初始大小拉齐,再次压测,效果立竿见影,一台cpu消耗80%,另两台cpu消耗30%
    在这里插入图片描述
    降了,终于降了!

  2. 但是80%的cpu消耗还是很高,遂继续排查该进程的堆栈情况,发现仍然存在大字符串的情况,跟进代码果然发现漏网之鱼,遍历循环在不少地方打印了任务包相关序列化后对象。
    那就调整日志打印级别呗,debug→info ,再次压测,守得云开见月明,cpu消耗降至60%多:
    在这里插入图片描述
    可以美滋滋睡个觉了。
    在这里插入图片描述

至此,本次性能调优结束。在下抛砖引玉,期待各位大佬提供更多性能调优的思路和法子。


解决方案:

1.sql调优→①语句调优②索引合理使用③分库分表
2.代码性能调优→①着重注意遍历逻辑②缓存合理使用③消息中间件合理使用
3.jvm参数调优→分析堆栈信息及gc
4.服务器资源调优→合理分配资源
5.配置调优→配置中心部署前区分环境差异

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值