@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线程-同步日志-线程不睡眠
vmstat(监控虚拟内存、进程、IO读写、CPU活动)
- Procs(进程) r: 运行队列中进程数量,这个值也可以判断是否需要增加CPU。(长期大于1) b: 等待IO的进程数量。
- IO(现在的Linux版本块的大小为1kb) bi: 每秒读取的块数 bo: 每秒写入的块数 注意:随机磁盘读写的时候,这2个值越大(如超出1024k),能看到CPU在IO等待的值也会越大。
31线程-同步日志-线程不睡眠
jstack(jstack是java虚拟机自带的一种堆栈跟踪工具)
- 使用top 定位到占用CPU高的进程PID top 通过ps aux | grep PID命令
- 获取线程信息,并找到占用CPU高的线程 ps -mp pid -o THREAD,tid,time | sort -rn
- 将需要的线程ID转换为16进制格式 printf "%x\n" tid
- 打印线程的堆栈信息 jstack pid |grep tid -A 30
- 第一步查进程内线程
- 第二部线程id转换
- 分析堆栈
总结
如何应对线上故障
- 淡定
- 第一时间保障业务正常运转,根据实际业务情况分析是临时解决bug或者版本回退
- 对复杂故障难以定位时,保存系统日志必要情况做系统dump,方便后续问题分析
- 线下分析故障并解决
个人经验
性能问题分析应该从四个维度:
- cpu
- 内存(缓存)
- io
- 网络
-
a.合理的线程管理,提升线程的执行效率,避免线程空转或发生死锁。 b.内存资源的合理分配,防止内存泄露和内存溢出。 c.向连接池获取连接使用完成后,及时close。 d.外部接口调用超时合理设置,防止接口调用超时主线程夯死。复制代码