一、问题回顾

问题现象:线上业务,某个进程被卡住了,所有任务都不响应,导致业务中断。

问题原因:程序中调用了system命令,执行了一次pidof命令,然而作者万万没想到这个pidof命令会卡住了,导致整个进程都阻塞了。

排查过程

第一步,确定进程状态,看看进程在干什么:先通过ps命令得到进程的pid,然后执行strace -p ${pid}挂进去。

执行完strace后发现进程一直卡在wait调用上,但是因为程序卡住了,没有更多的信息可以输出了,也不知道程序执行到哪了,所以并不能知道为什么会执行wait卡住。

于是分析执行wait的场景:wait只会出现在创建了子进程、父进程在等待子进程退出的情况下。会不会是某个子进程卡住了导致父进程阻塞呢?这个是很有可能的,但是反复检查代码,没有发现有创建子进程的地方,并且程序就是单进程设计的,没有主动调用wait的场景。所以这一点上被排除了。

那还会是什么原因导致程序卡在wait了?会不会是strace程序分析有问题导致的?当然这种可能性很小,基本可以排除。

然后又想,程序没有主动创建子进程,会不会是通过其他系统函数间接创建了子进程?很多系统函数都会创建子进程出来,典型的是system和popen函数,代码中是不是调用这两个函数?这很有可能!

于是,就在代码中搜索system和popen,还真找到了一处调用system的地方,但是system调用的都是系统命令:

sh -c kill -usr2 `pidof xxx`

看到命令后想到的第一个问题就是:这个会卡住吗?没听说过kill会阻塞,也没听说过pidof会阻塞啊。虽然我不太相信真的是它卡住了,但是我还是不自觉的打开了gdb。。。使用gdb -p ${pid}挂进去,直接输入bt打印出堆栈调用,映入眼帘的刚好就是这个代码所在的system调用。说明,真的就是这个system卡住了。

为了确定到底是kill卡住了还是pidof卡住了,使用ps过滤出来所有执行这个命令的进程:

可以看到,所有执行pid命令的pid是大于kill的,说明是pidof卡住了才导致kill卡住。

再仔细看,系统已经存在多个被卡住的pidof命令了,最早的可以追溯到一个月前。难以相信一个pidof命令会执行一个月。

本想着再查查为什么pidof命令会卡住,但线上业务着急恢复,执行strace和gdb没看出什么信息后就先恢复了业务。后来本地再也无法重现了,也没有找到pidof卡住的真正原因。

解决方案

  1. 去掉了system机制,kill命令直接通过signal函数来完成。
  2. 进程启动的时候,保存一份pid变量,不再执行pidof命令来获取pid。

二、总结和思考

  1. 程序中少使用system或popen调用外部命令。这点是我一直以来都提倡的,排斥使用system是因为调用system要创建子进程,要屏蔽信号,会有各种不确定的因素在里面,不如系统调用简单、安全。我认为一个好的系统,要尽量减少调用外部命令(但我们公司的传统就是喜欢各种脚本、命令调来调去,实在令人头疼)。
  2. 尽可能减少重复的复杂的逻辑。这里的体现就是pidof命令,是否有必要每次都通过外部pidof命令来获取pid?现在大部分的程序都是把pid保存到一个pidfile中,需要用的时候从文件中读取,这种方式来设计是不是会好一些?
最后修改:2020 年 03 月 08 日
如果觉得我的文章对你有用,请随意赞赏