一、问题回顾
问题现象:线上业务,某个进程被卡住了,所有任务都不响应,导致业务中断。
问题原因:程序中调用了 system 命令,执行了一次 pidof 命令,然而作者万万没想到这个 pidof 命令会卡住了,导致整个进程都阻塞了。
排查过程
第一步,确定进程状态,看看进程在干什么:先通过 ps 命令得到进程的 pid,然后执行 strace -p ${pid}
挂进去。
执行完 strace 后发现进程一直卡在 wait
调用上,但是因为程序卡住了,没有更多的信息可以输出了,也不知道程序执行到哪了,所以并不能知道为什么会执行 wait 卡住。
于是分析执行 wait 的场景:wait 只会出现在创建了子进程、父进程在等待子进程退出的情况下。会不会是某个子进程卡住了导致父进程阻塞呢?这个是很有可能的,但是反复检查代码,没有发现有创建子进程的地方,并且程序就是单进程设计的,没有主动调用 wait 的场景。所以这一点上被排除了。
那还会是什么原因导致程序卡在 wait 了?会不会是 strace 程序分析有问题导致的?当然这种可能性很小,基本可以排除。
然后又想,程序没有主动创建子进程,会不会是通过其他系统函数间接创建了子进程?很多系统函数都会创建子进程出来,典型的是 system 和 popen 函数,代码中是不是调用这两个函数?这很有可能!
于是,就在代码中搜索 system 和 popen,还真找到了一处调用 system 的地方,但是 system 调用的都是系统命令:
1 |
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 卡住的真正原因。
解决方案
- 去掉了 system 机制,kill 命令直接通过 signal 函数来完成。
- 进程启动的时候,保存一份 pid 变量,不再执行 pidof 命令来获取 pid 。
二、总结和思考
- 程序中少使用 system 或 popen 调用外部命令。这点是我一直以来都提倡的,排斥使用 system 是因为调用 system 要创建子进程,要屏蔽信号,会有各种不确定的因素在里面,不如系统调用简单、安全。我认为一个好的系统,要尽量减少调用外部命令 (但我们公司的传统就是喜欢各种脚本、命令调来调去,实在令人头疼) 。
- 尽可能减少重复的复杂的逻辑。这里的体现就是 pidof 命令,是否有必要每次都通过外部 pidof 命令来获取 pid?现在大部分的程序都是把 pid 保存到一个 pidfile 中,需要用的时候从文件中读取,这种方式来设计是不是会好一些?
评论