1. 事故的发生
服务调用场景和发生的事件如下图所示,红色表示服务不可用.
服务A
和服务B
都是内部服务,服务C_*
为不同运营商提供的服务,遵循一样的协议。
某一天,突然发现所有服务A
调用服务B
的请求都被熔断了。去服务器上看下,发现服务B
依然运行。日志不再打印,最近的日志都是调用服务C_3
请求超时。尝试了下重启,2,3分钟后服务又处于不可用状态。
1.1 查找原因
第一时间想到了发生了死锁,立刻使用jstack
查看一下,(忘记截图保存资料了)。
发现是RestTemplate
里有线程处于wait
状态,联想到爆出的服务C_3
服务处于宕机状态,立刻想到了httpClient
里配置的最大连接被占满,而且没有配置当连接数占满后的等待超时时间,导致其他请求处于一直等待状态!而正在使用的连接,因为请求服务C_3
超时(当时配置了超时时间10s)....
当然故障的主要原因不仅于此,还有重试机制!当请求失败后,会间隔5s后重试,所以服务B
的不可用状态不仅是因为新的请求而阻塞,主要原因是大量的重试。
1.2 临时的解决方案
- 立刻清除掉所有关于
服务C_3
的重试任务,清除掉后,服务B
恢复正常。 - 缩短请求超时时间。因为协议约定接口是异步通知的方式,所以超时时间不应过长
2. 应该做的
调用外部的服务,我们是通过同一个RestTemplate
实例做的,这是个错误
2.1 资源应该是隔离的
就像船舱一样,底部的船舱都会分隔成写小舱室,每个小舱室都可以做到完全隔离,以此确保某个舱室漏水时保证整个船是安全的。
同理,针对多个服务C_*
,他们之间的资源应该是隔离的,都有不同的RestTemplate
实例
2.2 快速的失败机制
如果单个服务C_*
宕机后,在人工未介入前,应该在一定时间内,再请求该服务应该是快速失败,不阻塞。
3. 总结
其实这个教训主要来自于资源的滥用和当服务出现故障后没有熔断机制。
服务_c*
属于外部服务,并没有按照spring cloud的开发方式去做,现在看来,哪怕不按照spring cloud的去做,也要参考一些它的机制。
防雪崩利器:熔断器 Hystrix 的原理与使用