为什么应用各项指标正常, 却不响应请求了?
收到告警, 发现有个应用里面有 4 台服务器(虚拟,非真实物理服务器)挂了. 幸亏这个服务后面的服务器足够多, 并没有什么影响. 同时去查看这个应用的各项指标, 都是正常. 即使显示挂了的这 4 台, CPU, 内存, tomcat 服务线程, 磁盘等各方面也是正常, 也就是说这 4 台服务器还在不断的主动更新自己的状态, 并且状态良好, 那么为什么检测告警这边认为他们挂了呢?
检测告警服务为什么认为这 4 台服务器挂了?
检测告警这边每分钟都在不断主动检测每台服务器的健康状况, 类似下面的 URL
http://host1.tianxiaohui.com:8080/status/health
每次访问, 总是不能建立连接, 导致检测告警这边认为这几台服务器挂了. 手工测试了一下这个 URL, 确实不能建立连接. 尝试任何其他合理的 URL, 总是不能建立连接.
这个服务根据是否管理端口,是否承接不同的来源的请求等, 实际上开了 4 个 http 端口, 分别是: 8080, 8081, 8082, 8083. 分别对 4 个端口测试, 发现只有 8081 端口还能正常访问. 服务检测只测试 8080, 所以它认为这 4 台服务器挂了.
为什么不能建立 http 连接?
为什么其它 3 个端口不能在连接 http 连接? 我们登上其中 1 台服务器, 查看连接状况, 发现特别多的 CLOSE_WAIT 状态的连接. 表明这些连接对方已经发了 FIN 准备断开, 可是我们服务器的进程这边还没发 FIN, 没有断开连接. 再次查看监听的端口方面, 看到如下的情况:
$ ss -l -t -n
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 101 100 *:8080 *:*
LISTEN 0 100 *:8081 *:*
LISTEN 101 100 *:8082 *:*
LISTEN 101 100 *:8083 *:*
上面只展示了我们监听的 4 个端口部分, 可以看到我们还在监听(listen) 4 个端口(8080, 8081,8082, 8083).
Send-Q 在 listen 的行表示当我们 open 这个 socket 的时候, 设置的 backlog 大小(参看 java.net.ServerSocket的构造函数). Java 默认值是 50, 可以自己设置, Tomcat 默认设置的值是 100, 也就是我们上面看到的值 100.
Recv-Q 表示 listen backlog 现在的连接数, 它表示 Linux 已经完成了 3 次握手, 从 syn backlog 移到 listen backlog, 可是应用程序还没有取走( accept()), 只能待在 listen backlog. 一旦应用程序(Tomcat)接受这个请求(accept()), 那么这个请求就会从 listen backlog 取走, 进入应用程序待处理队列, 同时 listen backlog 释放出一个位置. 通常你看到的这列的值都是 0, 因为应用处理的足够快, 如果是一个稍大值, 就要担心你的应用了.
从上面的结果看出, 我们的应用有问题, 导致 Recv-Q 满了. 通常情况下, Tomcat 处理 I/O 的线程会接收这个请求, 然后把这个请求放到自己的任务队列里, 然后 tomcat worker 线程会到任务队列去取任务执行. 如果这个 listen backlog 满了, 我们就不能再建立新的 tcp 连接, 就是我们遇到的这种情况. 那么我们的 Tomcat 处理 I/O 的线程到底怎么了?
Tomcat 处理 I/O 的线程怎么了?
当时为了做 RCA 分析, 又不影响服务, 我们选择了做 heap dump, 并且立即重启了服务. 从后面的 heap dump 可以看到, 我们的 8080, 8082, 8083 端口的 Tomcat I/O Acceptor 处理线程不见了, 只有 8081 端口的 I/O Acceptor 线程还活着.
不知道什么原因, 另外 3 个 Tomcat I/O 处理线程 Crash 了. 从其它指标看之前有段时间这 4 台机器发生过OOM, 也许相关, 可是相关的 log 已经被覆盖了.
关于 Linux 上 syn backlog, listen backlog, tomcat 处理队列, 参看: http://www.tianxiaohui.com/index.php/default/Tomcat-7-NIO-handling-request-on-Linux.html