在大型互联网项目中,服务、机器和网络的故障是很常见的。而由于一般使用普通服务器,硬件层面也没有什么可靠的容错机制,必须由软件应用层面予以解决。

这是一个典型的分层架构,PHP项目部署在多机房,并且对后端多个Services发起串行调用,这些services分属不同产品线,也都是分布式部署的。

以PHP项目的视角来看,网络交互的故障可能发生在service应用层、服务器、网络。service应用层面的故障可能由于对方代码、部署等问题造成,如果是功能性问题,我们是通过自动或手工的优雅降级解决。这里主要关注由于对方故障,导致的网络通信细节。

另外,需要注意的是,当前的多层架构,一般都引入了负载均衡中间层,或者zookeeper之类的可用性保障。zk不在本文讨论范围,如果使用中间层,在网络故障处理方面,可以简单地把它当成service。

以下的client端如无特殊说明,指PHP进程。

连接建立后的故障处理

service进程异常退出

如果仅是进程异常崩溃,所在服务器仍然可用的话,服务器内核会自动给所有已建立连接的peer端发送FIN分节,走类似正常关闭4步的流程。client端内核会响应以ACK,其read会读到EOF,一般会关闭连接以发送FIN,并等待ACK。

可以看到,如果client端没有等待到期望数据格式的响应,会摘除故障节点并retry或降级容错的话,那对client的业务流程是没有影响的。在retry的处理方式里,最多就是好使double了,也没有太大性能影响。

service服务器崩溃

假设这时的服务器崩溃,导致无法发出FIN分节。

如果client此时在write,是无法收到ACK的,内核会不断重试直至超时,可能有数分钟之久,然后返回ENETUNREACH之类的错误。

如果client此时在read,而且没有设置超时时间的话,那就永久block了。

所以,需要在PHP代码里合理的设置超时时间,例如通过stream_set_timeout等方法。

不论是否设置超时,可以看到这种服务器崩溃,对性能的影响还是比较大的,必须等到tcp或应用层的超时expire了之后,才可以继续下面的流程。

网络拥塞或路由器、交换机故障

这种情况与服务器崩溃的原理和影响都差不多。

但假如网络只是拥塞还不是完全不可用,而且client端又有超时、重试和故障摘除机制的话,可能影响更坏。想象下client端如果发现到某个service节点不可达了,它可能会重试几次,如果还不行就摘除并且切换到其他节点去,保障后面的交互是顺畅的。但假如内核层面或者应用层面的重试是断断续续成功的呢?那故障节点or网络路径还是会对外提供服务,却是质量受损的服务!

我厂hadoop client端里就是类似问题,是通过设置期望的网络读写速率来予以降低影响的。

连接建立过程中的故障处理

service服务器正常,但进程未启动

client发出SYN后,会很快接收到RST,从而得知故障。只要应用层有重试机制就没有影响。

service服务器故障或网络故障

client发出SYN后,很可能接收到的是路由不可达或主机故障,但协议规定的有重试机制,所以tcp层会持续重发几个SYN才会放弃,这时connect或fsockopen之类才会返回错误。所以,对服务质量有损,需要合理设置connect超时时间。

统筹故障处理

从上面可以看到,如果无法正常FIN或ACK,对服务质量是有损的,但从tcp协议层面又无法解决。而当前的多层服务器部署架构,经常是网状的,可以认为,故障服务、节点、链路会对上层多个节点和请求造成影响。

我们将client端的多个节点视为横向,多个请求视为纵向,期望是横纵都尽量降低影响。

所以,采用了:

  1. 单节点利用故障时间窗口,自动摘除和恢复底层故障节点
  2. 集群利用故障消息流,自动摘除底层故障service

这里是思路是,如果故障达到了阈值,那就从横纵两个方向扩散故障消息,告诉其他client端或其他请求不要使用故障服务。

Leave a Reply