作者介绍
fast于江:2004年北漂,主要在263从事个人和企业邮件的运维工作,2011年加入腾讯网继续从事web运维工作,2013年加入腾讯云负责售后技术支持团队工作,2015年加入泰康人寿主要负责运维规范流程的制定和推动工作。
系统出问题,通常我们怎么办?
做运维工作的同学,在日常的工作中总免不了跟各种各样的问题打交道,于是在身经百战之后,总结出了一套在日常工作中解决问题的流程:
- 发现问题,通过开发、编辑、客户、老板、监控系统等发现系统有问题了;
- 接下来就想要看问题是否可以重现,这点非常重要,因为有些问题很难重现,例如某些空指针异常或者内存泄露等问题,是需要累计运行到一定阶段之后才能发现;
- 接着不管是否重现与否,都要尽快查看是否有错误日志,通常在日志中会包含非常关键的信息提示;
- 接下来会进行判断,如果是运维能够自行解决的话,例如由于运行环境的版本有bug,或者某个软件的依赖库版本不对,或者某个配置不对,这种问题运维通常通过升级或者更新库,修改配置就可以解决;
- 如果发现是系统本身有Bug,则就需要开发介入进行解决了。
一般过程如下所示:
问题排查各阶段,看起来就是一个圈
在发现系统有异常现象的时候,我只简单的进行处理,不去解决。
我会先对问题进行简要的分析,尽量保留现场,以便分清楚哪些是干扰因素,哪些才是导致问题真正的原因,然后根据分析结果判断要采取什么行动,不过有时候老司机也有掉坑里的时候,这时候只能想办法重新回到正途中。
这时候往往现象比较棘手,分析就会陷入僵局,只好求助网上大神,然后各种分析工具齐上阵,貌似进展神速,但其实往往最终的效果是一般的,还是没有办法定位到根本原因。
这时候就需要重新梳理思路,去想办法定位到问题的关键,想想看有没有办法能让问题重现,经过一番尝试发现居然重现了问题,并且现象一致,那真是万幸啊,然后经过各种分析源码+一点点运气,心中就有底了,然后给代码打补丁,通过验证,终于一颗心可以落地了。
这个圈如下所示:
1、如何发现问题根因?
我将整个问题排查到解决的过程分成了4个阶段,将前4个步骤,定义为第1 阶段,下面我从两个案例,来讲讲在这个阶段做的一些事情,先讲讲案例:
案例1:汽车论坛发现系统异常现象
- 汽车论坛PHP升级+LVS改造后,通过auto.qq.com访问论坛页面出现“服务器暂时无法响应,请稍后再试”。
- 汽车开发收到论坛的URL扫描报警监控。
查询httpd的error.log日志,有如下的错误记录:
案例2: 房产后台发现系统异常现象
- 房产后台2台机器升级完PHP5.3.10后,测试均正常,上线运行一段时间编辑反应无法登入系统,重启PHP-FPM后,恢复正常。
- 经过1日又出现类似现象,查询nginx的error.log有明显报错。
1.1 问题排查第1阶段
1.1.1、简单处理不解决
再次重启的时候,查看了ulimit –a选项,发现默认情况只有1024打开文件数,调整到ulimit –SHn 65535之后再次重启相关服务。
运行一段时间后又出现类似无法打开页面的问题。通过下面命令查询到当前系统已经打开的文件句柄数,可用的句柄数,最大句柄数。
cat /proc/sys/fs/file-nr
系统当前状态打开文件也达到10多万了。虽然没有这到最大的可用数了,但有可能是会出现无法打开页面的问题。这种问题的重现概率非常高。
TCP:inuse:正在使用(正在侦听)的TCP套接字数量。其值≤ netstat –lnt | grep ^tcp | wc –l
TCP:orphan:无主(不属于任何进程)的TCP连接数(无用、待销毁的TCP socket数)
TCP:tw:等待关闭的TCP连接数。其值等于netstat –ant | grep TIME_WAIT | wc –l
TCP:alloc(allocated):已分配(已建立、已申请到sk_buff)的TCP套接字数量。其值等于netstat –ant | grep ^tcp | wc –l
TCP:mem:套接字缓冲区使用量(单位不详)
1.1.2、干扰因素问题简要分析
根据以前工作经验判断,打开文件过多问题,一般是打开文件没有close靠成的。代码问题可能是居多,但这些都只是猜测,还没有拿得出手的任何证据。近期同时操作了PHP升级和LVS改造,所以这3方面入手进行思考:
- 代码没有更新的背景上,还是怀疑PHP升级造成的可能性要大一些,但还觉是不是特别认可这个怀疑,但无法从PHP.net获得更多信息。
- 另一方面,LVS是非常成熟的技术,只涉及数据包的转发,只是为了验证RS是否存活,会周期性探测80端口是否有响应,会增加一定的访问量,但也只是一次简单GET访问,不会造成WEB无响应的问题。
同时监控LVS并未有异常的连接数的增加。 - 还有就是php加载了过多的公司自已独有的so文件,使整个事件事情的关键点过多,而且需要跨部门协调开发人员,增加了问题分析的复杂度。
1.1.3、解决问题方向判断错误
由于干扰因素过多,并且接手业务时间不长,所以增加了方向判断失误可能性。主要因素为出问题前做过PHP升级和LVS改造,还是在一定程序上增加了迷惑性。
一度只是简单通过strace分析,并没有认真的研究strace的具体调用细节。判断是连接数据库超时等原因造成的socket释放异常。
并且想通过“时间+used socket+参数优化”三个方面综合进行逆向查询入手查找。但由于涉及到系统各方面操作因素太多,而且对系统理解有限,也导致处理前期有了判断问题的方向性错误。
1.2 问题排查第2阶段
经过前面的判断,发现第一阶段的方向错误了,于是进入第二个阶段:
1.2.1、重回正途利用现有工具
短时间从PHP升级和LVS改造上面无法寻找到突破口。
决定利用LINUX现在提供的工具,如strace,gdb,netstat,lsof,/proc提供的各种系统分析工具进行排查问题。
计划准备使用的工具:
- strace – trace system calls and signals
- lsof – list open files
- gdb – The GNU Debugger
- netstat – Print network connections, routing tables, interface statistics, masquerade connections, and multicast member-ships
- Proc – 文件系统是一个伪文件系统,它只存在内存当中,文件可以用于访问有关内核的状态、计算机的属性、正在运行的进程的状态等信息
1.2.2、现象棘手分析迷茫
- 从架构入手:将RS服务器从LVS掉,恢复正常DNS指向, 问题依旧。
- 从PHP入手:将PHP恢复到原有版本,问题依旧,只是socketd速度增长没有新版本快(这点令我很奇怪)。
- 从系统入手:系统负载不高,连接数正常,IO压力正常,dmesg无报错。
- 从web应用入手:httpd日志正常,发现httpd进程打开大量的socket。
通过lsof命令将httpd进程的打开文件都列出来:
1.2.3、系统状态乱查一气
一度查到这条strace记录 时候,都开始怀疑数据库连接上面的问题。
1.2.4、求助网上大神
所谓大神,即伟大的Google.com,使用各种关键字进行搜索相关文章。
相对靠谱的文章,关于can’t identify protocol问题定位问题定位步骤:
- 用root帐户 遍历 /proc/进程ID/fd目录,如果该目录下文件数比较大(如果大于10,一般就属于socket泄漏),根据该进程ID,可以确认该进程ID所对应的名称。
- 重启程序恢复服务,以便后续查找问题。
- strace 该程序并记录strace信息。strace –p 进程ID >>/tmp/stracelog.log 2>&1 。
- 查看 /proc/进程ID/fd 下的文件数目是否有增加,如果发现有增加,记录上一个socket编号,停止strace 。
- 确认问题代码的位置。
打开/tmp/stracelog.log,从尾部向上查找close(socket编号)所在行,可以确认在该次close后再次创建的socket没有关闭,根据socket连接的server ip可以确认问题代码的位置。
Lsof FAQ
1.3 问题排查第3阶段
1.3.1、分析工具齐上场

再另一个终端上面查strace ,根据FD进行从下往上查询,查询FD19,20连接完数据库后,已经进行 正常close()操作了。
但查询到下面4个系统调用是最后使用19,20句柄,就没有下文了。而且没有正常close();
下面是一个标准的open,close操作记录,便于对比参考。
http://kasicass.blog.163.com/blog/static/3956192010101994124701/
根据这篇文章的介绍,can‘t identify protocol是lsof的源码,我也在dsock.c查到这个定义。
在 openbsd 下:
很奇怪哦,正确创建的 socket fd 居然显示 “can‘t identify protocol”。
根据TCP的状态迁移图。应用程序主动打开后,没有进行任何SYN及后续的ESTABLISED,CLOSE_WAIT。直接被应用程序关闭或超时,才会状态直接变成生CLOSED。
1.3.2、进展神速,效果一般
1.3.3、重理思路定位关键
排查的重点工作转向这2条系统调和是如何产生的,由于这个系统调和是比较独立的,所以并不知道是哪个文件,以何种方式进行调用,排查陷入困境。
分析系统调用:
socket() 为通信创造一个端点并返回一个文件描述符。 socket() 由三个参数:
- domain, 确定协议族。例如:
PF_INET 是IPv4 或者
PF_INET6 是 IPv6
PF_UNIX 是本地(用一个文件)
- type, 是下面中的一个:
SOCK_STREAM (可靠的面向连接的服务或者 Stream Sockets)
SOCK_DGRAM (数据包服务或者 Datagram Sockets)
SOCK_SEQPACKET (可靠的有序的分组服务),或者
SOCK_RAW (网络层的原始协议)。
- protocol 确定实际使用的运输层。最常见的是 IPPROTO_TCP, IPPROTO_SCTP, IPPROTO_UDP, IPPROTO_DCCP。这些协议是在
中定义的。如果 domain 和 type已经确定,“0” 可以用来选择一个默认的协议。
ioctl 主要参数SIOCGIFADDR 获取接口地址。
1.3.4、问题重现现象一致
经过上面的函数分析,得知是获取eth1的IP系统调用。
由于本人不是开发出身,所以了为避免出错,我需要通过另一种方法验证我的分析:
2、如何正确解决问题
在定位到问题之后,剩下的其实相对来说就容易的多了
2.1、分析源码运气稍好
- 先要查出PHP是如何调用,查询eth1网关,调和这个IP做什么。
传统方法:grep –R eth1./*结果很给力,多个.php文件都有调一个geteth1_ip_str**函数(实属运气,如果函数没写eth1类似的名称,还可能查不到哪. ^^) - 通过php源代码查到此函数,是公司t_common.so里面定义实现。手头正好有这个源码,查到get_eth1_ip_str这个函数,很简单就是返回一个eth1的IP地址。
2.2、心中有底略显激动
通过简单分析,以及咨询同事,觉是应该是申请了sock,没有进行close造成的。但真的是这样吗?我们还要验证一下。
心中有底略显激动
2.3、代码补丁验证通过
再次执行测试程序
/usr/local/php/bin/php test.php xxx.xxx.169.114
同时监控lsof 和/proc/pid/fd下面都没有出现socket不释放(can’t identify protocol)的问题。
更新扩展so后,线上测试均正常。没有再出现因为调用这个函数不释放socket句柄的问题。
至此整个问题都就都解决,世界又恢复了平静(大笑)
3、经验总结
对于本次问题处理的经验,归纳提炼成如下4句话:
- 收集信息,随时记录。
- 冷静判断,积极分析。
- 大胆假设,大胆尝试。
- 积极总结,以备后用。
当然,总结我这几年处理问题的思路及经验,可以提炼成以下这三点:
- 要有明确的数据流和业务流的概念,例如:通常对于Web数据流处理起来较简单,而对于Mail数据流则较复杂;
- 要能准确切入关键流节点,要敢于迅速的切入这些关键流,必要的时候进行快速模拟,以得到一手数据。
- 要掌握程序运行的状态,可以从两方面着手,第一是掌握输出日志内容;第二是进行strace跟踪程序运行状态等
END.
文章来源:高效运维(greatops)
本文链接:http://www.yunweipai.com/7885.html