51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 465|回复: 1
打印 上一主题 下一主题

[资料] 进程阻塞诊断详解

[复制链接]
  • TA的每日心情
    无聊
    2024-7-11 13:41
  • 签到天数: 33 天

    连续签到: 1 天

    [LV.5]测试团长

    跳转到指定楼层
    1#
    发表于 2023-7-17 11:42:19 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    前言
    进程在运行过程中会在执行,阻塞,就绪状态下来回切换。简单介绍下各个状态的切换原因吧。
      从执行->阻塞,一般是进程发生某种事件,例如IO,资源申请等。
      从阻塞->就绪,是进程等待的事件已完成,为接下来的执行做好了准备。
      从就绪->执行,处于就绪的进程得到了系统的调度,获得了时间片,实现了该进程在CPU上的执行。
      从执行->就绪,进程被系统调度,失去了CPU的执行权。一般是时间片使用完毕,或者被其他高优先级的进程抢占。
    一、问题现象
      如下图,程序直接卡死。。。
    进程处于阻塞状态,所有业务线程均被阻塞,失去了CPU执行权。阻塞时长,短的时候在几十秒,长的时候长达2-3分钟,然后又自行恢复,周而复始。
      这可是已经上线的实时业务,这不是要了开发的命了。看到这个情况,自己着急得像热锅上的蚂蚁,赶紧在家里开始着手排查。
    二、排查思路
      看到了问题的现象,可是问题的排查该从哪着手呢?
      首先怀疑了自己的业务代码里是否有长时间的业务处理流程。可是就算是有长时间的业务处理,所有线程的cpu不可能都为零呀;其次,难道业务代码里有锁,产生了竞争?不,我们的业务代码里没有互斥使用的对象,也就没有锁。即使有锁,那也至少有一个线程cpu不为0,而且不可能这样长时间为0。
      一头雾水啊,到底该从哪里着手分析呢?
      从现实回到理论,只有进程被阻塞,未被系统调度的时候,才有可能出现所有线程的CPU使用率为0。
      朝着这个大方向开始往下分析,那到底是什么事件,导致了所有线程被阻塞。
      有网络IO?不,我们不是一个有大量网络请求的服务端。
      有磁盘IO?不,线上业务流量规模不至于导致产生大量磁盘读写,而且通过iotop查看,在阻塞时,系统IO读写带宽还远远未达到磁盘IO的瓶颈。
      内存不够啦?但是阻塞的时候查看可用内存也还有1GB以上呀。
      机器坏啦?哈哈哈,在实在没有思路的时候,真的出现过这种想法。可是经验告诉我,不到万不得已,怀疑啥都别怀疑系统,甚至怀疑硬件,必定是自己的业务代码问题,或者是某些方法使用不当带来的问题。
      那到底是啥导致了所有业务线程的阻塞?这里可以确定的是,线程被阻塞,一定是有事件导致的阻塞。用户态可能性不大,那很有可能是系统调用阻塞在了内核态,那有没有该进程对应的内核态相关信息呢,从哪里看呢?
    三、问题排查
      如何查看进程运行态相关的内核态信息呢?
      Linux其实很完备啊,Linux内核提供了pro 文件系统,系统中当前运行的每一个进程在proc下都有一个对应的目录,且以进程的PID号为目录名,用户可以通过进程对应的PID目录得到对应进程的相关运行态信息。
      比如ID为93571的进程,有如下示例的一些信息:
    例如fd目录记录了当前进程打开的文件id,status文件记录了进程的内存使用和其它众多信息,task目录则包含了该进程及其所有子线程的信息。
      那哪个文件记录了进程当前的系统调用链呢?那就是我要找的stack文件,stack会记录当前进程在内核的调用栈,且是实时更新的!打开文件,会有某个进程当前在内核态的调用链信息,比如:
    但是问题来了,进程是一个多线程结构,如何查看每一个业务线程当前的内核态调用链呢?答案就是通过task目录获得想要查看的线程内核调用信息。
      先看看task里记录的线程id:
    再具体查看某个线程id的信息:
     发现了吗,上图的记录项与图三里的记录项一模一样,就是说我们也可以具体的了解到某个线程的运行态信息!比如某个业务线程的内核态调用链:

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

  • TA的每日心情
    无聊
    2024-7-11 13:41
  • 签到天数: 33 天

    连续签到: 1 天

    [LV.5]测试团长

    2#
     楼主| 发表于 2023-7-17 11:55:51 | 只看该作者
    另外,其实只要知道了某个线程的id号,直接通过cat /proc/$[thread_id]/stack也是可以的,而不需要通过进程ID下的task查看。例如:

    到这里,查看每一个业务线程对应的实时系统调用链的方法知道了,但是第二个问题来了:并行的业务线程很多,我怎么才能捕获到某个线程阻塞发生时的调用链信息?
      这确实是个问题,业务线程数量多,而且多种系统调用信息转瞬即逝,不可能一直在那不停的手动cat吧,这不跟大海捞针一样么。
      那该如何解决呢?是时候拿出另外一个秘籍了!
      日志是个好东西,通过watch和cat命令配合实现,一个记录时间,一个记录调用信息,不断的向日志文件追加信息。没错,就是实时记录所有业务线程的内核调用链信息,在阻塞发生后,再在日志中查看线程被阻塞对应时间段的系统调用链!
      操作步骤:
      (1)准备好要记录日志的所有线程id。例如图1中的68616、68618、68620、68622、68624、68626、68628、68630、68632,一共九个业务线程。
      (2)分别执行记录所有线程日志的命令行。注意每一个窗口执行一条命令。
    1. <font size="3">watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68616.log;cat /proc/68616/stack >> /root/68616.log"
    2.                                                         
    3. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68618.log;cat /proc/68618/stack >> /root/68618.log"
    4.                                                                  
    5. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68620.log;cat /proc/68620/stack >> /root/68620.log"
    6.                                                                  
    7. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68622.log;cat /proc/68622/stack >> /root/68622.log"
    8.                                                                  
    9. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68624.log;cat /proc/68624/stack >> /root/68624.log"
    10.                                                                  
    11. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68626.log;cat /proc/68626/stack >> /root/68626.log"
    12.                                                                  
    13. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68628.log;cat /proc/68628/stack >> /root/68628.log"
    14.                                                                  
    15. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68630.log;cat /proc/68630/stack >> /root/68630.log"
    16.                                                                  
    17. watch -n 1 "date '+%Y-%m-%d %H:%M:%S' >> /root/68632.log;cat /proc/68632/stack >> /root/68632.log"</font>
    复制代码
    (3)等待阻塞事件发生后,结束所有日志记录。
      万事俱备,只欠东风。到这里,一切安排妥当,坐等阻塞上钩!说实话,还担心你不出现呢!
      不出所料,当业务线程再次阻塞时,非常急切的打开了记录的日志信息!9个线程,有8个都在同一时间段打印了相同的调用链(卡住了),另外一个业务线程也是在这段时间出现了非常关键的,与众不同的调用信息: 
    what the fuck?
      alloc_pages_vma,do_try_to_free_pages,xfs_buf_iowait,是什么意思?这是在向系统请求内存分配啊,且最后内存分配出现了等待,而且都走到让buff/cache模块去释放内存了,这不就是线程阻塞的真相么!找到这样的关键信息真是欣喜万分!
      内存真的不足了么?再次阻塞时,赶紧查看了一下系统可供用户态程序使用的内存信息:
    可用内存真的不多了,只有300MB+,而且这300MB会通过buddy算法拆分为很多小块内存,并不是一个完整连续的空间。另外一个非常重要的信息是:阻塞时,系统无法满足应用程序向系统申请一个大于32KB的内存块。
      好了,终于真相大白了!我的业务线程在内存不足时向系统申请了一个比较大,可能超过32KB的内存,但是此时内存池(tcmalloc)无法提供该内存块大小,向系统申请内存时应该进行了加锁,导致了其它线程全部阻塞,然后申请内存的线程又长时间得不到返回,就出现了文章开头提到的长时间阻塞,问题终于找到了!
    四、解决方法
      阻塞业务的原因找到了,业务程序的内存使用并不大,但是为什么会出现这个问题,以及如何避免这个问题呢?毕竟现网业务可不能停啊!
      在后来的调查中,发现了造成程序阻塞的背后真凶。我们系统里另外一个业务程序“XXX”,会定时大量计算某些内容,存在内存开销冲高现象,同时导致系统供用户态程序使用的内存急剧下降。若某程序此时刚好碰上稍微大一点的内存申请,那就会因为内存不够而直接阻塞。
      解决方法:
      (1)优化了“XXX”的内存使用,某个时间点定时多任务并发执行,改为定时串行执行,降低内存开销。
      (2)优化了自己的业务程序,尽可能的减小内存申请的大小。
      (3)通过脚本释放buffer/cache占用内存,释放的内存会被还原到buddy管理的内存结构上,使得用户态程序可以申请到更多,更大的内存块。如上面图5所示,某个时间,buff/cache所占用的内存也非常高(buff/cache本质还是使用的物理内存,具体概念在这里就不再展开了),可以通过一个定时执行的脚本去释放这部分内存。
      在这也把脚本奉献给大家吧!不过需要注意,对buffer/cache的释放需要谨慎,有可能造成某些还未落盘的文件丢失,这部分还没有去考证过。
    1. <font size="3">#! /bin/bash

    2. # 内存使用百分比,可以传参,默认是20%

    3. log_dir=/root/log

    4. if [ ! -d ${log_dir} ]; then
    5.     sudo mkdir -p ${log_dir}
    6. fi

    7. log=/root/log/memory.log

    8. max_rate=$1

    9. if [ ! "$max_rate" ] ; then
    10.         max_rate=20
    11. fi

    12. #echo "max_rate: $max_rate"

    13. total=`free -g | awk 'NR==2' | awk '{print $2}'`
    14. used=`free -g | awk 'NR==2' | awk '{print $3}'`
    15. free=`free -g | awk 'NR==2' | awk '{print $4}'`
    16. buffer_cache=`free -g | awk 'NR==2' | awk '{print $6}'`

    17. rate=$(($buffer_cache*100/$total));

    18. echo "===========================" >> $log
    19. date >> $log
    20. echo "buffer_cache_usage_rate: $rate%" >> $log
    21. echo "Memory information:" >> $log
    22. echo "Total: ${total}GB" >> $log
    23. echo "Use: ${used}GB" >> $log
    24. echo "Free: ${free}GB" >> $log
    25. echo "buff_cache: ${buffer_cache}GB" >> $log

    26. if [ "$rate" -ge "$max_rate" ] ; then
    27.         echo 1 > /proc/sys/vm/drop_caches
    28.         echo "buffer and cache release over" >> $log
    29. fi

    30. echo -e "===========================\n" >> $log</font>
    复制代码

    五、结语
      为了诊断一个线上业务阻塞问题,就像中医一样经历了“望闻问切”四个过程,望(看到了进程阻塞),闻(IO?内存不足?硬件损坏?),问(此类问题的病理范围是什么?确定排查方向:看内核信息),切(把脉出是系统调用阻塞,且是内存不足引起),胸有成竹后,开了处方,来了个对症下药,最后彻底治愈。
      技术也是不断实践积累的,在此分享出来与大家一起共勉!

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    回复 支持 反对

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-28 06:38 , Processed in 0.063754 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表