性能测试产生的问题调优

背景

IM项目中,甲方根据实际业务量对我们做出了以下性能要求:

  1. mysql涉及的几个主要表,数据量要求6个月左右,其中chat_message表的数据量达到7千万,其他表的数据量在3百万左右。
  2. 场景要求能支持60个坐席签入后同时处理600个会话进线且互发消息保持15分钟后断开会话,再次进入600会话持续循环。总计持续3*24小时,且准确率≥99.9%

使用工具

  1. Jmeter:基于Java的压力测试工具,本次测试的载体

image.png

  1. Navicat:数据库连接工具,主要查看测试期间的数据变化及初始化数据

image.png

  1. Prometheus+Grafana:数据可视化平台,本次主要查看IM应用及相关组件测试过程中的变化

image.png

  1. MAT(Eclipse Memory Analyzer Tool):内存分析工具,针对本次内存溢出使用

image.png

测试过程

本次测试不是一上来就模拟甲方最终的性能要求,期间多次调整场景、并发数据及数据库数据量,测试了以下几个场景:

  1. 无数据量的情况下:20坐席签入后进线300会话保持15分钟互发消息;60坐席签入后进线600会话保持15分钟互发消息。
  2. 3个月数据量的情况下:20坐席签入后进线300会话保持15分钟互发消息;60坐席签入后进线600会话保持15分钟互发消息。
  3. 6个月数据量的情况下:20坐席签入后进线300会话保持15分钟互发消息;60坐席签入后进线600会话保持15分钟互发消息。

问题概述

测试脚本运行过程中产生的问题,主要围绕以下几个方面:

  1. 代码相关问题,例如并发进线导致的数据异常,MQ消费消息过慢。
  2. MySQL相关问题,例如锁表,连接数满。
  3. JVM相关问题,主要是内存溢出。

问题解决

并发进线

问题描述

单个坐席接待能力上限都是15个,采用的是坐席平均分配策略。20坐席进线300会话场景,坐席接待数出现异常情况;40坐席进线600会话场景,坐席接待数就大量超过接待能力数。6baab5e986b3a025e4b25fad23fe45d.png

解决思路

调整jmeter脚本的并发数,修改代码进线逻辑(redis锁)image.png

MQ消费过慢

问题描述

600会话保持15分钟互发消息后,断开socket操作,出现会话断开速度很慢的问题,这会导致每一轮都会出现进线达到最大接待数后多余的会话进入排队直至放弃,会导致高峰期会话无法进线问题。
image.png

解决思路

调整代码socket断开逻辑及ActiveMQ的消费能力配置;

image.png锁表

问题描述

由于业务量要求6个月,主要涉及的表数据量在3百万左右,最高表数据在7千万左右。刚开始无数据量的时候还好,查询速度相对稳定,模拟了三个月数据量后,查询明显变慢;甚至出现锁表。导致进线速度过慢出现接口超时进线失败的情况。
在性能脚本运行过程中,后台日志有输出以下截图错误: 锁等待超时
cbfd8f778e6e675c92a1054c63294d2.png

SELECT CONCAT('kill ',id,';') time, info FROM information_schema.processlist WHERE command != 'slepp' AND TIME > 1 ORDER BY TIME DESC;

查询结果如下:
3bafa260507d8431f06fe3542d6ebfe.png

SHOW full proesslist;

查询结果如下:
image.png
针对这些慢查询SQL来进行sql优化。

解决思路
  1. 查询条件优化,针对涉及到的查询代码里优化,尽量避免在 WHERE 子句中使用不必要的函数或表达式。
  2. 增加或优化索引,确保表上的查询条件字段都有适当的索引,以加快查询速度。
  3. 数据库分库分表,减轻单个表的访问压力(未实现待探讨)

数据库连接数

问题描述

性能脚本运行过程中遇到以下报错: 超过用户最大连接数。性能脚本运行过程中最大连接数峰值达到350+,但MySQL设置的值为150,则会导致后面的线程等待,时间长则会出现超时问题。
e78054c959dd952163bbd931eb30b16.png
使用以下命令查看mysql的配置

SHOW VARIABLES LIKE '%connections';

查询结果如下:
image.png

SHOW STATUS WHERE Variable_name LIKE = 'Thread_connected';

查询结果如下:
image.png

解决思路
  1. 设置全局的最大连接数
  2. 设置当前用户的最大连接数( 如果是root用户可以不用设置)
  3. 设置innodb的缓存池大小
  4. 修改IM配置中的数据库的最大连接数
SET GLOBAL max_connections = 2000;
SET GLOBAL max_user_connections = 2000;
SET GLOBAL innodb_buffer_pool_size = 8589934592;
spring.datasource.druid.max-active=500

堆外内存溢出

问题描述

性能脚本在运行过程中,发现堆外内存不断上升,最终因系统内存不足触发相关保护机制,Kill掉了占用内存最大的进程,导致我们服务不可用(IM分配最大堆内存16g)

排查命令
jmap -heap <pid> 

image.png

jcmd <pid> VM.native_memory summary scale=MB

该命令是查看该进程中详细的内存使用情况,summary是级别的意思 可设置为detail 查看具体的栈信息;其中Internal为直接内存(堆外内存),此次排查也是因该值持续升高导致服务Kill宕机。

jmap -histo <pid> | head -100 

jstat -gcutil <pid> 5000 

jcmd <pid> GC.heap_dump /tmp/dump.bin  

导出下来的文件可以输出到Eclipse Memory Analyzer Tool(MAT)去检查 dominator tree 从中找到哪个类不正常的占用大量内存,但是如果是堆外内存占用是无法查看到的。

过程
  1. 在脚本运行到30个小时后,系统堆外内存上升到了接近5个g,也就是Internal的值,在32个小时后到了服务器的上限将我们的服务Kill。
  2. Netty4.x版本后默认使用堆外内存,服务相关组件,如ActiveMQ,socketio,redis均引用了Netty,只能一步步的排查到底是哪个组件出现堆外内存上升。
  3. 我们分别对使用了Netty的组件一个一个的压测 也没看到有明显上升不降的情况,但本次压测最频繁的还是socketio组件(互发消息),我们将重点放在了socketio上。
解决思路
  1. Netty4.x版本默认使用的是堆外内存,可以在VM启动参数中将堆外设置成堆内,参数如下:
    :::tips
    -Dio.netty.noPreferDirect=true --使用堆内存
    -Dio.netty,noUnsafe=true --不使用unsafe
    -Dio.netty.allocator.numDirectArenas=0 --关闭堆外内存
    :::

  2. VM增加了该参数后,明显堆外内存不在持续上升,但是堆内老年代的使用率明显变高,如果满了必将触发Full GC,在通过一次手动触发GC后,老年代的使用率下降,但也不明显。通过命令观察发现某个类占有大量内存。

265e511aabbedae6d0ecf36c9d42501.png

  1. 通过将内存快照dump下来后我们分析,此次稳定性测试,60个坐席长期保持socket连接未断 ,其存在内存中的消息也因某种情况一直未释放导致内存不断升高。

edf11b89e12d3dd209e78213614125b.png

  1. netty4中的ByteBuf使用了引用计数(netty4实现了一个可选的ByteBuf池),每一个新分配的ByteBuf的引用计数值为1,每对这个ByteBuf对象增加一个引用,需要调用ByteBuf.retain()方法,而每减少一个引用,需要调用ByteBuf.release()方法。当这个ByteBuf对象的引用计数值为0时,表示此对象可回收。目前看情况是没有释放的,所以具体原因还待进一步排查。

  1. 基于ByteBuf的回收方式,在VM参数中又加了一个参数,使netty非池化分配内存,该方式老年代的上升速度明显比池化的速度要快,也很快触发了Full GC 但经过一段时间观察,老年代中的资源是可以被回收的,且在脚本停止后能大量被回收。
    :::tips
    -Dio.netty.allocator.type=unpooled --非池化
    :::

总结

本次性能测试脚本持续时间较长,因为测一次问题的时间成本较高,中间很多次需要运行60个小时以上才能显现问题。期间需不间断观察各个组件的使用情况,服务器的磁盘空间,内存空间等。
目前针对内存溢出的解决方案是在VM参数中增加netty相关的四个设置,通过将netty使用堆外改为使用堆内存,将内存释放交给gc来处理,只能算是一种暂行方案,因为该四个设置,或多或少影响服务的性能。后期还有待排查具体未释放原因。

参考链接

java堆外内存泄漏分析排查_java堆外内存泄漏排查-CSDN博客
Netty 源码分析及内存溢出思路
Netty篇:ByteBuf之堆外内存与回收策略源码分析(非池化)-CSDN博客

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值