原文: http://mp.weixin.qq.com/s?__biz=MzI4MjA4ODU0Ng==&mid=402163560&idx=1&sn=5269044286ce1d142cca1b5fed3efab1&3rd=MzA3MDU4NTYzMw==&scene=6#rd

写的太好了,忍不住就转了。
2016-01-19 Anthon Liu FangTalk

抱歉迟了一天发文,本来想昨天出货的。结果又一次被证明我的RPWT,关键时刻生产环境挂了...

原来准备写“分布式系统里的PAC哲学”的,不过这次处理的生产环境的问题还是很有代表性,同时又想起林涛(对!就是那个有百万女朋友的林涛!!!)提过的从一个实际案例出发去写文章的想法,因此就决定从头来写一下这次在线支持的来龙去脉。 (文章有点长,但希望整个思路能对您能有启发)

环境简述

要说清楚问题,先要简单说下生产环境的网络拓扑 (毕竟是个网络问题对吧)

看,挺简单的对吧,一个OpenResty做SLB承受客户端请求,后向代理到几台应用服务器。由于业务要求,必须要同步调用第三方运营商的接口并返回结果到客户端。

怎么“挂”了

深夜接到某妹子电话本该是激动人心的事,但是奈何怎么都高兴不起来,因为来电是来告诉我环境挂了。赶紧问清楚,回答说是一开始响应很慢,后来就彻底拿不到数据了。

好吧,自己摸出手机试下,果然.... 此时夜里11点。

第一反应 (请注意,这里开始是我的排错思路)

从开始打开电脑到电脑点亮的时间里我已经想好了一个初步的排错检查思路, 既然是拿不到数据,那有哪些可能呢?

  • 是不是特例还是所有情况下的数据都获取不到?

  • 是不是网络断了(比如某厂的光缆又断了?)

  • 是不是服务停了 (Sig 11?OOM?或者core dump)

  • 是不是应用服务器都CPU 100%了?

  • 看看监控系统有没有报警? (当然得有对吧)

  • 看看DB是不是被人删了?(进过某旅游网站的事件后,这总也是一种可能行对吧)

好,因为我们有云监控,看了下

  • SLB的心跳还活着,排除网络问题

  • 所有服务器的CPU/Memory/IO都还正常没有峰值

  • 关键进程还在

  • DB也还健在(还好 还好)

开始检查

既然初步排除上述的问题,那下一步基本就是SSH到服务器上去看情况了。 自然从网络开始,这里要想说给很多在做或者即将做在线生产环境支持的小伙伴说的第一句话: “先听听操作系统的声音,让操作系统来告诉你问题在哪”。 不论是windows和Linux都提供了一堆小工具小命令,在过度依赖安装第三方工具前请先看看是否操作系统自带的工具已经不够支撑你了。

好,第一个检查就是本机的网络连接

netstat -anop tcp

结果

.......此处省略100多行.....

我擦,close_wait又让我撞见了. 看了几台应用服务器都是上百个close_wait. (加起来有近千个close_wait, 发财了)。

网上有太多文章描述这个东西了所以我不会展开去解释,就浓缩成以下几点,大家参考这图理解

  • close_wait 是TCP关闭连接过程中的一个正常状态

  • close_wait只会发生在关闭链接的那一端(各位亲,请不要把图里的client/server和项目里的客户端服务器端混淆)

  • close_wait除非你杀进程,close_wait是不会自动消失的。当然不消失意味着占着资源呢,这里就是占着FD。

看到这里基本拿不到数据的原因之一找到了,大量的close_wait. 我之前项目也见过有的开发见到这种情况的直觉反应就是重启大法,其实也不能算这个做法有错,毕竟这个时候服务当了,客户疯了,夜已深了,你想休息了。 但,这样真的对吗?

“停” 先别急着重启

如果你这时候重启了,的确立竿见影解决了当前问题,但你却失去真正解决问题的机会。这就是我想说的第二句话: “保留一下现场,不是所有问题的根源都能从日志里找得到的”。 close_wait 绝对就是这类问题,如果你是一位有过类似经历的开发或者DevOps,你到现在应该有了下面2个疑问:

  1. 为啥一台机器区区几百个close_wait就导致不可继续访问?不合理啊,一台机器不是号称最大可以开到65535个端口吗?

  2. 为啥明明有多个服务器承载,却几乎同时出了close_wait? 又为什么同时不能再服务?那要SLB还有啥用呢

好,这也是我当时的问题,让我们继续往下分析:

  • 先理顺出现close_wait的链接流向

前面说过close_wait是关闭连接过程中的正常状态,但是正常情况下close_wait的状态很快就会转换所以很难被捕捉到。所以如果你能发现大批量的close_wait基本可以确定是出问题了。那第一个要确定的自然是连接的流向,判断依据很简单(还是netstat -anop tcp)


命令返回里有一栏Foreign Address,这个就是代表对方的IP地址,这个时候再结合上面那张TCP的握手图,我们就知道是哪台机器和你连接着但是却主动关闭了连接。

  • 根据项目数据请求流向还原可能的场景

知道了是哪台IP,那接下来就可以根据项目实际情况还原连接的场景。在我这里所有的close_wait都发生在和SLB的连接上。因此说明,是SLB主动关闭了连接但是多台应用服务器都没有响应ack 导致了close_wait. 只是这样够吗? 明显不够。继续

SLB 作为负载均衡,基本没有业务逻辑,那它会主动关闭连接的场景有哪些?

  • 进程退出(正常或者非正常)

  • TCP 连接超时

这2个情况很好判断而且大多数情况下是第二种(我遇见的也是),如果你还记得我文章一开始的环境结构图,我想基本可以得出以下结论是:

由于调用第三方的请求API太慢而导致SLB这边请求超时引起的SLB关闭了连接.

那解决方案也很容易就有了:

  • 加大SLB到应用服务器的连接超时时间

  • 在调用第三方的时候采用异步请求

完了吗? (我怎么那么啰嗦。。。)

“再等等” 还有问题没被回答

我们还有两个问题没回答:

  1. 为啥一台机器区区几百个close_wait就导致不可继续访问?不合理啊,一台机器不是号称最大可以开到65535个端口吗?

  2. 为啥明明有多个服务器承载,却几乎同时出了close_wait? 又为什么同时不能再服务?那要SLB还有啥用呢

是啊,解释了为什么出close_wait, 但并不能解释这2个问题。好吧,既然找到了第一层原因,就先重启服务让服务可以用吧。剩下的我们可以用个简单的原型代码模拟一下。此时我的目光回到了我们用的Tornado上面来,“当你有问题解释不了的时候,你还没有发现正真的问题”

Tornado是一个高性能异步非阻塞的HTTP 服务器(还不明白这个啥意思的可以看 “从韩梅梅和林涛的故事中,学习一下I/O模型 ” 这篇文章,生动!!!),其核心类就是IOLoop,默认都是用HttpServer单进程单线程的方式启动 (Tornado的process.fork_processes 也支持多进程方式,但官方并不建议)。我们还是通过图来大概说下

IOLoop干了啥:

  • 维护每个listen socket注册的fd;

  • 当listen socket可读时回调_handle_events处理客户端请求,这里面的实现就是基于epoll 模型

好,现在我们知道:

  1. Tornado是单进程启动的服务,所以IOLoop也就一个实例在监听并轮询

  2. IOLoop在监听端口,当对应的fd ready时会回调注册的handler去处理请求,这里的handler就是我们写业务逻辑的RequestHandler

  3. 如果我们启用了Tornado的  @tornado.gen.coroutine,那理论上一个请求很慢不会影响其他的请求,那一定是代码什么地方错了。

进而查看实现代码,才真相大白,虽然我们用了 @tornado.gen.coroutine 和yield,但是在向第三方请求时用的是urllib2 库。这是一个彻头彻尾的同步库,人家就不支持AIO(Tornado 有自己AsyncHTTPClient支持AIO).

由此让我们来总结下原因:

  1. Tornado是单进程启动的服务,所以IOLoop也就一个实例在监听并轮询

  2. Tornado在bind每个socket的时候有默认的链接队列(也叫backlog)为128个

  3. 由于代码错误,我们使用了同步库urllib2 做第三方请求,导致访问第三方的时候当前RequestHandler是同步的(yield不起作用),因此当IOLoop回调这个RequestHandler时会等待它返回

  4. 第三方接口真的不快!

最后来回答这两个问题:

1. 为啥一台机器区区几百个close_wait就导致不可继续访问?不合理啊,一台机器不是号称最大可以开到65535个端口吗?

[回答]: 由于原因#4和#3所以导致整个IOLoop慢了,进而因为#2 导致很多请求堆积,也就是说很多请求在被真正处理前已经在backlog里等了一会了。导致了SLB这端的链接批量的超时,同时又由于close_wait状态不会自动消失,导致最终无法再这个端口上创建新的链接引起了停止服务

2. 为啥明明有多个服务器承载,却几乎同时出了close_wait? 又为什么同时不能再服务?那要SLB还有啥用呢

[回答]: 有了上一个答案,结合SLB的特性,这个也就很好解释。这就是所谓的洪水蔓延,当SLB发现下面的一个节点不可用时会吧请求routing到其他可用节点上,导致了其他节点压力增大。也犹豫相同原因,加速了其他节点出现close_wait.

写在最后

花了2个小时写完这文章,意图并不是在重复去解释什么是close_wait. 只是希望藉由一次生产环境的实战给需要做支持的小伙伴们一个解决问题思路的参照。一次支持也许是快速且短暂的,解决的快不代表你真的就理解了里面的原因,有时候新的知识恰恰就藏在一次次的环境问题当中。

又见CLOSE_WAIT的更多相关文章

  1. 浅谈CLOSE_WAIT

    浅谈CLOSE_WAIT 发表于2016-01-19 TCP 有很多连接状态,每一个都够聊十块钱儿的,比如我们以前讨论过 TIME_WAIT 和 FIN_WAIT1,最近时不时听人提起 CLOSE_W ...

  2. HttpClient 专题

    HttpClient is a HTTP/1.1 compliant HTTP agent implementation based on HttpCore. It also provides reu ...

  3. CLOSE_WAIT问题-TCP

    环境简述 要说清楚问题,先要简单说下生产环境的网络拓扑(毕竟是个网络问题对吧) 看,挺简单的对吧,一个OpenResty做SLB承受客户端请求,反响代理到几台应用服务器.由于业务要求,必须要同步调用第 ...

  4. 【服务器】一次对Close_Wait 状态故障的排查经历

    最近接连听说一台线上服务器总是不响应客户端请求. 登录服务器后查询iis状态,发现应用程序池状态变为已停止. 按经验想,重启后应该就ok,第一次遇到也确实起了作用,当时完全没在意,以为是其他人无意把服 ...

  5. pipe 导致的 CLOSE_WAIT :: Utop's Blog

    历时一周总算把导致服务大量 CLOSE_WAIT 的原因给找到了.打印任务调用栈果然的必备手段啊! 问题描述 Python 服务 A,用于接收心跳包确认其他服务是否存活.其他服务每 5 分钟向 A 发 ...

  6. 活久见!TCP两次挥手,你见过吗?那四次握手呢?

    活久见!TCP两次挥手,你见过吗?那四次握手呢? 文章持续更新,可以微信搜一搜「小白debug」第一时间阅读,回复[教程]获golang免费视频教程.本文已经收录在GitHub https://git ...

  7. Tomcat一个BUG造成CLOSE_WAIT

    之前应该提过,我们线上架构整体重新架设了,应用层面使用的是Spring Boot,前段日子因为一些第三方的原因,略有些匆忙的提前开始线上的内测了.然后运维发现了个问题,服务器的HTTPS端口有大量的C ...

  8. [Nginx笔记]关于线上环境CLOSE_WAIT和TIME_WAIT过高

    运维的同学和Team里面的一个同学分别遇到过Nginx在线上环境使用中会遇到TIME_WAIT过高或者CLOSE_WAIT过高的状态 先从原因分析一下为什么,问题就迎刃而解了. 首先是TIME_WAI ...

  9. Material Design Reveal effect(揭示效果) 你可能见过但是叫不出名字的小效果

    Material Design Reveal effect(揭示效果) 你可能见过但是叫不出名字的小效果 前言: 每次写之前都会来一段(废)话.{心塞...} Google Play首页两个tab背景 ...

随机推荐

  1. 二维码生成(QRCode.js)

    什么是 QRCode.js? QRCode.js 是一个用于生成二维码的 JavaScript 库.主要是通过获取 DOM 的标签,再通过 HTML5 Canvas 绘制而成,不依赖任何库. 基本用法 ...

  2. React最佳实践(1)

    React最佳实践不敢妄谈,但最差实践非知乎莫属. 旧版知乎看起来土了点,但体验流畅,起码用起来舒服. 新版知乎看起来UI现代化,技术实现上采用了React,但是可能因为知乎缺钱,请不起高水平的前端工 ...

  3. Redis Sentinel 集群安装 step by step

    一. 准备材料 服务器 IP address 操作系统 位数 Redis 版本   CNT06CAH05 192.168.3.47 CentOS 6.5 x64 Redis-3.2.6 sentine ...

  4. java 整型数据转换为小数类型 BigDecimal 装换为Double

    A,B为String类型 ,A-B=C BigDecimal A=(BigDecimal) map.get("A"); BigDecimal B=(BigDecimal) map. ...

  5. React基本语法

    React 一.导入     0.局部安装 react 和 react-dom         npm install --save-dev react react-dom       1.react ...

  6. Spring : JPA的单独使用

    title: 如何单独使用spring data jpa 引用pom文件: <dependency> <groupId>org.springframework.data< ...

  7. php 计算两个文件的相对路径

    <?php /** * 计算两个文件的相对路径 */ function relative_path($path1, $path2) { $arr1 = explode('/', dirname( ...

  8. 搭建Jupyter Notebook服务器

    昨天发了Jupyter的使用,补一篇Jupyter服务器的搭建~ 一.搭建Jupyter 1.安装 使用virtualenv建虚拟环境.在虚拟环境中安装jupyter.matplotlib等等需要的库 ...

  9. 【C】switch-case里面,加或不加break的区别

    int test; test = ; switch(test) { : test++; printf("value = 0"); // 打印printf,后续没有break代码,系 ...

  10. AS5600磁编码器开发记录

    AS5600使用简介--(程序员版) -----------------本文由"智御电子"提供,同时提供范例教程,以便电子爱好者交流学习.---------------- 前言: ...