首页 运维杂谈运维老司机带你出坑:复杂故障的排查及解决(案例实录)

运维老司机带你出坑:复杂故障的排查及解决(案例实录)

运维派隶属马哥教育旗下专业运维社区,是国内成立最早的IT运维技术社区,欢迎关注公众号:yunweipai
领取学习更多免费Linux云计算、Python、Docker、K8s教程关注公众号:马哥linux运维

作者介绍

fast于江:2004年北漂,主要在263从事个人和企业邮件的运维工作,2011年加入腾讯网继续从事web运维工作,2013年加入腾讯云负责售后技术支持团队工作,2015年加入泰康人寿主要负责运维规范流程的制定和推动工作。

系统出问题,通常我们怎么办?

运维工作的同学,在日常的工作中总免不了跟各种各样的问题打交道,于是在身经百战之后,总结出了一套在日常工作中解决问题的流程:

  1. 发现问题,通过开发、编辑、客户、老板、监控系统等发现系统有问题了;
  2. 接下来就想要看问题是否可以重现,这点非常重要,因为有些问题很难重现,例如某些空指针异常或者内存泄露等问题,是需要累计运行到一定阶段之后才能发现;
  3. 接着不管是否重现与否,都要尽快查看是否有错误日志,通常在日志中会包含非常关键的信息提示;
  4. 接下来会进行判断,如果是运维能够自行解决的话,例如由于运行环境的版本有bug,或者某个软件的依赖库版本不对,或者某个配置不对,这种问题运维通常通过升级或者更新库,修改配置就可以解决;
  5. 如果发现是系统本身有Bug,则就需要开发介入进行解决了。

一般过程如下所示:

运维

问题排查各阶段,看起来就是一个圈

在发现系统有异常现象的时候,我只简单的进行处理,不去解决。

我会先对问题进行简要的分析,尽量保留现场,以便分清楚哪些是干扰因素,哪些才是导致问题真正的原因,然后根据分析结果判断要采取什么行动,不过有时候老司机也有掉坑里的时候,这时候只能想办法重新回到正途中。

这时候往往现象比较棘手,分析就会陷入僵局,只好求助网上大神,然后各种分析工具齐上阵,貌似进展神速,但其实往往最终的效果是一般的,还是没有办法定位到根本原因。

这时候就需要重新梳理思路,去想办法定位到问题的关键,想想看有没有办法能让问题重现,经过一番尝试发现居然重现了问题,并且现象一致,那真是万幸啊,然后经过各种分析源码+一点点运气,心中就有底了,然后给代码打补丁,通过验证,终于一颗心可以落地了。

这个圈如下所示:

运维故障

1、如何发现问题根因?

我将整个问题排查到解决的过程分成了4个阶段,将前4个步骤,定义为第1 阶段,下面我从两个案例,来讲讲在这个阶段做的一些事情,先讲讲案例:

案例1:汽车论坛发现系统异常现象

  1. 汽车论坛PHP升级+LVS改造后,通过auto.qq.com访问论坛页面出现服务器暂时无法响应,请稍后再试”
  2. 汽车开发收到论坛的URL扫描报警监控。

查询httpd的error.log日志,有如下的错误记录:

故障

案例2: 房产后台发现系统异常现象

  1. 房产后台2台机器升级完PHP5.3.10后,测试均正常,上线运行一段时间编辑反应无法登入系统,重启PHP-FPM后,恢复正常。
  2. 经过1日又出现类似现象,查询nginx的error.log有明显报错。

故障排查

这些情况怎么破?且听我徐徐道来。

1.1 问题排查第1阶段

运维故障排查

1.1.1、简单处理不解决

针对汽车论坛,房产后台进行重启后服务均恢复正常,但运行一段时间后又出现类似的问题。
再次重启的时候,查看了ulimit –a选项,发现默认情况只有1024打开文件数,调整到ulimit –SHn 65535之后再次重启相关服务。

运行一段时间后又出现类似无法打开页面的问题。通过下面命令查询到当前系统已经打开的文件句柄数,可用的句柄数,最大句柄数。

cat /proc/sys/fs/file-nr

系统当前状态打开文件也达到10多万了。虽然没有这到最大的可用数了,但有可能是会出现无法打开页面的问题。这种问题的重现概率非常高。

运维故障排查

运维

sockets: used:已使用的所有协议套接字总量
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方面入手进行思考:

  1. 代码没有更新的背景上,还是怀疑PHP升级造成的可能性要大一些,但还觉是不是特别认可这个怀疑,但无法从PHP.net获得更多信息。
  2. 另一方面,LVS是非常成熟的技术,只涉及数据包的转发,只是为了验证RS是否存活,会周期性探测80端口是否有响应,会增加一定的访问量,但也只是一次简单GET访问,不会造成WEB无响应的问题。
    同时监控LVS并未有异常的连接数的增加。
  3. 还有就是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记录 时候,都开始怀疑数据库连接上面的问题。

刚开始对strace的输出内容一时也没有理清头绪,但随着查询文档的增多,也随步加深了对strace输出信息的理解。

1.2.4、求助网上大神

所谓大神,即伟大的Google.com,使用各种关键字进行搜索相关文章。

相对靠谱的文章,关于can’t identify protocol问题定位问题定位步骤:

  1. 用root帐户 遍历 /proc/进程ID/fd目录,如果该目录下文件数比较大(如果大于10,一般就属于socket泄漏),根据该进程ID,可以确认该进程ID所对应的名称。
  2. 重启程序恢复服务,以便后续查找问题。
  3. strace 该程序并记录strace信息。strace –p 进程ID >>/tmp/stracelog.log 2>&1
  4. 查看 /proc/程ID/fd 下的文件数目是否有增加,如果发现有增加,记录上一个socket编号,停止strace
  5. 确认问题代码的位置。
    打开/tmp/stracelog.log,从尾部向上查找close(socket编号)所在行,可以确认在该次close后再次创建的socket没有关闭,根据socket连接的server ip可以确认问题代码的位置。

Lsof FAQ

运维

1.3 问题排查第3阶段

接下来进入非常关键第3 阶段

运维故障

1.3.1、分析工具齐上场

以nginx+php-cgi场景开始重复测试工具,抓取有用信息:
运维

上面抓取的信息,在一定程度上影响了我的判断,因为19u,20u是因为连接完数据库后,状态才生成的can’t identify protocol,所以我的关注点转向了数据库连接上面。
再另一个终端上面查strace ,根据FD进行从下往上查询,查询FD19,20连接完数据库后,已经进行 正常close()操作了。

但查询到下面4个系统调用是最后使用19,20句柄,就没有下文了。而且没有正常close();

故障

下面没有19和20的任何输出了,当然也没有包括close()的操作。
下面是一个标准的open,close操作记录,便于对比参考。

运维

http://kasicass.blog.163.com/blog/static/3956192010101994124701/

根据这篇文章的介绍,can‘t identify protocol是lsof的源码,我也在dsock.c查到这个定义。

在 openbsd 下:

运维故障排查

在 debian 下:

20

很奇怪哦,正确创建的 socket fd 居然显示 “can‘t identify protocol”。

PS:
根据TCP的状态迁移图。应用程序主动打开后,没有进行任何SYN及后续的ESTABLISED,CLOSE_WAIT。直接被应用程序关闭或超时,才会状态直接变成生CLOSED

21

1.3.2、进展神速,效果一般

通过多次测试,监控系统调用,已经可以定位FD问题是由于socket和ioctl两个系统调产生的,并且处理正确处理造成socket没有释放。

22

一直在处纠结了很长时间,很长时间… … …

1.3.3、重理思路定位关键

排查的重点工作转向这2条系统调和是如何产生的,由于这个系统调和是比较独立的,所以并不知道是哪个文件,以何种方式进行调用,排查陷入困境。

分析系统调用:

23

函数解释:
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系统调用。

由于本人不是开发出身,所以了为避免出错,我需要通过另一种方法验证我的分析:

24

2、如何正确解决问题

在定位到问题之后,剩下的其实相对来说就容易的多了

25

2.1、分析源码运气稍好

  1. 先要查出PHP是如何调用,查询eth1网关,调和这个IP做什么。
    传统方法grep –R eth1./*结果很给力,多个.php文件都有调一个geteth1_ip_str**函数(实属运气,如果函数没写eth1类似的名称,还可能查不到哪. ^^)
  2. 通过php源代码查到此函数,是公司t_common.so里面定义实现。手头正好有这个源码,查到get_eth1_ip_str这个函数,很简单就是返回一个eth1的IP地址。

27

问题总结的时候想到脚本,可以列出来所有加载扩展库的支持函数列表:

28

输出内容:

29

2.2、心中有底略显激动

通过简单分析,以及咨询同事,觉是应该是申请了sock,没有进行close造成的。但真的是这样吗?我们还要验证一下。

运维

sleep(10000); sleep函数是要进程进行阻塞(sleep可以实现一种比较特殊的阻塞,这点跟IO阻塞不太一致),这样有时间可以提取这个进程运行状态。
心中有底略显激动

故障

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句话:

  1. 收集信息,随时记录。
  2. 冷静判断,积极分析。
  3. 大胆假设,大胆尝试。
  4. 积极总结,以备后用。

当然,总结我这几年处理问题的思路及经验,可以提炼成以下这三点:

  1. 要有明确的数据流和业务流的概念,例如:通常对于Web数据流处理起来较简单,而对于Mail数据流则较复杂;
  2. 要能准确切入关键流节点,要敢于迅速的切入这些关键流,必要的时候进行快速模拟,以得到一手数据。
  3. 要掌握程序运行的状态,可以从两方面着手,第一是掌握输出日志内容;第二是进行strace跟踪程序运行状态等

END.

文章来源:高效运维(greatops)

本文链接:https://www.yunweipai.com/7885.html

网友评论comments

发表回复

您的电子邮箱地址不会被公开。

暂无评论

Copyright © 2012-2022 YUNWEIPAI.COM - 运维派 京ICP备16064699号-6
扫二维码
扫二维码
返回顶部