压力测试 案例 jconsole 消息推送服务器压力下导致系统卡死的分析调试过程

开发消息推送服务器, 基于netty
主要引擎分4大组件: toolPooler, taskDispatcher, httpHandler, registerCenter

toolPooler, 任务池, 负责任务接收和保存
taskDispatcher, 任务派送器, 负责任务发送处理
httpHandler, http连接器, 负责处理netty上的所有websocket和ajax连接, 提供心跳支持
registerCenter 注册中心, 负责连接的注册信息(userId, devicerId), 以及心跳时间决定

3个人, 1个架构师, 1个4年,1个3年

前期架构师设计花了3天, 中期2个人各花10天, 后期集成调试3天

服务器开发完成, 在后期集成调试时, 做压力测试,
1.长连接测试: 不带消息处理, 只连接和心跳,
  a.以一台2W块钱的服务器虚拟出来的wind8, 能支持1600个websocket同时在线并心跳后, 就很难再连接上了, 分析其原因, 打开其他页面, netty响应速度极慢(5s-30s)
  b.以一台2W块钱的服务器虚拟出来的centos6, 能支持3000个websocket同时在线并心跳, 其实这个只是保持数量, 因为没有更多的客户端了...
2.消息推送测试: 同时在线100人, 每秒都给这100人发消息
  a.win8的1600的能力被嫌弃, 只在centos6测试, 跑了30分钟, 程序就卡死了, 打开自己写的后台监控页面同样极慢(5s-30s-抓狂),
  b.把程序回到开发机上, 下降压为每秒推30个消息, 打开jconsole监控, 如下图





现象: 线程直线上升, 到600个线程后, 线程数量一直上升没有回收(就像房价, 不像股市), 程序就出现访问简单页面卡死现象,
由此推出这个程序在centos6虚拟机的瓶颈是600个线程

分析: 消息推送, 在taskPooler里, 实现逻辑为:
收到消息 -> 存数据库 -> 发事件 -> 激活taskDispatcher的观察事件 ->
taskDispatcher处理: 判断推送的用户是否在线(通过registerCenter判断), 在线则调用httpHander把消息推出去, 否则无视

从图上分析, 线程数量直线上涨, 没有回落, 观察左右下角的panel, 有大量线程被阻塞, 主要入口在
receiveMsg (taskPooler.java:48)
即接收外面要推送消息, 最后追踪到自己的代码是
delete (taskPoolerImpl.java:135)

调试: 在delete前后加入时间日志





分析: 看出来删除记录花了8秒, 而每秒又会产生100个任务入库, 入库后推送又需要删除,
所以每个线程卡死在8秒, 造成堆压, 现有数据库的数据量为60W, mysql数据库
然后定位删除代码, 发现是通过
where deviceId=? and sent_to=?
去数据库, 发现是innoDB表引擎, 没有做任何索引
接着就是改索引, 改表引擎, 如下图







然后再更新到服务器去了, 跑99个一直稳定在28-22个线程并发数量, 如图





----------再作死, 加大压力----------

再加大压力, 跑198个, 线程又出现同样情况, 飚升到600后卡死, 但增加速度慢了很多
再走同样的排除法, 发现还是delete卡住,

以30人天开发的服务器, 能达到每秒99个消息推送的能力, 已经可以了, 到此为止,
赶紧跟其他子系统集成


后续(如果有的话):
1.深度优化数据库
2.现在已经在用连接池了, 后续不使用连接池, 直接长期保持delete专用连接
3.在接收消息时添加队列进去缓冲和拒绝








总结, JVM要做成房价那样就是失败了, 要做成股市那样就是成功了

猜你喜欢

转载自mocha-c-163-com.iteye.com/blog/2267337