You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

298 lines
23 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 18 | 偶发性问题如何排查?
你好,我是胜辉。
在开始今天的课之前我先问你一个问题你在工作中没有遇到过那种“神出鬼没”的故障就是说大部分时候情况都是正常的但偶尔会来一下故障的那种。我猜有99%的可能你遇到过。
这种问题挺麻烦,经常是我们准备排查的时候,现场就没有了。那么,没有第一手的详细数据,我们还能查到问题的根因吗?
在我以往的实践当中,我发现不少人在对这个问题的认识上,会有两个常见的误区。
**误区1没有现场也没有抓包但只要我们有历史记录就能通过它查到根因。**
这里说的历史记录,是指应用日志、性能指标等。的确,很多问题通过查看历史记录,就可以解决。但还有一些问题场景,单靠历史记录是无法查到根因的。
比如这样一个场景用户访问页面时偶尔遇到HTTP 503错误。而LB日志里记录的其实也是一次HTTP 503以及“后端服务不可用”这类信息。但是如果我们继续追问为什么当时后端服务不可用呢当时网络上有什么问题导致这种不可用呢
日志并不能告诉我们这些问题的答案。这是它本身的局限性导致的,即它的视角是应用层,并不是网络层,所以天生就无法了解底层网络到底发生了什么。这种时候,你是不是有一种“隔靴搔痒”的感觉?
事实上,这类问题的排查,需要在**重现**reproduce问题时做全面的排查工作比如抓包拿到最真实全面的信息后才能真正彻底完成。
**误区2为了任何时候都能有现场数据就一直抓包一旦有问题就直接看抓包文件。**
这样做,理论上确实保留了现场,但是现实中却并不可行。主要有两大原因:
* 第一,**抓包数据量太大**。以一个平均流量在1Gbps的服务为例1天就有10TB的网络报文数据。现在一个企业有几十个、上百个服务的情况也很常见10TB再乘以10或者100这种系数数据量就又上了几个数量级这对存储、读取来说都是极大的压力。
* 第二,**常态的抓包对系统性能的影响不能低估**。因为抓包本身也占用内存和CPU资源这会对已经有问题的系统产生“叠加伤害”有可能会出现“不抓包的时候只有老问题而抓包之后除了老问题还出现了新问题”。所以常态化的抓包工作既不可行也没必要。
因此,在这个大背景下,我们如何抓包、抓多久,就很有讲究了。
那么今天,我们就通过一个实际的案例,一起来探讨下在面临偶发性问题时,我们都可以采取哪些有效的排查手段,来解决这种“想查的时候问题不来,问题来的时候没在查”的困境。
在这个过程中,你可以观察下整个重现和排查的方法论,并重点关注我**对大量报文进行某个指标分析**的技巧。这样,当你在日后遇到偶发性问题时,就可以参考今天所讲的内容,把问题真正地解决。
## 我们可以采用怎样的重现+排查策略?
我们先来了解下需要采用什么样的重现和排查策略。事实上,网络排查是一个更偏重实践的领域,所以只要能解决问题,我觉得都是合适的办法。针对偶发性问题的重现和排查,我个人会选择如下的策略。
#### 1.初步估计问题出现的时间跨度,对于问题何时重现有个预期
通过对用户反馈、应用日志以及监控仪表盘Dashboard的异常事件做初步统计了解到问题是多久出现一次这样就决定了我们在观察和抓包上需要投入的时间成本是多少。
举个例子,一个偶发性问题是几分钟出现一次,另外一个偶发性问题是几天才出现一次。如果你对后者只观察几分钟,显然是无效的。反过来,对前一个问题用好几天的时间去观察和抓包,则远超实际需求,虽然有效,却很低效。
> 另外在这里我还要做个提醒。针对频率一会儿密集一会儿稀疏的问题最好选取稀疏频率也就是做保守估计。比如某个问题在第一个小时出现了10次平均6分钟一次第二个小时却一次也没有到第三个小时才又出现一次那么我们认为其时间跨度是2小时即第二和第三小时而不是6分钟。
#### 2.在问题机器上发起抓包,根据预估的时间跨度,指定抓包方式。
基于前一点的分析,我们知道了问题重现的大致时间跨度,由此也确定了做监控和抓包的时间跨度。无论是我们在监控上花的时间长短,还是抓包文件占用磁盘的大小,这些都是需要控制的成本。
这里面还有个矛盾tcpdump抓包抓久了文件太大抓短了又可能错过问题现场。那么**如何做到既可以抓取到期待的报文,同时抓包文件也不至于特别大呢?**给你分享两个技巧。
**技巧一利用tcpdump里跟循环抓包相关的几个参数。**
* \-W 个数:生成的循环文件数量,生成到最大数量后,新的报文数据会覆盖写入第一个文件,以此类推。
* \-C 尺寸每个文件的大小单位是MB。
* \-G 间隔时间:每次新生成文件的间隔时间,单位是分钟。
下面这个例子就是每100MB或者60分钟满足任一条件即可就生成一个文件一共10个文件
```plain
tcpdump -i eth0 -w file.pcap -W 10 -C 100 -G 60
```
**技巧二在循环抓包的基础上再利用tcpdump的-s参数可以大幅减小抓包文件的大小。**
比如:
* tcpdump -s 54抓取到从二层到四层的报文头部不带TCP Options了。
* tcpdump -s 74可以抓取到所有TCP Options对排查TCP行为足够用。
如果要对应用层的头部比如HTTP头部也进行抓取那可以设置更大的-s参数值。整体来说这样的抓包文件要比抓满的情况1514字节小很多。
> 补充1514字节是网络层MTU的1500字节加上帧头的14字节。所以一般不指定-s参数的抓包文件其满载的帧大小是1514字节。
#### 3.定时观察,在问题重现时停止抓包。
这里又分成两种不同的做法:
* **直接人工观察应用日志或者仪表盘**,比如每隔几分钟就刷新一次,直到问题重现。在问题出现频率相对高的场景下,这样做最为简单。这种做法类似软件设计里的轮询机制。
* **设定自动告警机制**当通过邮箱或者手机短信等途径收到相应告警时就知道问题重现了。这样做的好处是抓取的问题时间点更准确。现在有告警通知功能的监控系统很多比如Prometheus + Grafana。这种做法类似软件设计里的事件通知机制。
#### 4.导出抓包文件,结合应用日志展开分析。
这就是我们前面提到的“问题现场”详情了,在抓包文件里,所有的网络行为都会被记录下来。我们把报文情况跟应用层的报错情况做对比,就能极大地推动根因排查工作。
我把这个方法论整理成了一个简单的流程图,供你参考:
![](https://static001.geekbang.org/resource/image/5c/71/5c0ea3757e87b3d2df8d04c6eb9d3171.jpg?wh=2000x528)
下面,我们就进入实际案例。
## 实战案例:网站偶尔会变慢?
曾经有个客户向我们报告了这样一个情况他们的网站偶尔会变慢比如正常1秒内就能完成的请求可能会变成5秒钟或者更长。
但是这个情况不是每次必现客户还做了功课他们发现问题出现的频率大约是几百次里面有一次。客户怀疑是不是网络有丢包或者什么问题导致了有时候HTTP请求或者响应没有被及时传输呢所以就委托我们查清楚原因。
### 预估:如何预估问题的出现频率和周期?
按照前面的方法论,我们**先做预估**,也就是估计一下问题重现的时间跨度。
客户说“每几百次出现一次”我们可以保守估计为1000次中出现一次问题。那么通过这个数据能得出时间跨度吗还不行因为我们还不知道**请求的频率**是多少。
我们再次跟客户确认后了解到他们业务还处在起步阶段并不十分繁忙访问频率大概在10次/分钟。现在我们做个简单的算术题1000/10=100分钟这就是预估的时间跨度。
### 抓包:抓多长时间合适?
有了预估的时间跨度我们就可以做相应时长的抓包了。我们让客户在主机上用tcpdump抓取了100分钟左右的数据包并发回给我们做分析。
### 监控:如何重现问题?
在这个时间段内,这个站点依然在对外服务。除此以外,客户那边的几个工程师也用浏览器不断刷新页面作为测试,发现也有遇到一次页面卡顿的情况。所以我们已经清楚,问题现场也就是跟问题相关的数据包,已经在抓包文件里了。
在这个案例里,因为客户那边的监控手段比较缺乏,没有全面的客户端日志,所以动用了“人肉”重现的方法。
其实,这个看起来很朴实的方法,在不少场景下也是挺有用的。我前面也提过,**网络排查是一项偏向实践性的工作,只要有效的方法,我们都可以参考采用**。当然如果你有Grafana等比较先进的监控系统自然可以采用告警通知等更好的办法。
![](https://static001.geekbang.org/resource/image/2a/dd/2aa3b64b995d21037cb9323ccc57d9dd.jpg?wh=2000x1125)
## 抓包分析1如何在网络视角上精确定义问题
接下来,就进入了分析阶段。不过在具体展开前,我们先思考两个简单的问题:
* 什么是慢/卡顿?
* 多慢才是慢?
客户的应用是HTTP协议的业务是在线二手商品交易他们的客户是通过浏览器和App进来交易的。所以针对第一个问题“慢”对于浏览器来说就可能是页面一直显示转圈圈的图要很久才能加载好页面。对于App就是界面里有很多内容空白过一会儿才能正常使用。
第二个问题没有标准答案但一般来说一个页面响应超过3~4秒问题就很大了1秒以内完成是最好的。页面打开越快客户体验越好成交量就越高。有研究发现电商网站页面的加载时间只要快0.1秒,成交量就有明显的提升。
所以基于以上的分析我们确定了查找条件就是“响应时间超过3秒的HTTP事务”相关的报文。但是当我们打开抓包文件随机选取一个HTTP事务的报文我们就能很方便地找到“响应时间超过3秒的HTTP事务”吗
![图片](https://static001.geekbang.org/resource/image/b0/d2/b085eaf6dac2598e3cbc3af7858eb6d2.png?wh=1155x206)
上图是某个随机选取的HTTP事务的TCP流。当然这里也是 **Wireshark的一个小的知识点**左侧的两个水平方向的箭头一个向右一个向左分别表示这两个数据包是HTTP请求和HTTP响应。然后我们可以根据第二列的时间差Time算出来HTTP耗时。
这次事务的耗时主要发生在642号和641号报文之间大约是115毫秒所以不符合3秒的条件。
## 抓包分析2如何用Wireshark实现对大量HTTP事务的性能分析
前面的做法是先找到HTTP事务的报文然后用Follow TCP Stream的方式找到这个TCP流最后分析其HTTP请求和响应之间的耗时。如果抓包文件里只有几个这样的事务这样做倒也无妨。但是如果抓包文件里有几百几千个HTTP事务难道也这么一个一个去对比分析怕是几天都看不完。
事实上我们还有更好的办法也就是利用Wireshark的高级用法来帮助我们实现对大量HTTP事务的某个性能指标进行解析和统计也就是可以借助它的两个特性**过滤器filter和自定义列**。
### 过滤器
我们先来看看如何使用Wireshark的过滤器来实现方便的数据分析。
在这个案例里我们关注的是HTTP事务的耗时。其实在Wireshark窗口里我们可以选中一个HTTP响应报文然后找到它的耗时。比如前面提到的115毫秒的那个事务它的HTTP响应报文的详情是这样的
![图片](https://static001.geekbang.org/resource/image/34/1a/343587d46c4850yy81dfc65164ec4d1a.png?wh=618x399)
可以看到,`[Time since request: xxx seconds]` 就表示了HTTP耗时。它本身不是报文属性而是Wireshark对报文的分析属性。所有Wireshark提供的分析属性都会用**方括号**标识出来这跟ps等命令里把内核线程也用方括号标识出来的做法类似
这个HTTP耗时指标对应的Wireshark过滤器是 `http.time`。我们可以直接在Wireshark过滤器输入框里输入 `http.time`,看看会出现什么:
![图片](https://static001.geekbang.org/resource/image/eb/ea/ebbc2c46d17f36881400a7073bd3b8ea.png?wh=653x307)
可见所有的HTTP响应报文都被过滤显示出来了。这是因为Wireshark把http.time这个分析属性体现在HTTP响应报文上所以这里搜出来的就都是响应报文了。而如果用过滤器http就会同时显示HTTP请求和HTTP响应的报文。现在没有其他TCP包的干扰看起来清爽多了。
不过等等,我们想要看的**耗时信息在哪里呢?**没有地方展示?
### 自定义列
到这里,就需要第二个特性上场了,也就是**自定义列**。跟之前课程里介绍的[添加自定义列](https://time.geekbang.org/column/article/481782)的方法一样我们在Wireshark窗口下方的http详情部分选中前面介绍过的Time since request右单击选择Apply as column然后Wireshark主窗口里就多了一列 `http.time`
不过新增加的列默认出现在最右侧你可以根据需要把它拖放到合适的位置比如我把它拖到第三列插入在info列之前查看起来十分方便。
![图片](https://static001.geekbang.org/resource/image/30/36/301281a684daaa0be292522485dee436.png?wh=729x309)
这样,我们结合 `http.time` 过滤器和 `http.time` 自定义列就可以很直观地看清楚每个HTTP事务的耗时了。
好了,这时我们点击 `http.time` 列就可以排序了,一下子就可以找到耗时最高的几个事务,如下:
![图片](https://static001.geekbang.org/resource/image/7b/e3/7b255eef494b951007941f65a72f0fe3.png?wh=738x216)
我们可以很清楚地看到有两个事务消耗了长达5到6秒的时间其他事务就相对很低了都在1秒以内。既然已经定位到问题事务了接下来按照常规套路右单击这个包选择Follow -> TCP Stream我们就可以跟踪到这个高耗时事务的前后TCP行为进而可以定位是否是网络问题。
![图片](https://static001.geekbang.org/resource/image/9e/b6/9e8882627282ca8baa15c944a1b065b6.png?wh=736x459)
这个完整的TCP流是这样的
![图片](https://static001.geekbang.org/resource/image/92/de/9244f5f49154e8494245d18dcde988de.png?wh=903x611)
显然从第二列Time来看6秒多的时间耗费在576号报文和647号报文之间具体来说
* 576号报文服务端监听在8082端口回复了TCP确认确认收到客户端发送过来的POST请求。
* 中间**停顿了6.5秒左右**。
* 647号报文服务端回复的HTTP响应头部可以在详情部分里看到HTTP返回码和多个header。
* 648号报文客户端确认收到HTTP响应头部。
* 649号报文服务端继续发送HTTP响应的body部分。在这里因为HTTP响应报文已经完整648+649号报文Wireshark可以在这649报文上显示 `HTTP/1.1 200 OK` 这样的信息了。
* 650号报文客户端确认收到HTTP响应body部分同时携带FIN标志位结束这个连接。
非常明显,问题是**服务端收到HTTP请求后花了大约6.5秒的时间才回复了HTTP响应**。这充分说明两点:
* 网络上没有任何丢包、重传等问题;
* 服务端响应耗时高达6秒以上。
既然排查了网络的嫌疑,客户就转而去检查应用层面的问题了。
## **是否还有其他可能?**
当然你可能会问是否有可能服务端及时发送HTTP响应了只是一直丢包导致6.5秒后客户端才收到呢?就是下图这种情况:
![](https://static001.geekbang.org/resource/image/ee/4c/ee7ccdfd9f4834bb12cc5e948854744c.jpg?wh=1794x1013)
这个可能性当然是可能存在的,如果做得透彻一点,我们应该同时在客户端和服务端抓包,进行对比即可确认或排查这个可能性。具体的例子可以参考我们在[第5讲](https://time.geekbang.org/column/article/481042)介绍的防火墙的案例,里面就是用两侧抓包来查出问题的。
但是在当前的案例中,假设真有网络问题引起的重传,那么这个**重传行为不会只集中在HTTP响应**上。
也就是说一个有丢包表现的网络不会“聪明”到只盯着HTTP响应去丢包而是其他阶段也会有丢包。这里说的其他阶段就是TCP握手、HTTP请求、TCP挥手等等丢包同样会引起这些阶段出现重传等现象。但是我们在这个抓包文件中并没有看到这类现象因此反过来就说明这个推测不太可能成立。
另外,细心的你可能已经注意到了,图中假想的丢包和重传,遵循了[指数退避原则](https://en.wikipedia.org/wiki/Exponential_backoff)。这个知识点我们在[第3讲](https://time.geekbang.org/column/article/479163)有介绍过,你如果觉得生疏了,可以去复习一下。
## **如何用命令行工具做同样的分析?**
前面我介绍了如何在Wireshark里结合过滤器和自定义列来定位“高耗时HTTP事务”。不过因为图形界面的关系这个过程有好几步操作稍显麻烦有没有更快一些的办法呢
其实是有的这次我们不用Wireshark图形界面程序而是利用它自带的另外一个命令行工具 **tshark**,我们在上节课里也介绍过这个工具**。**
就拿前面这个案例来说我们可以直接用tshark在命令行中实现对高耗时HTTP事务的获取。直接运行以下命令
```plain
tshark -r 文件名 -T fields -e http.time | grep -v ^$
```
示例输出如下:
![图片](https://static001.geekbang.org/resource/image/d6/52/d66f4b615a651dc0b389e0e0eyyde252.png?wh=479x321)
注意,我们在命令的后面,必须加上**管道符“ | ”和“grep -v ^$”**要不然就会看到下面这种有大段空白的输出因为凡是非HTTP的数据包都没有输出会成为一个空行
![图片](https://static001.geekbang.org/resource/image/13/46/13075fe935cfb325511371f6b2c07946.png?wh=480x290)
当然如果要针对某个高耗时的事务进行数据面的分析那光看http.time这一个指标是不够的你需要知道这个事务对应的包号、TCP流号等信息并追踪这个流里面的数据包。比如你可以运行以下命令把http.time耗时最高的事务对应的TCP流数据包都在命令行里展示出来
```plain
tshark -r 文件名 -T fields -e frame.number -e http.time -e tcp.stream | sort -k2 -r | head -1 | awk '{print $3}' | xargs -n 1 -I {} tshark -r captured.pcap -Y "tcp.stream eq {}"
```
我来解释一下其中的几个参数:
* `-T fields -e frame.number -e http.time -e tcp.stream`表示要展示数据包哪几个列的信息。frame.number表示帧号包号、http.time表示HTTP耗时、tcp.stream表示TCP流号。
* `-Y "tcp.stream eq {}"`在管道符后面使用指的是需要把这个TCP流号相关的数据包都展示出来。因为结合了xargs命令所以eq后面是一个`{}`符号这个值就是TCP流的编号由awk命令输出
下图是对这个案例应用此命令得到的输出这一系列数据包跟我们前面在Wireshark图形界面里Follow TCP Stream而得到的那些数据包是一致的
![图片](https://static001.geekbang.org/resource/image/ab/98/abf3a85e16062d1546103b8a305ed698.png?wh=744x277)
是不是有点神奇我们在图形界面里能做的事情在命令行里也一样能做到只要借助tshark。而正因为它是命令行形式所以具备下面这些**优势**
* 图形界面里需要反复多次进行的操作,在命令行里利用管道特性,一条命令就可以完成。
* 图形程序集中在单个文件的处理,而命令行可以方便地对多个文件进行批量处理。
* 命令行的输入和输出都是文本,便于复制,在多人沟通协作场景下更显便利。
这样通过利用tshark对大量HTTP事务的耗时进行统计我们很容易就能得出整体的HTTP事务性能状况了比如平均耗时、各耗时区间的事务占全部事务的比例等等。这不仅能在**问题排查**的时候提供帮助,而且在一些**性能调优**的场合,还可以给我们提供应用日志以外的另一种性能数据源。
## 小结
这节课,我给你介绍了一种排查偶发性问题,你可以重点关注和回顾以下这些知识点。
* **偶发性问题的排查思路**
即先预估时间,然后一边抓包一边观察问题是否出现,在问题出现后停止抓包,最后进行抓包分析。也就是这样的流程:
![](https://static001.geekbang.org/resource/image/5c/71/5c0ea3757e87b3d2df8d04c6eb9d3171.jpg?wh=2000x528)
* **排查技巧**
首先是控制tcpdump抓包大小的方法。
为了控制抓包文件在合理的范围,我们可以用两种方法。
* 方法一运行tcpdump时指定循环参数比如每隔多少MB或者每隔多少分钟就生成一个文件一共生成n个文件循环使用。
* 方法二tcpdump -s参数指定-s后面的数值为54到74就可以抓取到二层到四层的头部信息了。如果要抓取应用层头部可以指定相应的更大的值。
第二是找到HTTP耗时最高事务的方法。
为了找到HTTP耗时最高的事务我们可以通过**过滤器** `http.time` 或者 `http`把HTTP事务都过滤出来然后增加一个**自定义列**来展示HTTP耗时。最后我们点击这个列就完成对HTTP耗时这个自定义列的排序了。
而除了Wireshark这个工具以外我们用**tshark**命令行工具,也同样可以实现“从大量报文中对某个指标进行解析和排序”的目的。
第三是找到HTTP事务的报文对的方法。
在Wireshark中选中HTTP的请求或者响应报文时这个报文以及与它对应的响应或者请求报文的左边会出现**两个水平方向的箭头一个向右一个向左表示它们属于同一个HTTP事务。**
## 思考题
最后,给你留两道思考题:
* 前面我介绍了使用tshark来找到耗时最高的HTTP事务的方法。关于tshark你自己还有哪些使用经验呢
* 在“是否还有其他可能?”这里,我提到了可能的重传。如果要验证是否真的存在这种重传,你觉得应该做什么呢?
欢迎在留言区分享你的答案,也欢迎你把今天的内容分享给更多的朋友,我们一起成长、进步。