高并发场景下线程池配置不合理造成的超时过长

2016-08-11 12:09:00   最后更新: 2016-08-11 12:09:00   访问数量:664




项目在线上一直运行良好,CPU、内存都有较多剩余,CPU load 长期在 0.3 左右,接到通知周内公司业务促销,可能使 QPS 上升到日常值的三倍以上,认为完全可以承受,毫无压力

活动当天午高峰业务无任何异常,到晚高峰时,突然一个接口返回时间大幅上升,从原来的 100 到 200 毫秒左右上升到 2 到 3 秒,其他接口均没问题

 

出现这个问题,首先是采用临时加机器的方法来解决,有所好转

情况十分诡异,因为虽然接口返回时间达到了 2 到 3 秒,但返回数据是无误的

接口做了什么呢?逻辑十分简单,调用了一个远程接口,然后组装成一个新的对象返回

查看监控,我的接口调用的接口的 99.9% 的耗时在 450 毫秒左右,而我调用该远程接口的超时时间设置为 500 毫秒,如果超时,那么是无法返回任何数据的,而实际情况是数据返回无误,也就是说依赖的这个远程接口调用成功并在 500 毫秒以内返回了数据,而本地的创建并初始化对象占用了剩余的 1.5 到 2.5 秒的时间,这显然是不合理的,难道是因为返回对象过大,造成了频繁 GC ?查看 GC 日志,full gc 并没有增加,而 young gc 有小幅度上升,但是无论怎么 GC 显然不可能占用 1 到 2 秒的时间

排除了上述可能,还有可能是什么原因呢?最终定位了问题,我的接口在访问远程接口时是采用多线程并发的方式访问的,使用了 spring 封装的线程池 org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor

在正常情况下,依赖的后端接口返回时间大约是 100 毫秒,而线程池 corePoolSize 设置为 20,可以承受单机 200QPS 左右,而日常单机 QPS 仅在 20 到 30,因此服务能力绰绰有余

活动当天,午高峰时段,由于访问量暴增,依赖的后端接口超时显著上升,达到了 300 毫秒左右,单机可以承受 60QPS 左右,而当时 QPS 在 50 左右,服务能力刚好可以承受

晚高峰时,依赖的后端接口超时进一步上升,达到了 500 毫秒左右,由此单机仅能承受 40 QPS,而当时单机 QPS 已超过 80,由于线程繁忙,任务被缓存在线程池的任务队列中等待,因此等待时间达到了 1.5 秒到 2.5 秒,造成了大量超时的情况

 

在态度上,对于即将到来的活动所产生的服务压力过于乐观,忽略了系统可能的瓶颈

而同时,对业务抗压能力估计不足

今后要推动测试部门与我们合作进行必要的线上压力测试,这显然是必要的也是亟待实施的

此前一直比较关注 jvm 调优,也可以看到经过几轮的 jvm 调优,服务性能有了很大幅度的提高,即使在三倍压力下,GC 量和耗时都并没有显著增加,没有达到影响业务的程度

但是,对于线程池的使用和调优明显展现出经验不足和关注不够,预先没有预估过线程池的抗压能力与瓶颈,这么大的坑此前没有过任何考虑和评估简直无法想象

目前线程池 corePoolSize 设置仍保持 20,queueCapacity 由原来的 1000 下调为 10,maxPoolSize 上调到 200

可以看到,此前的设置是显然不合理的,此前的设计理念是,20 线程并发足以支持线上流量,意外的情况下缓存到 queue 中,所以 queue 设置为 1000 只是为了缓存意外情况的突发事件,而 maxPoolSize 于此并没有任何作用

更新后的配置 corePoolSize 用来解决日常的并发请求,queue 仅作为临时缓存,maxPoolSize 则用来在洪峰到来之际迅速扩充线程池响应高并发请求

在极端情况下,后端接口返回全部达到 1 秒的超时,200 线程并发可以承受单机 200QPS 的压力,是足够的

 






技术帖      技术分享      线程池      pool      java      threadpool      qps     


京ICP备15018585号