《网络排查案例课》应用层案例
《网络排查案例课》15 | Nginx的499状态码是怎么回事?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
应用层报错日志的排查方法 ======================================================================================= 公网上丢包现象不可能完全消失。千分之一左右的公网丢包率属于正常范围。 从排查的方法论上来说,对于更广泛的应用层报错日志的排查,我的推荐是这样的: 1.首先查看应用文档,初步确定问题性质,大体确定排查方向。 2.通过对比应用日志和抓取的报文,在传输层和网络层寻找可疑报文。在这一步,可以采用以下的比对策略来找到可疑报文: a.日志中的 IP 跟报文中的 IP 对应; b.日志和报文的时间戳对应; c.应用层请求信息和报文信息对应。 3.结合协议规范和报文现象,推导出根因。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
Nginx 499状态码;Nginx 499案例 ======================================================================================================== Nginx 499 是 Nginx 自身定义的状态码,并非任何 RFC 中定义的 HTTP 状态码。 它表示的是“Nginx 收到完整的 HTTP request 前(或者已经接收到完整的 request 但还没来得及发送 HTTP response 前),客户端试图关闭 TCP 连接”这种反常情况。 对内表现为记录 499 日志,对外表现为回复 HTTP 400 给消息网关。 这个 499 错误日志,在流量较大的场景下,特别是面向 Internet 的 Web 站点场景下还是很常见的 。 499 这个状态码本身能帮到我们什么呢?我们可以查一下它在 Nginx 里的官方定义: NGX_HTTP_CLIENT_CLOSED_REQUEST | 499 -------------------------------------------------------------------------------------------------- 案例: Nginx 服务器会连续几天记录较多的 499 错误日志,之后几天可能趋零,然后再回升,整体状况起伏不定。 现象:nginx日志显示499错误,但是抓包分析,看到了http 400错误 也就是说nginx日志记录为499错误,但是回包时,在数据包中表现为400错误 以应用层的视角,是无法“看到”具体的网络报文的。 判断TCP RST流就是产生nginx499日志的报文,因为以下三点: 1.客户端 IP:日志中的 remote IP 跟抓包文件里面的 IP 符合。 2.时间戳:日志的时间戳也跟这个 TCP 流的时间吻合。 3.应用层请求:日志里的 HTTP URL 路径和这个 TCP 流里的 URL 相同。 案例根因: 1.client端有5秒超时的机制,超时则发送FIN报文 2.存在严重的网络问题,导致了数据包的重传 分析: 1.3次握手很快,但client端的post请求包却在3秒才被抓获,这里可以判断有丢包的可能,且在多次重传后,才被发送到server端 2.FIN报文在整数秒被抓获,往往这种整数,可以猜测为应用层的设置 3.最后的RST报文在nginx中记录为499错误;但是在server端返回的却是http 400错误,所以client端应该会被记录为400错误
《网络排查案例课》16 | 服务器为什么回复HTTP 400?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
HTTP概述;HTTP 400;数据包载荷计算:IP载荷长度、TCP载荷长度、HTTP载荷长度 ======================================================================================================== HTTP 的英文全称是 Hypertext Transfer Protocol,中文是超文本传输协议 开发HTTP的原因是因为这些协议并不满足博纳斯·李的需求,比如: FTP 只是用来传输和获取文件,它无法方便地展示文本和图片; NNTP 用来传输新闻,但不适合展示存档资料; SMTP 是邮件传输协议,缺乏目录结构。 在 2015 年之前,HTTP 先后有 0.9、1.0、1.1 三个版本,其中 HTTP/1.0 和 1.1 合称 HTTP/1.x。 虽然谷歌在 2009 年就提出了 SPDY,但最终被接纳成为 HTTP/2,也已经是 2015 年的事了。 最近几年蓬勃发展的还有 HTTP/3(也就是 QUIC 上的 HTTP/2) 但从语义上说,HTTP/2 跟 HTTP/1.x 是保持一致的。HTTP/2 不同,主要是在传输过程中,在 TCP 和 HTTP 之间,增加了一层传输方面的逻辑。 在 HTTP/2 里面,header 和 body 的定义和规则,就跟 HTTP/1.x 一样。比如 User-agent: curl/7.68.0 这样一个 header,在 HTTP/1.x 里是代表了这次访问的客户端的名称和版本,而在 HTTP/2 里,依然是这个含义,没有任何变化。 可以把 HTTP/2 理解为是在 HTTP/1.x 的语义的基础上,增加了一个介于 TCP 和 HTTP 之间的新的“传输层”。 HTTP 协议的知识,包括: 1.HTTP 的各种版本的知识点:HTTP/2 和 HTTP/3 的语义跟 HTTP/1.x 是一致的,不同的是 HTTP/2 和 HTTP/3 在传输效率方面,采用了更加先进的方案。 2.Authorization 头部的知识点:它的格式为 Authorization: <auth-scheme> <authorization-parameters>,如果缺少了某一部分,就可能引发服务端报 HTTP 400 或者 500。 3.HTTP 报文的知识点:两次回车(两个 CRLF)是分隔 HTTP 头部和载荷的分隔符。 4.HTTP 返回码的知识点:HTTP 400 Bad Request 在语义上表示的是请求不符合 HTTP 规范的情况,各种不合规的请求都可能导致服务端回复 HTTP 400。 --------------------------------------------------------------------------------------------------------------- HTTP 400: HTTP 的 RFC 有过好几版,1999 年 6 月的RFC2616确定了 HTTP 的大部分规范,而后在7230、7231、7232等 RFC 中做了更新和细化。RFC2616 是这样定义 400 Bad Request 的: 400 Bad Request The request could not be understood by the server due to malformed syntax. The client SHOULD NOT repeat the request without modifications. 这个请求因为语法错误而无法被服务端理解。客户端不可以不做修改就重复同样的请求。 RFC2616 里还定义了几种必须返回 400 的情况,比如: A client MUST include a Host header field in all HTTP/1.1 request messages . If the requested URI does not include an Internet host name for the service being requested, then the Host header field MUST be given with an empty value. An HTTP/1.1 proxy MUST ensure that any request message it forwards does contain an appropriate Host header field that identifies the service being requested by the proxy. All Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message which lacks a Host header field. 400 Bad Request 的语义,就是让服务端告诉客户端:你发过来的请求不合规,我无法理解,所以我用 400 来告诉你这一点。 ------------------------------------------------------------------------------------------------------------------ 数据包载荷计算:IP载荷长度、TCP载荷长度、HTTP载荷长度 在 IP 协议里有2个字段:Total Length 字段、Header Length字段 IP 载荷 = IP Total Length - IP Header Length 在 TCP 协议里有1个字段:Header Length字段(data offset) TCP payload Length = IP Total Length - IP Header Length - TCP Header Length(data offset) 在 HTTP 里,载荷的长度一般也是由一个 HTTP header(这里指的是某一个头部项,而不是整个 HTTP 头部),也就是 Content-length 来表示的。 假设你有一次 PUT 或者 POST 请求,比如上传一个文件,那么这个文件的大小,就会被你的 HTTP 客户端程序(无论是 curl 还是 Chrome 等)获取到,并设置为 Content-Length 头部的值,然后把这个 header 封装到整体的 HTTP 请求报文中去。 HTTP 报文内容,分成了头部(headers)和载荷(Payload 或者 body)两部分, 而分界线则是两次 CRLF。 也就是说只有PUT和POST请求才同时拥有http头部和http body吗?GET请求只有http头部? 因为在PUT和POST请求中有http body,所以需要在http头部中使用Content-length字段对http body长度进行说明?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
案例:服务器为什么回复 HTTP 400? 该案例其实就是http put请求的header构造有问题,导致服务端识别错误,认为错误的请求,回复了http 400错误 前面引发 HTTP 400 的 PUT 请求,其 Authorization 后面也出现了两个 CRLF,这就会被认为是 headers 的结束,payload 的开始。但实际上,后面跟的又是剩余的 HTTP 头部项,在最后一个头部之后,又是两个 CRLF。 该案例的分析: 如果是传统和狭义上的网络,只包含交换机、路由器、防火墙、负载均衡等环节,那么这里并没有什么问题。没什么重传,也不丢包,更不影响应用消息本身。 如果是广义的网络,那就包含了至少以下几个领域: 对应用层协议的理解; 对传输层和应用层两者协同的理解; 对操作系统的网络部分的理解。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
实验 1:对 HTTP 发送不合规的请求(telnet工具发送http请求);实验 2:对 HTTPS 发送不合规的请求(openssl工具发送https请求) ============================================================================================================== 实验 1:对 HTTP 发送不合规的请求(telnet工具发送http请求) 一边用 telnet 模拟发送 HTTP 请求,一边用 tcpdump 的 -X 参数,展示抓取的报文里面的文本细节。 [root@centos7 ~]# telnet 180.101.49.11 80 Trying 180.101.49.11... Connected to 180.101.49.11. Escape character is '^]'. GET / HTTP/1.1 Authorization #两次回车就表示这次请求发送结束 HTTP/1.1 400 Bad Request #由于请求不合规,目标站点立刻回复了 HTTP 400 Bad Request。 Connection closed by foreign host. #在输入 Authorization 时,后面加上“: Basic”,会收到 HTTP 500。这是因为服务端认为 Authorization: Basic 这个格式本身是正确的,只是后面缺少了真正的凭据(Credential),所以报告了 HTTP 500。 Authorization 后面直接回车,就表示它并没有带上 <auth-scheme>,所以属于不合规,应该回复 HTTP 400。 Authorization: Basic 后直接回车,它的 Authorization 头部有 <auth-scheme>,但是没有带上有效的凭据,应该回复 HTTP 500。 [root@centos7 ~]# telnet www.163.com 80 Trying 36.25.241.115... Connected to www.163.com. Escape character is '^]'. GET / HTTP/1.1 Authorization:Basic HTTP/1.1 400 Bad Request #没有出现预期的500错误 Server: nginx Date: Sun, 31 Jul 2022 13:46:48 GMT Content-Type: text/html Content-Length: 207 Connection: close <html> <head><title>400 Bad Request</title></head> <body> <center><h1>400 Bad Request</h1></center> <hr><center>dx-zhejiang-huzhou-11-36-25-241-29</center> <hr><center>nginx</center> </body> </html> Connection closed by foreign host. ################################################################################################################################ 实验 2:对 HTTPS 发送不合规的请求(openssl工具发送https请求) 对方站点是 HTTPS 的话,如果还是用 telnet,会遇到 TLS 握手,这一关就过不去了 我们可以用 openssl 命令。执行openssl s_client -connect 站点名:443 [root@centos7 ~]# openssl s_client -connect www.baidu.com:443 CONNECTED(00000003) depth=3 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA verify return:1 depth=2 OU = GlobalSign Root CA - R3, O = GlobalSign, CN = GlobalSign verify return:1 depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign RSA OV SSL CA 2018 verify return:1 depth=0 C = CN, ST = beijing, L = beijing, OU = service operation department, O = "Beijing Baidu Netcom Science Technology Co., Ltd", CN = baidu.com verify return:1 --- Certificate chain 0 s:/C=CN/ST=beijing/L=beijing/OU=service operation department/O=Beijing Baidu Netcom Science Technology Co., Ltd/CN=baidu.com i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign RSA OV SSL CA 2018 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign RSA OV SSL CA 2018 i:/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign 2 s:/OU=GlobalSign Root CA - R3/O=GlobalSign/CN=GlobalSign i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA --- Server certificate -----BEGIN CERTIFICATE----- MIIKEjCCCPqgAwIBAgIMRBfOhu+C7GkhzG9oMA0GCSqGSIb3DQEBCwUAMFAxCzAJ …………………………………………………… Cs1X1NAVNn661QMlJ0W0YM0uAsEPCudBb1hpIJ6tR1IatebljR0= -----END CERTIFICATE----- subject=/C=CN/ST=beijing/L=beijing/OU=service operation department/O=Beijing Baidu Netcom Science Technology Co., Ltd/CN=baidu.com issuer=/C=BE/O=GlobalSign nv-sa/CN=GlobalSign RSA OV SSL CA 2018 --- No client certificate CA names sent Peer signing digest: SHA512 Server Temp Key: ECDH, P-256, 256 bits --- SSL handshake has read 5452 bytes and written 415 bytes --- New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256 Server public key is 2048 bit Secure Renegotiation IS supported Compression: NONE Expansion: NONE No ALPN negotiated SSL-Session: Protocol : TLSv1.2 Cipher : ECDHE-RSA-AES128-GCM-SHA256 Session-ID: 9F4721B25880B05DA79A676D810AAFF08F136E204C87CEB68D409F08E8494C83 Session-ID-ctx: Master-Key: 826FDFCCD5D01C16CA5E18F2A62C7B16F97DFDB9DEA83AEF7B2D5CC3BEF165B1C9851BF606575B79BC47E449BE1FC833 Key-Arg : None Krb5 Principal: None PSK identity: None PSK identity hint: None TLS session ticket: 0000 - fe ed 46 0f 70 0c c0 74-2d 97 1b 47 f5 84 cb 2c ..F.p..t-..G..., 0010 - 83 df f5 70 30 59 1b 76-01 38 e3 f6 be dd f3 5a ...p0Y.v.8.....Z 0020 - 1b 49 19 22 1e 3c 9a 34-bf 65 b5 f3 eb 29 c1 d5 .I.".<.4.e...).. 0030 - 24 16 65 38 af 76 a1 89-ca 7e a8 13 98 4a 61 ac $.e8.v...~...Ja. 0040 - 44 df 49 f9 1c 56 82 c8-70 ff 84 c9 9c da 32 3c D.I..V..p.....2< 0050 - 45 79 fa a2 c9 d5 e0 3e-9a aa 18 8b 5b 95 30 54 Ey.....>....[.0T 0060 - 78 73 44 69 42 ab 21 92-8f 07 c9 0a 0c 38 84 1b xsDiB.!......8.. 0070 - f6 01 aa 44 d4 f1 57 0c-13 c0 92 72 d4 93 84 c8 ...D..W....r.... 0080 - 6a 1f 30 e0 a0 90 f6 0c-f5 e5 8e 2f bc 2a ab b9 j.0......../.*.. 0090 - 96 eb fb 89 03 e4 bb c3-ca c4 2e 1a 82 ae c9 61 ...............a Start Time: 1659275361 Timeout : 300 (sec) Verify return code: 0 (ok) --- GET / HTTP/1.1 #跟 telnet 一样,直接键入 HTTP 请求就好了! Authorization HTTP/1.1 400 Bad Request closed
《网络排查案例课》17 | 为什么前端页面里多选一个城市就报错?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
MDN(Mozilla Developer Network);HTTP 5xx 系列:500/502/503/504;TCP流经过LB后,判定两个TCP连接是同一TCP流的方法 ======================================================================================================= 在学习 HTTP 协议的时候,除了阅读 RFC2616 等 RFC 文档,还可以参考 MDN(Mozilla Developer Network),因为是有中文版的,所以对我们更加友好。 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status -------------------------------------------------------------------------------------------------------- HTTP 5xx 系列 500错误是服务器内部错误,而且一般程序上是ASP错误为多的,可能是你的用户权限的问题导致,或者是数据库连接出现了错误,那么要好好检查下服务器语句错误问题。 502 Bad Gateway 是一种HTTP协议的服务器端错误状态代码,它表示作为网关或代理角色的服务器,从上游服务器(如tomcat、php-fpm)中接收到的响应是无效的。 多种情况会导致 HAProxy 回复 502 给客户端,比如: 后端服务器返回的 HTTP 响应不符合 HTTP 规范; 后端服务器没有及时响应; header 的大小写问题; header size 超限。 503 Service Unavailable 是一种HTTP协议的服务器端错误状态代码,它表示服务器尚未处于可以接受请求的状态。 504 Gateway Timeout 是一种HTTP协议的服务器端错误状态代码,表示扮演网关或者代理的服务器无法在规定的时间内获得想要的响应。 502/503/504 这几个返回码,都是给 反向代理 / LB 用的。反向代理 / LB 位于客户和服务之间,起到了转发、分流、处理的作用。 HTTP 502/503/504 状态码的本质 HTTP 5xx 系列状态码的语义的本质:跟 500 不同,502、503、504 都是 LB / 反向代理的后端的服务出了问题。 ---------------------------------------------------------------------------------------------------------- TCP流经过LB后,判定两个TCP连接是同一TCP流的方法 LB 云 LB 的左边是一个 TCP 连接,右边是另外一个 TCP 连接,两者在网络层面毫无关联。 如何判定两个TCP连接是同一个TCP流呢? tcp contains :比如应用层时常会用 uuid,作为区分不同 HTTP 请求的方法,正好可以为我们所用。 使用这个方法的根据是:进入到客户侧的请求,一般会由 LB 或反向代理大体不改动地转发到服务侧。 这里说“大体不改动”,是因为反向代理或者 LB 可能会插入一些 HTTP header(比如常见的 X-Forwarded-For),但一般不改写原有的 URL 和 header。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
案例:为什么前端页面里多选一个城市就报错?(header size 超限导致HTTP 502);排查思路 ================================================================================================ 现象: 选择5个城市,通过LB访问时,LB返回502错误 选择5个城市,直接访问服务器时,请求返回正常 分析: 在LB上抓包进行分析,通过"tcp contains"锁定了经过LB前后的2个TCP流(同一TCP流),发现从服务端返回http 200,但LB却返回http 502给客户端 HTTP 502:云 LB(基于 HAProxy)认为,后端返回的 HTTP 响应并不符合它对于“有效”的定义。 多种情况会导致 HAProxy 回复 502 给客户端,比如: 1.后端服务器返回的 HTTP 响应不符合 HTTP 规范; 2.后端服务器没有及时响应; 3.header 的大小写问题; 4.header size 超限。 考虑到客户在内网直接测试 Nginx 可以正常完成,那么 1、2、3 基本可以排除。header size 要重点排查 查看返回的http 200 信息,判断出http header大小差不多为9817字节左右 教程的思路是算出http 200的总大小,然后减去request中的"Content-Length"(这不合理呀。。。) HTTP 协议头部的构造,其中 Content-Length 头部就是表示了 HTTP body 的大小。 查阅了 HAProxy 版本 1.5.0 的官方文档,并对比了 v1.5.0 的源代码,终于发现了 header size 的秘密。 #ifndef BUFSIZE #define BUFSIZE 16384 #endif // reserved buffer space for header rewriting #ifndef MAXREWRITE #define MAXREWRITE (BUFSIZE / 2) HAProxy 定义了一个读写缓存 BUFSIZE。 每次读取 HTTP 头部的时候,有可能会做增加 header 和改写 header 的操作,所以预留了一部分空间 MAXREWRITE,它的值等于 BUFSIZE/2。 真正可以用来临时存放 HTTP 头部的缓存大小就是:BUFSIZE - MAXREWRITE = 16384 - 16384/2 = 8192 字节。 也就是真正能接纳的 HTTP 请求的头部的大小,只有 8192 字节! 根因:服务器返回时,http header的信息长度过长,超过了HAProxy 1.5.0 定义的BUFSIZE / 2,所以LB判断有问题,向客户端返回了http 502 修复: 1.临时修改了云 LB(HAProxy)的配置,把它的限制从 8KB 提升到 16KB,这个问题立刻被解决了。 2.作为长期方案,我们建议客户合理使用 Set-Cookie 头部,确保整体的 HTTP Response size 在一定的合理区间之内(8KB),避免无谓的系统开销和难以预料的问题的发生。 ----------------------------------------------------------------------------------------------------- 排查思路: -> 确认问题症状 -> 排除法确定问题在LB -> tshark统计发现大量502 -> 根据前端连接的应用层uuid,找到后端连接的对应TCP流 -> 发现后端连接实际返回200,定位是HAProxy导致 -> 从文档和源码中确认是header size的限制 -> 计算抓包文件中字节数,确认根因是超限 -> 提升header size limit,问题解决
《网络排查案例课》18 | 偶发性问题如何排查?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
偶发性问题的2个误区;偶发性问题的排查思路 ============================================================================================= 误区 1:没有现场,也没有抓包,但只要我们有历史记录,就能通过它查到根因。 比如这样一个场景:用户访问页面时偶尔遇到 HTTP 503 错误。 它的视角是应用层,并不是网络层,所以天生就无法了解底层网络到底发生了什么。 误区 2:为了任何时候都能有现场数据,就一直抓包,一旦有问题就直接看抓包文件。 实中却并不可行。主要有两大原因: 第一,抓包数据量太大。 第二,常态的抓包对系统性能的影响不能低估。 会对已经有问题的系统产生“叠加伤害”,有可能会出现“不抓包的时候只有老问题,而抓包之后,除了老问题,还出现了新问题”。所以,常态化的抓包工作,既不可行,也没必要。 #################################################################################################### 我们可以采用怎样的重现 + 排查策略? 1. 初步估计问题出现的时间跨度,对于问题何时重现有个预期 2. 在问题机器上发起抓包,根据预估的时间跨度,指定抓包方式。 技巧一:利用 tcpdump 里跟循环抓包相关的几个参数。 -W 个数:生成的循环文件数量,生成到最大数量后,新的报文数据会覆盖写入第一个文件,以此类推。 -C 尺寸:每个文件的大小,单位是 MB。 -G 间隔时间:每次新生成文件的间隔时间,单位是分钟。 tcpdump -i eth0 -w file.pcap -W 10 -C 100 -G 60 #每 100MB 或者 60 分钟(满足任一条件即可)就生成一个文件,一共 10 个文件 技巧二:在循环抓包的基础上,再利用 tcpdump 的 -s 参数可以大幅减小抓包文件的大小。 tcpdump -s 54:抓取到从二层到四层的报文头部(不带 TCP Options)了。 tcpdump -s 74:可以抓取到所有 TCP Options,对排查 TCP 行为足够用。 如果要对应用层的头部(比如 HTTP 头部)也进行抓取,那可以设置更大的 -s 参数值。整体来说,这样的抓包文件,要比抓满的情况(1514 字节)小很多。 1514 字节是网络层 MTU 的 1500 字节,加上帧头的 14 字节。所以一般不指定 -s 参数的抓包文件,其满载的帧大小是 1514 字节。 3. 定时观察,在问题重现时停止抓包。 a.直接人工观察应用日志或者仪表盘,比如每隔几分钟就刷新一次,直到问题重现。 b.设定自动告警机制,当通过邮箱或者手机短信等途径收到相应告警时,就知道问题重现了。 4. 导出抓包文件,结合应用日志展开分析。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
实战案例:网站偶尔会变慢?;排查技巧总结 =================================================================================================== 实战案例:网站偶尔会变慢? 现象:网站偶尔会变慢,比如正常 1 秒内就能完成的请求,可能会变成 5 秒钟或者更长。 预估:如何预估问题的出现频率和周期? 每几百次出现一次”,我们可以保守估计为 1000 次中出现一次问题。 访问频率大概在 10 次 / 分钟。 1000/10=100 分钟,这就是预估的时间跨度。 抓包:抓多长时间合适? tcpdump 抓取了 100 分钟左右的数据包,并发回给我们做分析。 监控:如何重现问题? 抓包分析 1:如何在网络视角上精确定义问题? Wireshark 的一个小的知识点:左侧的两个水平方向的箭头(一个向右,一个向左),分别表示这两个数据包是 HTTP 请求和 HTTP 响应。 抓包分析 2:如何用 Wireshark 实现对大量 HTTP 事务的性能分析? Wireshark 过滤器是 http.time [Time since request: xxx seconds] 就表示了 HTTP 耗时 思路:先过滤,再排序;通过http耗时锁定了存在问题的会话 结论: 服务端收到 HTTP 请求后,花了大约 6.5 秒的时间才回复了 HTTP 响应。这充分说明两点: 1.网络上没有任何丢包、重传等问题; 2.服务端响应耗时高达 6 秒以上。 排查了网络的嫌疑,客户就转而去检查应用层面的问题了。 tshark -r 文件名 -T fields -e http.time | grep -v ^$ 注意,我们在命令的后面,必须加上管道符“ | ”和“grep -v ^$”,要不然就会看到下面这种,有大段空白的输出(因为凡是非 HTTP 的数据包都没有输出,会成为一个空行) 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 命令输出)。 ---------------------------------------------------------------------- 排查技巧 首先是控制 tcpdump 抓包大小的方法。 为了控制抓包文件在合理的范围,我们可以用两种方法。 方法一:运行 tcpdump 时指定循环参数,比如每隔多少 MB 或者每隔多少分钟就生成一个文件,一共生成 n 个文件,循环使用。 方法二:tcpdump -s 参数,指定 -s 后面的数值为 54 到 74,就可以抓取到二层到四层的头部信息了。如果要抓取应用层头部,可以指定相应的更大的值。 第二是找到 HTTP 耗时最高事务的方法。 为了找到 HTTP 耗时最高的事务,我们可以通过过滤器 http.time 或者 http,把 HTTP 事务都过滤出来,然后增加一个自定义列来展示 HTTP 耗时。最后,我们点击这个列,就完成对 HTTP 耗时这个自定义列的排序了。 而除了 Wireshark 这个工具以外,我们用 tshark 命令行工具,也同样可以实现“从大量报文中对某个指标进行解析和排序”的目的。 第三是找到 HTTP 事务的报文对的方法。 在 Wireshark 中,选中 HTTP 的请求或者响应报文时,这个报文以及与它对应的响应或者请求报文的左边,会出现两个水平方向的箭头(一个向右,一个向左),表示它们属于同一个 HTTP 事务。
《网络排查案例课》19 | TLS的各种特性:TLS握手为什么会失败?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
HTTPS和TLS;加密算法套件Cipher Suite ================================================================================== 什么是 HTTPS? 它其实不是某个独立的协议,而是 HTTP over TLS,也就是把 HTTP 消息用 TLS 进行加密传输。 ---------------------------------------------------------------------------------- TLS 同时使用了对称算法和非对称算法。TLS 的整个过程大致可以分为两个主要阶段: TLS 的整个过程大致可以分为两个主要阶段: 握手阶段,完成验证,协商出密码套件,进而生成对称密钥,用于后续的加密通信。 加密通信阶段,数据由对称加密算法来加解密。 非对称加密完成随机数的传送,最终组合成对称加密秘钥 对称加密秘钥则用于对数据进行加密 实际上,TLS 的握手阶段需要在下面四个环节里实现不同类型的安全性,它们可以说是 TLS 的“四大护法”。 密钥交换算法:保证对称密钥的交换是安全的,典型算法包括 DHE、ECDHE。 身份验证和签名算法:确认服务端的身份,其实就是对证书的验证,非对称算法就用在这里。典型算法包括 RSA、ECDSA。 补充:如果是双向验证(mTLS),服务端会验证客户端的证书。 对称加密算法:对应用层数据进行加密,典型算法包括 AES、DES。 消息完整性校验算法:确保消息不被篡改,典型算法包括 SHA1、SHA256。 ------------------------------------------------------------------------------------- Cipher Suite 在 TLS 中,真正的数据传输用的加密方式是对称加密;而对称密钥的交换,才是使用了非对称加密。 一个典型的密码套件:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA(0xc013) TLS 代表了 TLS 协议。 ECDHE 是密钥交换算法,双方通过它就不用直接传输对称密钥,而只需通过交换双方生成的随机数等信息,就可以各自计算出对称密钥。 RSA 是身份验证和签名算法,主要是客户端来验证服务端证书的有效性,确保服务端是本尊,非假冒。 AES128_CBC 是对称加密算法,应用层的数据就是用这个算法来加解密的。这里的 CBC 属于块式加密模式,另外一类模式是流式加密。 SHA 就是最后的完整性校验算法(哈希算法)了,它用来保证密文不被篡改。 0xc013 呢,是这个密码套件的编号,每种密码套件都有独立的编号。完整的编号列表在 IANA 的网站上可以找到。 在不同的客户端和服务端软件上,这些密码套件也各不相同。所以,TLS 握手的重要任务之一,就是找到双方共同支持的那个密码套件,也就是找到大家的“共同语言”,否则握手就必定会失败。 怎么获得这个 Java 库能支持的密码套件列表呢?其实最直接的办法,还是用抓包分析。 检查一下 Client Hello 报文。在那里,就有 Java 库支持的密码套件列表。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
案例 1:TLS 握手失败(handshake_failure:TLS 服务端无法协商出一个可以接受的安全参数集,即在client和server找不到一个共同的加密算法套件 Cipher Suite) ========================================================================================================================================== 案例 1:TLS 握手失败(handshake_failure:TLS 服务端无法协商出一个可以接受的安全参数集,即在client和server找不到一个共同的加密算法套件 Cipher Suite) 问题根因:因为这个 Java 库和 API server 2 之间,没有找到共同的密码套件,所以 TLS 握手失败。 背景:我们有一个应用需要访问 Kubernetes 集群的 API server。因为我们有很多个集群,所以相应的 API server 也有很多个。 现象1:从同一台client使用客户端程序去访问 API server 1 是可以的,但访问 API server 2 就不行。 现象2:client使用curl访问 API server 2 是ok的 错误日志:javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure 排除服务端问题 从这台客户端用curl发起对 API server 2(也就是握手失败的那个)的 TLS 握手,发现其实是可以成功的。这说明,API server 2 至少在某些条件下是可以正常工作的。 curl -vk https://api.server.777.abcd.io * Rebuilt URL to: https://api.server.777.abcd.io/ * Trying 10.100.20.200... * Connected to api.server.777.abcd.io (10.100.20.200) port 443 (#0) * found 153 certificates in /etc/ssl/certs/ca-certificates.crt * found 617 certificates in /etc/ssl/certs * ALPN, offering http/1.1 * SSL connection using TLS1.2 / ECDHE_RSA_AES_128_GCM_SHA256 #看到协商出的密码套件,即客户端和服务端都支持的密码套件 * server certificate verification SKIPPED * server certificate status verification SKIPPED * common name: server (does not match 'api.server.777.abcd.io') * server certificate expiration date OK * server certificate activation date OK * certificate public key: RSA * certificate version: #3 * subject: CN=server * start date: Thu, 24 Sep 2020 21:42:00 GMT * expire date: Tue, 23 Sep 2025 21:42:00 GMT * issuer: C=US,ST=San Francisco,L=CA,O=My Company Name,OU=Org Unit 2,CN=kubernetes-certs * compression: NULL 但是用客户端程序进行访问,TLS就是协商失败,且100%复现 TLS Alert 报文显示,它的编号是 40,指代的是 Handshake Failure 这个错误类型。 正确的做法是:去 RFC 里寻找答案;握手用的是 TLS1.2 协议,我们就来看它的RFC5246 handshake_failure Reception of a handshake_failure alert message indicates that the sender was unable to negotiate an acceptable set of security parameters given the options available. This is a fatal error. 基于已经收到的 Client Hello 报文中的选项,TLS 服务端无法协商出一个可以接受的安全参数集”。而这个所谓的安全参数集,在这里具体指的就是加密算法套件 Cipher Suite。 怎么获得这个 Java 库能支持的密码套件列表呢?其实最直接的办法,还是用抓包分析。 检查一下 Client Hello 报文。在那里,就有 Java 库支持的密码套件列表。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
TLS 证书链:根证书 -> 中间证书 -> 叶子证书;TLS相关知识小结 ====================================================================================== TLS 证书链:根证书 -> 中间证书 -> 叶子证书 TLS 证书验证是“链式”的机制。比如,客户端存有根证书和它签发的中间证书,那么由中间证书签发的叶子证书,就可以被客户端信任了,也就是这样一条信任链: 信任根证书 -> 信任中间证书 -> 信任叶子证书 三种不同情况下的信任链: 1.客户端有根证书+中间证书;服务端发送了叶子证书(验证ok,信任链是完整的) 2.客户端有根证书,无中间证书;服务端发送了中间证书+叶子证书(验证ok,信任链是完整的) 3.客户端有根证书,无中间证书,服务端发送了叶子证书(验证失败,信任链不完整) 每次证书在更新的时候,它对应的私钥不是必须要更新的,而是可以保持不变的。 所以可能存在旧中间证书可以解开新叶子证书的签名部分 TLS 证书签名 TLS 证书都有签名部分,这个签名就是用签发者的私钥加密的。 --------------------------------------------------------------------------------------- TLS相关知识小结 加密算法的类型 对称加密算法:加密和解密用同一个密钥,典型算法有 AES、DES。 非对称加密算法:加密和解密用不同的密钥,典型的非对称加密算法有 RSA、ECDSA。 TLS 基础 TLS 是先完成握手,然后进行加密通信。非对称算法用于交换随机数等信息,以便生成对称密钥;对称算法用于信息的加解密。 Cipher Suite(密码套件) 在握手阶段,TLS 需要四类算法的参与,分别是:密钥交换算法、身份验证和签名算法、对称加密算法、消息完整性校验算法。这四类算法的组合,就形成了密码套件,英文叫 Cipher Suite。 这是 TLS 握手中的重要内容,我们的案例 1 就是因为无法协商出公用的密码套件,所以 TLS 握手失败了。 TLS 证书链 TLS 的信任是通过对证书链的验证:信任根证书 -> 信任中间证书 -> 信任叶子证书 本地证书加上收到的证书,就形成了证书链,如果其中有问题,那么证书校验将会失败。我们的案例 2,就是因为一些极端情况交织在一起,造成了信任链过期的问题,导致证书验证失败了。 Trust store 它是客户端使用的本地 CA 证书存储,其中的文件过期的话可能导致一些问题,在排查时可以重点关注。 排查技巧 curl -vk https://站点名 #使用 curl 命令,检查 HTTPS 交互过程的方法 openssl s_client -tlsextdebug -showcerts -connect 站点名:443 #使用 OpenSSL 命令来检查证书的方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
案例 2:有效期内的证书为什么报告无效?(客户端本地的中间证书过期) ======================================================================================= 案例 2:有效期内的证书为什么报告无效? 现象: 有一次,一个产品开发团队向我们运维团队报告了一个问题:他们的应用在做了代码发布后,就无法正常访问一个内部的 HTTPS 站点了,报错信息是:certificate has expired。 这次确实有个变更,会在客户端打开服务端证书校验的特性,而这个特性在以前是不打开的。 测试: 从另外一台客户端的 OpenSSL 去连接这个 HTTPS 站点,也报告 certificate has expired。 使用strace命令进行排查: strace openssl s_client -tlsextdebug -showcerts -connect abc.ebay.com:443 stat("/usr/lib/ssl/certs/a1b2c3d4.1", {st_mode=S_IFREG|0644, st_size=2816, ...}) = 0 #OpenSSL 读取了/usr/lib/ssl/certs目录下的文件 a1b2c3d4.1;它就是 TLS 客户端本地的 Trust store 里,存放的中间证书文件。 openat(AT_FDCWD, "/usr/lib/ssl/certs/a1b2c3d4.1", O_RDONLY) = 6 ...... write(2, "verify return:1\n", 16verify return:1 ) = 16 ....... write(2, "verify error:num=10:certificate "..., 44verify error:num=10:certificate has expired ) = 44 write(2, "notAfter=", 9notAfter=) = 9 write(2, "Oct 14 18:45:33 2020 GMT", 24Oct 14 18:45:33 2020 GMT) = 24 #OpenSSL 就报告了 certificate has expired 的错误,expire 的日期是 2020 年 10 月 24 日(输出中的“24Oct 14”)。 根因:本地存放了旧的中间证书,服务器发来中间证书和叶子证书;客户端使用本地的旧中间证书去解叶子证书(客户端无事了服务器发来的中间证书),成功解锁,但是本地中间证书已过期,所以报了"certificate has expired" 客户端的 Trust store 里就有这个 CA 的公钥(在 CA 证书里),它用这个公钥去尝试解开签名,能成功的话,就说明这张叶子证书确实是这个 CA 签发的。(即使这个中间证书是旧的) 这里最关键的部分在于,新老中间证书用的私钥是同一把,所以这张叶子证书的签名部分,用老的中间证书的公钥也能解开,这就使得下图中的橙色的验证链条得以“打通”,不过,谁也没料到打通的是一条“死胡同”。
《网络排查案例课》20 | TLS加解密:如何解密HTTPS流量?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
TLS 加密原理;TLS加密相关概念 ======================================================== TLS 加密原理 查看 TLS 证书信息: chrome浏览器--->点击锁--->点击"连接是安全的"--->点击"证书有效" 解读 TLS 证书 证书链;证书名称、身份验证和签名算法、有效期。 TLS 证书为了支持更多的域名,设计了一个扩展选项 Subject Alternative Name,简称 SAN,它就包含有多个域名。 密钥交换算法在证书里看不出来,需要根据握手协商的结果来判定。 不过,我们也可以有个初步的判断。如果这次通信用的 TLS 版本是 1.3,那么就是 DHE 或者 ECDHE 这样的“前向加密”的密钥交换算法了。结尾的 E 是 Ephemeral,意思是“短时间的”,也就是密钥是每次会话临时生成的。 身份验证和签名算法 证书里明确写着的 ECDSA,其中 EC 就是 Elliptic Curve 的缩写,也就是椭圆曲线算法,它可以用更短的密钥达到跟 RSA 同样的密码强度。 后面跟着的 SHA-256 是哈希摘要算法,证书内容用这个 SHA-256 算法做了哈希摘要,然后用 ECDSA 算法对摘要值做了签名,这样的话,客户端就可以验证这张证书的内容有没有被篡改了。 对称加密算法 在证书这里看不出来,因为它也是通过握手协商出来的。当然,用 OpenSSL 或者 curl 命令就可以观察到 完整性校验算法 SHA-256 $ openssl s_client -connect sharkfesteurope.wireshark.org:443 ...... New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 # TLS 里协商出来的密码套件 ...... [root@centos7 ~]# openssl s_client -connect www.baidu.com:443 ……………… New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256 # TLS 里协商出来的密码套件 ……………… TLS1.3 版本,密码套件是 TLS_AES_256_GCM_SHA384 TLS1.2 的密码套件 TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA 相比之下,TLS1.3 的套件 TLS_AES_256_GCM_SHA384 少了两个算法:身份验证和签名算法,还有密钥交换算法。 身份验证和签名算法倒是可以从证书里看到,它是 ECDSA。 密钥交换算法:因为 TLS1.3 只允许前向加密(PFS)的密钥交换算法了,所以使用静态密钥的 RSA 已经被排除了,它默认使用的是 DHE 和 ECDHE,所以就不写在密码套件名称里了。 前向加密(PFS) 前向加密又称为“完美前向加密”,它的英文就是 Forward Secrecy 和 Perfect Forward Secrecy。 TLS1.3 强制要求前向加密的原因: 在密钥交换的时候用非前向加密的算法(比如 RSA),那么一旦黑客取得了服务端私钥,并且抓取了历史上的 TLS 密文, 他就可以用这个私钥和抓包文件,把这些 TLS 会话的对称密钥给还原出来,从而破解所有这些密文。因为可以把之前的密文都破解,RSA 就不属于“前向”加密。 解决:每次参与协商对称密钥时的非对称密钥都不一样。这样的话,即使黑客破解了其中一次会话的密钥,也无法用这个密钥破解其他会话。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
客户端做 TLS 解密(wireshark解读HTTPS内容);SSLKEYLOGFILE文件;wireshark解读HTTPS内容 ============================================================================== 客户端做 TLS 解密 1.创建一个用来存放 key 信息的日志文件,然后在系统里配置一个环境变量 SSLKEYLOGFILE,它的值就是这个文件的路径。 2.重启浏览器,启动抓包程序,然后访问 HTTPS 站点,此时 TLS 密钥信息将会导出到这个日志文件,而加密报文也会随着抓包,被保存到抓包文件中。 补充:如果是 Mac 又不想改动全局配置,那么你可以在 terminal 中的 export SSLKEYLOGFILE=路径,然后执行 open "/Applications/Google\ Chrome.app", 这时 Chrome 就继承了这个 shell 父进程的环境变量,而 terminal 退出后,这个环境变量就自动卸除了。 3.在 Wireshark 里,打开 Preferences 菜单,在 Protocol 列表里找到 TLS,然后把 (Pre)-Master-Secret log filename 配置为那个文件的路径。 原理: 浏览器在启动过程中会尝试读取 SSLKEYLOGFILE 这个环境变量。 如果存在这个变量,而它指向的又是一个有效的文件,那么浏览器就会做最为关键的事情了:它去调用 TLS 库,让 TLS 库把访问 HTTPS 站点的过程中的 key 信息导出到 SSLKEYLOGFILE 文件中。 ----------------------------------------------------------------------------- SSLKEYLOGFILE文件 TLS 库把密钥交换阶段的核心信息 Master secret 导出到了这个文件中。基于这个信息,Wireshark 就可以还原出当时的对称密钥,从而破解密文。 SSLKEYLOGFILE 的格式 TLS1.2,每一行是一条记录,每一条记录由 3 个部分组成,中间用空格分隔 <Label1> <ClientRandom1> <Secret1> <Label2> <ClientRandom2> <Secret2> Label:是对这条记录的描述,对于 TLS1.2 来说,这个值一般是 CLIENT_RANDOM。另外,RSA 也是一个可能的值(因为需要前向加密,所以不推荐使用RSA) ClientRandom:这个部分就是客户端生成的随机数,随机数原始长度为 32 字节,但在这里是用 16 进制表示的,每个字节用 16 进制表示就会成为 2 个字符,所以就变成了 64 个字符的字符串。 Secret:这就是 Master secret,也就是通过它可以生成对称密钥。Master secret 固定是 48 字节,也是十六进制表示的关系,成为 96 个字节的字符串。 Master secret 就是最为关键的信息了,也正是黑客苦苦寻求的东西。它是万万不能在网络上传输的,自然也不可能在抓包文件里看到它,只有 TLS 库才能导出它。 TLS1.2 的 KEYLOGFILE 的具体的例子: CLIENT_RANDOM 770c2……ec18ec(64个字符) bea2c0……db9b8a0183a721(96个字符) TLS1.3 的格式会很不一样 ------------------------------------------------------------------------------ Wireshark 是怎么解开密文的? 1.Wireshark通过 ClientRandom 找到对应的 TLS 会话(你可以理解为是 TCP 流),从而找到 Master secret ClientRandom出现在抓包文件TLS client hello包中 2.由于在抓包文件里就有 ECDHE 密钥交换算法所需要的各种参数,结合这里的 Master secret,Wireshark 就可以解析出对称密钥,从而把密文解密了! 要想破解密文,既要有抓包文件,也要有 SSLKEYLOGFILE 日志文件,两者结合才能解密。 同时还要有TLS 握手阶段的报文 ---------------------------------------------------------------------------------- 实时查看解密信息的步骤: 1.SSLKEYLOGFILE 环境变量; 2.之后再启动浏览器,然后直接在 Wireshark 里开始抓包; 3.设置 Wireshark 的 TLS 协议,配置 (Pre-)Master secret logfile。 停止抓包后再启动抓包,抓包文件又变成密文了? 因为没抓包TLS握手报文的话,就无法通过ClientRandom找到 Master secret,自然也就不知道使用哪个Master secret对报文进行解析了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
服务端做 TLS 解密;TLS 解密小结 ====================================================================================================== 服务端如何做 TLS 解密? 很多服务端程序并没有提供 TLS 解密的功能,也就是想做抓包解密也做不了。而要自己实现这个特性,难度跟简单的参数配置,不在一个等级。难度高,可能是更加关键的原因。 服务端TLS 解密原理:在软件架构上,服务端和客户端也是类似的,也是基于 TLS 库来构建 TLS 加解密的能力的。 一些商业产品比如 Netscaler 就是可以一边抓包,一边导出 TLS key。 服务端程序没有提供TLS解密功能,那么可以使用 SSLCTXsetkeylogcallback() 回调函数,就可以把 TLS 信息导出来 解密步骤其实和客户端解密是一样的: 1.导出 SSL KEY 2.tcpdump 抓包 3.在 Wireshark 里同样配置好 TLS 协议的 (Pre)-Master-Secret log filename,打开抓包文件后,就可以跟在客户端类似,直接看到明文了。 ---------------------------------------------------------------------------------- 小结 证书中的 SAN 列表包括了它所支持的站点域名,所以只要被访问的站点名称在这个列表里,名称匹配就不是问题了。 证书中的域名通配符只支持一级域名,而不支持二级或者更多级的域名。 在 TLS1.3 中,密钥交换算法被强制要求是前向加密算法,所以默认采用 DHE 和 ECDHE,而 RSA 已经弃用。 RSA 依然可以作为可靠的身份验证和签名算法来使用。另外一种验证和签名算法是 ECDSA,它可以用更短的密钥实现跟 RSA 同样的密码强度。 前向加密可以防止黑客破解发生在过去的加密流量,提供了更好的安全性。 首先,在客户端做抓包解密,需要做三件事: 创建一个文件,并设置为 SSLKEYLOGFILE 这个环境变量的值; 重启浏览器,开始做抓包,此时 key 信息被浏览器自动导入到日志文件; 在 Wireshark 里把该日志文件配置为 TLS 的 (Pre)-Mater-Secret log filename。 服务端抓包解密,就要依托于软件实现了,但是有些软件并没有提供这种功能,比如 Envoy。借助底层 BoringSSL 库的接口,eBay 流量管理团队实现了对这个接口的调用,我们也可以在 Envoy 上完成抓包解密了。 Wireshark 能解读出密文的原理: 从抓包文件中定位到 client random; 从日志文件中找到同样这个 client random,然后找到紧跟着的 Master secret; 用这个 Master secret 导出对称密钥,最后把密文解密。
《网络排查案例课》21 | 为什么用了负载均衡更加不均衡?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
syscall和strace工具;进程导致 load 高的2个排查思路 ============================================================================================= 系统调用,英文叫 system call,缩写是 syscall。 内核态和用户态 x86 CPU 实现了 4 个级别的保护环,也就是 ring 0 到 ring 3,其中 ring 0 权限最大,ring 3 最小。 就拿 Linux 来说,它的内核态就运行在 ring 0,只有内核态可以操作 CPU 的所有指令。Linux 的用户态运行在 ring 3,它就没办法操作很多核心指令。 strace工具 通过 strace,我们可以把排查工作从进程级别,继续追查到更细的 syscall(系统调用)级别。无论是系统调用读写文件时的问题,还是系统调用本身的问题,都可以在 strace 的帮助下现出原形。 strace 的用法一般有两种: 1.直接在命令之前加上 strace 示例:strace curl www.baidu.com 能看到前后的几十个系统调用,包括打开文件的 openat()、关闭文件描述符的 close()、建立 TCP 连接的 connect() 等等。 2.执行 strace -p PID,这比较适合对持续运行的服务(Daemon)进行追踪。 示例:strace -p PID; timeout 5 strace -cp PID #收集 5 秒钟的数据; -c 参数,统计每个系统调用消耗的时间和次数 strace -p $(pidof nginx | awk '{print $1}') -------------------------------------------------------------------------------------------- 进程导致 load 高的2个排查思路 1.白盒检查:检查代码本身,找到根因。 2.黑盒检查:不管代码怎么回事,我们从程序的外部表现来分析,寻求根因。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
案例 1:高负载和不均衡(负载是均衡的,服务器因为安装了性能监控软件,所以系统负载高,最终导致个别服务器响应慢,看起来不均衡) ============================================================================================= 案例 1:高负载和不均衡(负载是均衡的,服务器因为安装了性能监控软件,所以系统负载高,最终导致个别服务器响应慢,看起来不均衡) 背景: LB 运行在第四层,设置的负载均衡策略是 round robin(轮询),也就是新到达 LB 的请求,依次派发给后端 3 个 Nginx 服务器,使得每台机器获得的请求数相同。 Nginx 运行在第七层,它作为 Web 服务器接收 HTTP 请求,然后通过FastCGI接口传递给本机的php-fpm进程,进行应用层面的处理。 故障现象: 有一天,客户忽然报告系统出问题了,网站访问越来越慢,甚至经常会抛出 HTTP 504 错误。 我们借助浏览器开发者工具可以看到,这个响应耗时长达 60 秒,然后抛出了 504 错误。 初步排查: 后端的3台nginx的负载都比较高,且有一台特别高(都是8核设备,第1台load为7,第2台load 10左右,第3台load超过40) 表项看起来为负载不均衡,但是从LB侧查看日志,转发负载其实是均衡的。 跳过LB,直接访问nginx进行测试,发现高负载的nginx 响应时间多70+秒,返回http 200。(lb的超时时间为60s,所以超过60s会返回504错误) 使用strace工具进行排查主机问题 timeout 5 strace -cp PID 发现gettimeofday() 占用了这个进程高达 97.91% 的运行时间。在短短的 5 秒之内,gettimeofday() 调用次数达到了 2 万多次, 而其他正常的系统调用,比如 poll()、read() 等,只有几十上百次。 也就是说,非业务操作的耗时是业务操作耗时的 50 倍(98%:2%)。 难怪进程这么卡,原来全都花在执行 getimeofday(),也就是收集系统时间数据上了。 故障根因: 客户在设备上安装了性能监控软件New Relic,该软件发起了极为频繁的 gettimeofday() 系统调用。 导致了系统负载高,最终导致响应慢! 解决:卸载软件 客户卸载 New Relic,应用立刻恢复正常。用 strace 再次检查,显示系统调用也恢复正常
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
案例 2:LB 特性和不均衡(LB启用根据源IP进行会话保持,某些IP的访问需求比较多,导致了负载不均衡) ============================================================================================= 案例 2:LB 特性和不均衡(LB启用根据源IP进行会话保持,某些IP的访问需求比较多,导致了负载不均衡) 背景: 还有一个电商客户,也遇到了一次负载不均衡的问题。这是在双十一期间,客户发起了促销活动,随之而来的访问压力的上升也十分明显,而他们的后端服务器也出现了负载不均的现象 架构: 一台 LB 后面接了两台服务器,其中一台的 CPU load 高达 50 以上,也是远远超过了 8 个的 CPU 核数。 另外一台情况要好一些,load 在 10 左右,当请求被分配到这台服务器上的时候,还算勉强可以访问。 客户怀疑:LB 的请求分配做得不均衡,导致其中一台处理不过来。 排查过程: 排查LB日志,发现大部分请求都是访问 /api.php 按 HTTP 请求的源 IP 来分开统计,某些 IP 的访问量远远大于其他 IP。 对 LB 日志做进一步的分析后发现,同样是源 IP 的请求,要么去了 Nginx 1,要么去了 Nginx 2。(因为客户在 LB 上开启了“会话保持”(Session Persistence)) HAProxy的会话保持有两种: 源 IP:凡是源 IP 相同的请求,都去同一个后端服务器。(客户启用的是这一种) Cookie:凡是 HTTP Cookie 值相同的请求,都去同一个后端服务器。 解决: 客户关闭了会话保持,两台后端服务器的负载很快就恢复平衡
《网络排查案例课》22 | 为什么压力测试TPS总是上不去?
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
压力测试的分类;ab工具; ========================================================================================================================= 压力测试的分类: 1.应用的承受能力:这主要在第七层应用层,比如发起了压测,把服务端的 CPU 打到 95% 甚至 100%,观察这时候的请求的 TPS、请求耗时、并发量等等。而这些对于不同的业务场景,又会有不同的侧重点。比如: 对于时间敏感型业务来说,请求耗时(Latency)这个指标就是关键了。 对于经常做秒杀的电商来说,并发处理量 TPS(Transaction Per Second)就是一个核心关注点了。 2.LB 的连接处理能力:这主要在第四层 TCP,看 LB 能最大支持的 TCP 并发连接数。这时候,发起压测的客户端一般会指定比较大的并发数,这样就可以发起尽量多的 TCP 连接。 3.网络的承受能力:这可能主要在第三层 IP 层了,比如测试上行和下行带宽能否跑满、是否有丢包和额外的延迟,等等。特别是对于一些流量比较大的场景,很可能服务端计算能力都还在,但带宽已经不够用了,所以我们要提前发现这些隐患。 --------------------------------------------------------------------------------------------------------- ab 是 Apache Benchmark 的缩写,它的用途就是对 HTTP 服务端发起测试,以获得性能指标(Benchmark)。 ab 本身不是独立安装的,而是在 apache2-utils 工具包里,所以你可以这样来安装它: apt install apache2-utils ab 是一个轻量级的工具,因为相对其他重量级的工具比如 LoadRunner 或者 JMeter 来说,ab 只要一行命令就可以发起压测了 ab -c 100 -n 10000 目标URL -c 100 这个参数,让 ab 发起了 100 个并发的请求, -n 10000 指定了总共发送的请求量。 补充:这里有一个小的注意点。如果目标 URL 只是站点名本身,还是需要在结尾处加上“>/”,要不然 ab 会报这个错误:ab: invalid URL ab -k -c 100 -n 10000 目标URL -k 参数是启用长连接 用下面这条 ab 命令,对一个著名网站发起“压测”,当然我的参数选择的很小,只有 10 个并发,一共 100 次请求,尽量避免打扰到这个网站。我们可以看一下输出: $ ab -c 10 -n 100 https://www.baidu.com/abc This is ApacheBench, Version 2.3 <$Revision: 1843412 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking www.baidu.com (be patient).....done Server Software: Apache Server Hostname: www.baidu.com Server Port: 443 SSL/TLS Protocol: TLSv1.2,ECDHE-RSA-AES128-GCM-SHA256,2048,128 Server Temp Key: ECDH P-256 256 bits TLS Server Name: www.baidu.com Document Path: /abc Document Length: 201 bytes Concurrency Level: 10 Time taken for tests: 9.091 seconds Complete requests: 100 Failed requests: 0 Non-2xx responses: 100 Total transferred: 34600 bytes HTML transferred: 20100 bytes Requests per second: 11.00 [#/sec] (mean) Time per request: 909.051 [ms] (mean) Time per request: 90.905 [ms] (mean, across all concurrent requests) Transfer rate: 3.72 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 141 799 429.9 738 4645 Processing: 19 67 102.0 23 416 Waiting: 17 67 101.8 23 416 Total: 162 866 439.7 796 4666 Percentage of the requests served within a certain time (ms) 50% 796 #第 50 个请求(因为总数是 100 个)的耗时小于等于 796 毫秒,另外 50 个请求大于 796 毫秒。 66% 877 75% 944 80% 1035 90% 1093 95% 1339 98% 1530 99% 4666 100% 4666 (longest request) #耗时最长的那个就是排最后一名的 4666,它的耗时是 4666 毫秒。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
包量PPS(Packet Per Second);sar工具;案例 1:压测 TPS 上不去(到达pps上限) ============================================================================================================================ 一般说到网络性能,我们会讨论的就是带宽、时延、网速等等这些指标。实际上,另外一个性能指标常常在达到带宽极限之前就已经触顶了,它就是包量。 包量是对 PPS(Packet Per Second)的简称,一般用来衡量一台主机的网络处理能力。 一般的包量测试,不是随便什么大小的报文都可以测试,而是普遍使用 64 字节长度的 IP 报文。 包的大小对包量性能的影响也不是很大。 因为,对于网络处理来说,主要的开销在包的头部的处理上,而载荷本身的处理是很快的。 ----------------------------------------------------------------------------------------------------------------------------- 性能工具 sar sar 是 sysstat 工具集的一部分。这个工具集包含了一些很有用的工具,除了 sar,还有 mpstat、iostat 等等。 sar -n DEV 1 10 #查看网卡性能 iostat 1 10 #查看IO性能 mpstat 2 5 #查看CPU性能 sar -n DEV 包量pps,就是体现在两个 pck/s 指标中,一个是 rxpck/s,就是接收方向的包量;一个是 txpck/s,就是发送方向的包量。 ############################################################################################################################## 案例 1:压测 TPS 上不去(到达pps上限) 客户是传统企业转型做电商,有一次准备搞大促。为了确保大促顺利,他们要提前对网站进行压测。 客户用 ab 时指定的具体参数是这样的: ab -k -c 500 -n 100000 http://site.name.com/path 并发数为 500,总次数为 10 万 -k 参数是启用长连接。 压测过程中,客户发现测试端的带宽用到 400Mbps 后,TPS(Transaction Per Second)就上不去了,无论把并发量或者总量的数值进行怎样的调整,TPS 都会维持在一个稳定的数值。 Transfer rate: 52087.82 [Kbytes/sec] received #带宽用到 400Mbps 故障现象: 客户端和服务端都是 1Gbps 的网卡,客户是想压满 100% 的带宽,现在只用到了 40% 的带宽,TPS 就上不去了。 我们就跟客户配合,一边做 ab 压测,一边做了 4 秒钟的 tcpdump 抓包 GET 有 1603 次(倒数第三行),而 RST 有 1982 次(第三行),比 GET 这种 HTTP 请求的次数还更多。也就是说,平摊的话,每次 GET 请求对应了一次以上的 RST。 查看了任一tcp流,发现存在重传,原因为 Retransmission 超时重传 专家信息显示有 184 个 Suprious 重传和 2274 个(超时)重传。 出现 RST 的原因,是客户端在已经没有了这个 TCP 连接的情况下,收到了服务端的 ACK 报文。从现象来看,客户端应该是做了 TIME_WAIT 优化的设置 sar -n DEV 包量pps,sar结果显示两个 pck/s 指标rxpck/s、txpck/s的数值已经在 5 万左右,这个正是当时我们云主机性能的上限。 难怪服务端已经无法提供更高的 TPS 了,因为网络包的处理都来不及做了。 对于客户遇到的这种包量达到上限的情况,我们可以选择的应对办法是这样的: 1.选择更高网络性能的主机,比如硬件的 RSS、软件的 RPS 等特性,都会大幅提升包量处理性能。 2.对服务集群进行水平扩展,也就是在 LB 后面增加服务器,这样 VIP 作为一个整体提供的包,处理能力也就提升了。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
案例 2:LoadRunner 压测发现部分失败(端口资源耗尽);案例 3:压测报 cannot assign requested address 错误(端口资源耗尽);实验:修改net.ipv4.tcp_max_tw_buckets ================================================================================================================================================================= 案例 2:LoadRunner 压测发现部分失败(端口资源耗尽) 另外一个客户,他们没有用 ab 这样的简单工具,而是用了 LoadRunner 这样一个企业级的测试软件。 LoadRunner 的厉害之处在于,不仅可以发起巨大的请求量,而且可以模拟用户的复杂行为,比如登录、浏览、加入购物车等等。这一系列事务有前后状态关系,这就不是简单的 ab 可以做到的了。 故障现象: 测试结果中的小几十个 Failed 和 Error 引起了客户的疑虑。 TCP 连接是基于五元组的。那么对于客户端来说,源 IP、目的 IP、目的端口、协议,这四个元素都不会变化,唯一会变的就是自己的源端口了。 netstat -ant 输出显示:大量的 TCP 连接都在 TIME_WAIT 状态,尤其是源端口已经用到了 65534。所以可以肯定,这次压测中出现失败的原因,就在于源端口耗尽。 解决: 显然我们也变不出更多的端口来。这个时候,我们可以调整压测软件的设置,比如从短连接改成长连接。 这样就可以避免源端口耗尽的情况,因为所有的请求是在长连接里完成的,只要连接池本身设置合理,源端口就不会被用完。 ----------------------------------------------------------------------------------------------- 案例 3:压测报 cannot assign requested address 错误(端口资源耗尽) 测试背景: 这个团队从多台客户端机器向 LB 上的一个 VIP,发送大量的请求,而 LB 的后面就是很多的服务器。 故障现象: 团队在做业务压力测试,结果遇到了一个奇怪的报错:cannot assign requested address。 还没等到这些服务器的负载跑起来,客户端那边提前报错了。这是一个 Go 语言的程序,具体的报错信息如下: https://test.vip/a": dial tcp 10.123.123.12:443: connect: cannot assign requested address http post Post \"https://test.vip/b": dial tcp 10.123.123.12:443: connect: cannot assign requested address retry 1 times 表面上看,就是客户端往 10.123.123.12:443 这个 VIP 发起连接请求(用 connect)然后遇到了报错,也难怪测试团队会找到我们来查看 LB 的问题。 根因: HTTP Keep-alive 没有打开,导致这些 TCP 连接被视作短连接来处理了,也就是一次 HTTP 请求和响应完成后,这条连接就关闭了。 由于发起关闭的是客户端自己,于是这条连接也就进入了 TIME_WAIT 状态。 查看 TIME_WAIT 状态的连接数量 $ netstat -ant |awk '{++a[$6]} END{for (i in a) print i, a[i]}' TIME_WAIT 28231 ss -ant | awk '{++s[$1]}END{for(k in s) print k,s[k]}' TIME-WAIT 28231 可见,处于 TIME_WAIT 状态的连接数接近 3 万个,差不多就是 Linux 的本地动态端口的范围了。 $ cat /proc/sys/net/ipv4/ip_local_port_range 32768 60999 下限是 32768,上限是 60999,范围正好就是 28231,跟 TIME_WAIT 的数量一致,显然也是一次源端口耗尽导致的压测问题。 ------------------------------------------------------------------------------------------------ 实验:修改net.ipv4.tcp_max_tw_buckets 步骤一:安装 Nginx 和 Apache ab。 apt install nginx apt install apache2-utils 步骤二:启动 ab 测试,执行下面的命令。 ab -c 500 -n 10000 http://localhost/ 步骤三:观察 TCP 连接的各种状态的统计数。 ss -ant | awk '{++s[$1]}END{for(k in s) print k,s[k]}' 此时你应该会看到 1 万多个 TIME_WAIT 的连接,然后等待 2 分钟后继续步骤四。 步骤四:修改内核参数 tcp_max_tw_buckets 为 100。 sysctl net.ipv4.tcp_max_tw_buckets=100 步骤五:再次 ab 测试。 ab -c 500 -n 10000 http://localhost/ 步骤六:再次观察 TCP 连接的各种状态的统计数。 ss -ant | awk '{++s[$1]}END{for(k in s) print k,s[k]}'
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
TCP RST;案例 4:压测遇到 connection reset by peer(连接已释放,回复RST) =========================================================================================================================== 在 Wireshark 里看到 TCP RST,往往会觉得它不是一个好的征兆。确实,有时候是 RST 引起了故障,有时候又是网络故障迫使 TCP 用 RST 来结束连接。 无论 RST 是因还是果,它总是跟问题本身逃不脱关系。 大体上,TCP RST 的原因可以分为这么几个大类: 1.找不到相关连接,那么接收端可以放心地直接发送 RST。 2.找到了相关连接,但收到的报文不符合 TCP 规范,那么接收端也可以发送 RST。 3.找到了相关连接,但传输状况恶劣,内核选择及时“止损”,发送 RST。 ----------------------------------------------------------------------------------------------- 案例 4:压测遇到 connection reset by peer(连接已释放,回复RST) 场景: 一个应用团队用 netty http client 去调用一个 VIP 做压测。具体来说是 9 台客户端机器,向同一个 LB VIP 发起大量的请求。 故障现象: 结果遇到了 connection reset by peer。蹊跷的是,这个报错是零零星星出现的。 从图上看,报错数量不超过 50 个,主要集中在 11 点 20 分到 11 点 35 分这个时间段,这正是压测的时段。 因为报错只有 50 来个,这相比于这次压测发起的成千上万的请求来说,是很小的比例了。 初步分析: 如果像之前的例子,真的源端口用尽了,那么接下来一段时间内,这些请求都会因为本地没有源端口可用而宣告失败,也就是报错会是大面积出现,而不会零星出现。 压测期间每个客户端的请求频率是 700TPS,所以 9 台客户端一共会发起 6300TPS 的请求量。 这个问题诡异的地方就是,大部分的请求都能得到正确及时的回复,但是隔了两三分钟,就会出现几次这种 connection reset by peer 的问题。 根因: 这次案例里面的情况,符合"找不到相关连接,那么接收端可以放心地直接发送 RST" LB 上的 VIP 有一个 idle timeout 的设置,如果客户端在一定时限内不发任何报文,那么这条连接将被回收。这个时限是 180 秒,而且回收时不向客户端发送 FIN 或者 RST 报文。 这次的压测客户端框架里,有一个设置值也是关于 idle timeout 的。不过这个值设置的是 360 秒。 问题解决: 把客户端的 Idle timeout 参数,从原先的 360 秒,改成比 LB 的 180 秒更低的值就好了。这次改到了 120 秒,RST 就完全消失了。
111111111111