解决多线程性能问题技巧分享

@TOC 最近工作中在使用多线程处理业务逻辑时遇到了问题,程序运行期初,与之前未使用多线程没有任何差别,但是当对应的线程处理队列开始拥堵时,处理速度开始愈来愈慢,为解决该性能问题,投入多日并作出如下总结

问题定位

业务方法耗时跟踪

判定原因:某方法耗时较长,拖垮整个业务流程,导致性能下降 验证过程:在整个业务流程的主方法中加入方法耗时日志,发现所有方法耗时整体偏慢,且耗时颇长的方法并不唯一,存在随机性(正常方法耗时在3ms左右,异常时方法耗时平均90ms,更有甚者耗时上百上千毫秒)

同步日志输出

判定原因:同步日志输出拖慢系统整体性能 验证过程:针对日志输出方式,分别对同步和异步日志进行两次压测,但是效果虽有改善但是整体影响不大,整体还是偏慢,该原因并不是导致系统问题的主要原因,压测数据如下

日志输出方式 速率(5min) 速率(10min) 速率(15min) 速率(20min) 速率(25min) 速率(30min)
同步 34972 9628 11485 15927 1758 ~
异步 45595 48460 28836 9930 1746

线程数量过多

判定原因:改造后,业务逻辑处理多了31个线程,怀疑线程数量过多,导致线程之间切换时间大于线程工作时间 验证方式:减少启动线程数,启动16个线程进行压测,结果速率恢复正常,但是通过对系统监控,31个线程对于系统整体200多原有线程数来说并不算多,线程数过多是原因之一但不是主要原因

线程空跑

判定原因:31个线程在分别处理31个队列,并不是每个队列都有值,但是31个线程需要常驻,并时刻判断队列是否有值,此时空值队列的消费线程无限空转判断,造成CPU秘籍,严重影响性能 验证方式:重新启动31个线程,在消费时队列为空,线程强制睡眠5S,结果效果显著,速率恢复正常,压测结果如下

线程休眠对照 日志输出方式 速率(5min) 速率(10min) 速率(15min) 速率(20min) 速率(25min) 速率(30min)
不睡眠 同步 34972 9628 11485 15927 1758 ~
不睡眠 异步 45595 48460 28836 9930 1746
睡眠 同步 45716 46013 46521 43581 47421 45658
睡眠 异步 43655 49189 45319 47910 47761
结论:空跑线程会严重影响系统性能,针对于系统常驻线程,在不做业务处理时,需要让对应线程先休息,释放响应系统资源
复制代码

性能分析工具

top(动态查看进程变化,监控linux的系统状况)

  • Cpu(s): us 用户空间占用CPU百分比 sy 内核空间占用CPU百分比 ni 用户进程空间内改变过优先级的进程占用CPU百分比 id 空闲CPU百分比 wa 等待输入输出的CPU时间百分比 hi 硬中断(Hardware IRQ)占用CPU的百分比 si 软中断(Software Interrupts)占用CPU的百分比 st (Steal time) 是当 hypervisor 服务另一个虚拟处理器的时候,虚拟 CPU 等待实际 CPU 的时间的百分比。
  • Mem: total 物理内存总量 used 使用的物理内存总量 free 空闲内存总量 buffers 用作内核缓存的内存量 Mem: total 物理内存总量 used 使用的物理内存总量 free 空闲内存总量 buffers 用作内核缓存的内存量 Swap total 交换区总量 used 使用的交换区总量 free 空闲交换区总量 cached 缓冲的交换区总量。 内存中的内容被换出到交换区,而后又被换入到内存,但使用过的交换区尚未被覆盖, 该数值即为这些内容已存在于内存中的交换区的大小。 相应的内存再次被换出时可不必再对交换区写入。

31线程-同步日志-线程不睡眠

31线程-同步日志-线程不睡眠

vmstat(监控虚拟内存、进程、IO读写、CPU活动)

  • Procs(进程) r: 运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1) b: 等待IO的进程数量。
  • IO(现在的Linux版本块的大小为1kb) bi: 每秒读取的块数 bo: 每秒写入的块数 注意:随机磁盘读写的时候,这2个值越大(如超出1024k),能看到CPU在IO等待的值也会越大。

31线程-同步日志-线程不睡眠

在这里插入图片描述

jstack(jstack是java虚拟机自带的一种堆栈跟踪工具)

  1. 使用top 定位到占用CPU高的进程PID top 通过ps aux | grep PID命令
  2. 获取线程信息,并找到占用CPU高的线程 ps -mp pid -o THREAD,tid,time | sort -rn
  3. 将需要的线程ID转换为16进制格式 printf "%x\n" tid
  4. 打印线程的堆栈信息 jstack pid |grep tid -A 30
  1. 第一步查进程内线程
    在这里插入图片描述
  2. 第二部线程id转换
    在这里插入图片描述
  3. 分析堆栈
    在这里插入图片描述
    在这里插入图片描述

总结

如何应对线上故障

  • 淡定
  • 第一时间保障业务正常运转,根据实际业务情况分析是临时解决bug或者版本回退
  • 对复杂故障难以定位时,保存系统日志必要情况做系统dump,方便后续问题分析
  • 线下分析故障并解决

个人经验

性能问题分析应该从四个维度:

  • cpu
  • 内存(缓存)
  • io
  • 网络
  • a.合理的线程管理,提升线程的执行效率,避免线程空转或发生死锁。
    b.内存资源的合理分配,防止内存泄露和内存溢出。      
    c.向连接池获取连接使用完成后,及时close。
    d.外部接口调用超时合理设置,防止接口调用超时主线程夯死。复制代码

猜你喜欢

转载自juejin.im/post/5d976499e51d45780931ef8c