141

什么?-你的服务竟然被探活搞死了? - haolujun

 6 years ago
source link: https://www.cnblogs.com/haolujun/p/8086557.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今年开发了好多服务,着实踩了不少的坑。这不,分分钟就被探活搞的死去活來。这里我把这些经验分享给大家,避免大家再继续犯这种错误。

通用tcp探活原理

其实,探活原理特别简单,只要稍懂计算机网络就能够理解。

  • 检测端 发起tcp三次握手,建立新连接,连接建立成功代表服务活着,建立失败代表服务死了,之后发送rst包主动断开连接。
  • 被检测端 接受检测端发送的三次握手手建立连接,当接收到检测端的rst包后,被检测端断开连接,释放资源。

但是假如rst包丢失了,会发生什么样的情况呢?我们以thrift为例讲述一下之后所发生的事情,如下图所示。

261319-20171226001354994-427962992.png

经过三次握手后,服务端已经建立一个新的数据连接,并把连接丢给工作线程。服务端的工作线程监听连接,并准备接收请求(毕竟,任何一个thrift服务都是先接收请求数据,之后进行计算,最后返回响应数据,所以当新连接建立后,thrift默认首先读取连接上的数据)。

检测端建立连接成功后,认为下游服务还活着,所以立刻发送rst包(检测端认为这个rst包一定会到达被检测服务),并且释放连接资源。

但是如果网络状态不好,rst包丢失,那么服务端(被检测端)的工作线程就会无限制的hang在读取连接数据上(因为检测端已经单方面认为连接断开,不会写任何数据,所以服务端也读不到任何数据)。如果多丢几个rst包,那可以预期无论你有多少thrift工作线程,都将会hang死。

此时真正调用服务的客户端的请求也无限制的hang住,因为这些请求得不到thrift工作线程的处理。

最最可怕的是:由于探活只是单纯的建立连接而并不发送或者接受额外数据,并且thrift服务有单独的线程进行accept,这导致了连接建立每次都成功,但实际上服务已经没有了计算能力。

最后的结局就是:整个系统流量突然降低,下游接收不到请求(以为是上游调用的锅),上游发出的请求得不到响应(认为是下游服务的锅),并且没有报警短信发出(以为是运维的锅),对排查问题造成很大的困扰。

出现这种问题时有一个非常明显的现象:即使在系统流量为0的情况下,服务端也会不断有新连接建立并处于Establish状态(这是因为周期性的探活导致的),并且服务端日志不滚动,客户端请求无响应。如果你的系统有这种情况,那么多半和探活有关。

我当时遇到的情况比较糟糕,因为之前一段时间rst丢包率特别低,平均几天丢一个rst包,所以服务hang死造成的影响并不大,只要抽空重新启动服务即可。可是突然某一天,单台服务的rst丢包数达到了秒级:隔几秒丢一个rst包,导致我的服务短时间内全部hang死。

HTTP探活原理

在实际工作中,对于HTTP服务的探活通常也采取TCP探活原理,毕竟HTTP建立在TCP服务之上,所以TCP探活同样适用于HTTP服务。很多HTTP服务实现原理和thrift大同小异,所以当TCP探活的rst包丢失后,HTTP服务同样会hang在读取连接数据。

为了避免rst包丢失,HTTP服务通常使用HTTP探活请求:探测端对被探测端发送特定的HTTP请求,并且验证被探测端的响应数据是否符合预期。如果rst包丢失,服务端连接最差的情况下会处于TIME_WAIT状态,经过2倍超时时间后服务端会主动关闭连接释放资源,这样服务端的工作线程不会长时间的被占用,相比于用TCP探活要好的多。HTTP探活原理如下图所示。

261319-20171226001500478-255501777.png

此时rst包虽然丢失,但是由于探测使用的HTTP请求,那么整个请求的交互就会完成(探测端会等待服务端的响应数据,所以能够确保服务端能够完成这个响应),服务端发送完响应数据后会主动关闭连接,发送FIN包(一般的HTTP服务框架默认都会这么做),服务端连接处于TIME_WAIT状态,经过一段时间后连接释放,不会长时间占用服务端资源。

对于HTTP服务,我们需要提供一个不耗费cpu和内存的调用接口,这个接口只是为了满足HTTP探活请求调用,如果探活接口死了,可以认为这个服务也死了。

如何确保不被探活搞死?

我们不能保证网络时时刻刻正常rst包不丢失,所以我们只能改造服务使得其更健壮。
正确解决方案如下:

  • 服务端加入读超时时间。当rst包丢失导致服务端线程hang在读取上,超过一定时间后服务端线程会主动断开连接,释放资源,从而确保服务端线程能够服务于其它请求。
  • 客户端请求服务端加入超时限制并进行重试。当一个服务hang死,导致连接其上的客户端也hang住后,超过一段时间后,客户端主动断开连接并且尝试请求其它可用服务。

现在我们清楚探活问题的来龙去脉,那么我想问:你的服务加入超时时间了么?你的服务会被探活搞死么?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK