SRE重案调查组 第二集 | 挖掘应用处理变慢的“真相”

这是本人发表在 eBay 微信公众号 eBay技术荟 上的 一系列文章, 原文地址如下. 编辑非常给力, 请查看原文, 这里只是供搜索引擎访问.
https://mp.weixin.qq.com/s/Sh7ddc-3mGXWCGppi50EHg

如果原文由于某种原因不存在了, 请查看这个pdf 版本:SRE重案调查组 第二集 _ 挖掘应用处理变慢的“真相”.pdf

导读

eBay SRE部门经常处理生产环境中各种各样的疑难杂症,此次探究的是应用处理变慢问题。从监控系统入手,到找出根本原因是锁形成的顺序队列,不仅完美解决该问题,还为本部门积累了一笔宝贵的经验。

- 阅读剩余部分 -

HTTP 2 的特性

从 HTTP 2 in action 这本书看的, 发现这个网站总结的不错: https://tools.keycdn.com/http2-test, 就摘抄过来了. 顺便测试了一下 (20191104), taobao, ebay 支持 Http2, jd 和 baidu 主页都不支持 HTTP2

  1. HTTP/2 is binary, instead of textual.
  2. It is fully multiplexed, sending multiple requests in parallel over a single TCP connection.
  3. It uses header compression HPACK to reduce overhead.
  4. It allows servers to “push” responses proactively into client caches instead of waiting for a new request for each resource
  5. It uses the new ALPN extension which allows for faster-encrypted connections since the application protocol is determined during the initial connection.
  6. It reduces additional round trip times (RTT), making your website load faster without any optimization.
  7. Domain sharding and asset concatenation is no longer needed with HTTP/2.
  8. Flow control
  9. Stream prioritization

如何判断一个网站是不是支持 HTTP2? 使用最新的 curl 命令:

LM-SHC-16507776:tmp xiatian$ curl -vvv -s  --http2 https://www.jd.com:443/ 2>&1 | grep ALPN
* ALPN, offering h2
* ALPN, offering http/1.1
* ALPN, server accepted to use http/1.1
LM-SHC-16507776:tmp xiatian$ curl -vvv -s  --http2 https://www.ebay.com:443/ 2>&1 | grep ALPN
* ALPN, offering h2
* ALPN, offering http/1.1
* ALPN, server accepted to use h2

上面的输出中 baidu 首页不支持, ebay 首页支持.


从下面这个问答看:
在 Chrome 里面, 对于 HTTP 2 同一个 origin 不同的 tab 之间共享一个 tcp 连接.
对于 HTTP 1.1:

  1. HTTP/1.1 sockets are stored in a pool when they are not active. A request from any tab may end up using any socket in the pool;
  2. Incognito tabs use their own independent socket pool (All incognito tabs for a profile share just one socket pool, though);
  3. Isolated apps use their own socket pools.
  4. Different profiles and guest mode use their own socket pools.
  5. Global requests not tied to a profile use their own socket pool;

https://stackoverflow.com/questions/51535819/in-http-1-1-are-connections-shared-across-multiple-chrome-tabs-to-the-same-origi
https://groups.google.com/a/chromium.org/forum/#!topic/chromium-dev/S3TEvmRWcNE

netcat(nc) 的各种操作

  1. 扫描端口

    nc -vzu 192.168.0.1 80-90
    nc -v -z www.tianxiaohui.com 90-100
    nc -v -n -z -w 1 192.168.1.2 1-1000
  2. 从客户端传文件到服务端
    服务端先起来

    nc -l 8080 > file.txt
    nc -v remote_server 8080 > localfile.txt
    带压缩:
    nc -l -p 1234 | uncompress -c | tar xvfp -
    tar cfp - /some/dir | compress -c | nc -w 3 [destination]:1234
  3. 从服务端传文件到客户端

    nc -4 -v -l 7070 < myfile.txt
    客户端可用浏览器或者 nc 命令
    nc -v www.tianxiaohui.com 7070
  4. 作为代理

    单向代理: nc -l 12345 | nc www.google.com 80
    双向:
    mkfifo backpipe
    nc -l 12345 0<backpipe | nc www.google.com 80 1>backpipe
  5. 让任何命令作为网络服务器 (网络后门)

    服务端: nc -4 -l -p 1234 -e /bin/sh
    客户端: nc remote_server 1234
    ls -lah

    如果遇到:
    close: Bad file descriptor
    加上 -4 参数.

使用 telnet, nc, openssl 浏览网站

LM-SHC-16507776:~ xiatian$ telnet www.tianxiaohui.com 80
Trying 156.236.115.130...
Connected to www.tianxiaohui.com.
Escape character is '^]'.

GET /about.html HTTP/1.1
Host: www.tianxiaohui.com

HTTP/1.1 400 Bad Request
Date: Sat, 02 Nov 2019 03:18:30 GMT
Server: Apache
Accept-Ranges: bytes
Connection: close
Content-Type: text/html


printf "GET /index.html HTTP/1.0\r\nHost: www.tianxiaohui.com\r\n\r\n" | nc www.tianxiaohui.com 80

使用 openssl 浏览 https 的
LM-SHC-16507776:~ xiatian$ openssl

OpenSSL> s_client -crlf -connect www.bing.com:443 -quiet

depth=2 C = IE, O = Baltimore, OU = CyberTrust, CN = Baltimore CyberTrust Root
verify return:1
depth=1 C = US, ST = Washington, L = Redmond, O = Microsoft Corporation, OU = Microsoft IT, CN = Microsoft IT TLS CA 2
verify return:1
depth=0 CN = www.bing.com
verify return:1

GET / HTTP/1.1
Host:www.bing.com

HTTP/1.1 302
Cache-Control: private
Content-Length: 137
Content-Type: text/html; charset=utf-8
Location: https://cn.bing.com/
Date: Sat, 02 Nov 2019 03:11:59 GMT

Object moved
Object moved to https://cn.bing.com.

GET / HTTP/1.1
Host:cn.bing.com

HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Length: 114805

Tomcat 7 NIO handling request on Linux

围绕这个图, 做几点解说(左边的虚线框表示 Tomcat):
Tomcat_Request_handling.png

  1. 从操作系统层面来说, 收到 TCP 的 SYN 之后, 放到 TCP SYN backlog 队列, 然后发送 (syn + ack) 包, 等待对方的 ack 包;
  2. SYN backlog 长度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 设置, 还有文章说最终值有几个因素共同决定, 不过在 Linux Kernel 4.3 之后,这个 syn backlog 不在由 net.ipv4.tcp_max_syn_backlog 决定, 而是由 net.core.somaxconn 决定

    eric@host:~$ sysctl net.core.somaxconn
    net.core.somaxconn = 4096
    eric@host:~$ sysctl net.ipv4.tcp_max_syn_backlog
    net.ipv4.tcp_max_syn_backlog = 4096
  3. 如何查看一个监听端口的 SYN backlog 当前的队列长度?
    没有直接看当前长度的命令, 不过可以自己手工计算:

    # 查看连到当前 host 8080 端口上并且处于 sync-recv 状态的连接
    eric@host:~$ ss -n state syn-recv sport = :8080
  4. 如果 SYN backlog 已经满了, 那么操作系统层面会自动丢弃接收到的 syn 包, 那么客户端以为对端没收到, 会继续尝试.
  5. 当操作系统回复 syn+ack 之后, 对端返回 ack, 完成三次包握手, 这时, 这个连接变成 Established 的状态, 该连接从 syn backlog 进入 listen backlog;
  6. listen backlog 的长度由 listen 系统调用的参数决定, 在 Java 里由 ServerSocket(int port, int backlog)的构造函数的 backlog 参数决定. java 默认是 50. Tomcat 7 里面由 Connector 参数 acceptCount 决定, 默认是 100;
  7. 如果这个 listen backlog 满了, OS 收到对端为了完成握手的 ack 时, 选择 ignore 这个 ack, 那么客户端会以为丢包, 会继续尝试发送 ack;
  8. 如何查看一个监听端口的 listen backlog 队列的当前长度? Linux -> ss -ltn 看 Send-Q 列
    xiatian_lvsbastion200_____ssh_.png
  9. Tomcat 7 NIO 处理这些 socket 连接有 2 类 background 线程, 分别是 Acceptor 线程和 Poller 线程;
  10. Acceptor 线程 通过 serverSock.accept() 方法接受新连接, 就是 listen backlog 里面已经建立的连接;
  11. Acceptor 接受之后, 就把这个接受的 socket 送给 Poller 线程去处理;
  12. Poller 线程把 socket 封装之后放入 TaskQueue;
  13. Poller 线程还负责 从 socket 的 Channel 搬运数据到对应的 Buffer, 也就是负责 NIO 的 Selector 的处理任务;
  14. Acceptor 线程和 Poller 线程的数量分别由 Tomcat Connector 的 acceptorThreadCount 和 pollerThreadCount 决定;
  15. Tomcat 的 TaskQueue 是一个 LinkedBlockingQueue;
  16. TaskQueue队列里的 Task 由 Executor 里的线程读取并执行;
  17. TaskQueue队列里的 Task 由 Poller 放入;
  18. TaskQueue的长度默认是 Int 的最大值, 不过基本不会放这么多, 如果TaskQueue的任务和在处理的超过 maxConnection 的值, Poller 就会决绝新的任务;

参考: https://blog.cloudflare.com/syn-packet-handling-in-the-wild/