嗯,没错,这是一个讲单一职责原则的故事。如果你也用SpringBoot Actuator的话,你更要认真看噢。
午夜追魂CALL
小二同学隐隐约约听到手机在振动,他一把在床上摸起手机,半夜1点,有20多条的短信息,小二心里一震。强忍着手机屏幕强烈的背光,眯着眼睛看起了短信:
“服务A因健康检测连续失败三次,已将其重启,本应用已累计重启26次”
“昨晚上线是替换掉了健康检测用的接口,改成了用SpringBoot的actuator的health检测,但当时观察了很久,一切都正常呀,为啥突然就出问题了?”小二紧锁着眉头,百思不得其解,但空想是没办法解决的问题的,于是小二麻溜地穿上了衣服,打了辆车,飞快的赶回公司。
“应用在正常工作?!”
在公司电梯上去着生产 ,小二看了下手机短信告警,一件事情让他更加困惑了——后面居然没有了告警短信,就好像刚刚的问题自动恢复了一样。
小二找到了运维 同事,拿到了生产权限,然后开始了问题排查。经过初步排查,服务A确实恢复了正常,看起来并没有任何异样。
小二继续登录到监控管理平台,发现重启是在12点多到一点半左右这个时间发生的,也就是说在这段时间里,健康检查策略连续地失败了。小二唯一能联想到的,是不是跟这段时间跑批压力有关?但没道理呀,跑批压力跟的监控检测八竿子打不着呀?
“健康检测依然存在零星失败”
小二看了下日志,发现监控检测依然间歇性存在失败的情况!但应用没有重启了,说明不存在连续检测失败的情况。这就好说了,场景还能复现!
小二把一个实例的监控检测策略调回了原先的检测接口(其就是一个正常的业务访问接口)。小二观察着这个实例与其他未修改回来的实例的区别,观察了一小段时间,其他实例基本都出现了一两次监控检测失败,但原来基于业务接口的检测却没有出现这个问题。
因这个间歇性失败,小二推断这是一个超时导致的失败。于是就写了个带超时的循环发送http请求新健康检测端口的命令(watch + curl),每秒发送一次,以期望复现这个超时。
“他们看起来毫无关联呀?”
在等待复现超时的过程中,小二开始翻起了应用的详细日志,逐行逐行的看起来,日志大多都显示正常。
突然有一行奇怪的日志映入了小二的眼里,服务在运行过程中跟配置中心拉取了配置?小二虽然觉得奇怪,但是也没有深入琢磨它。他继续往下翻。
“那个拉取配置中心的日志又出现了,为啥?!”,这不符合小二对配置中心的认识呀。他快速地阅读日志,发现每隔5分钟就会出现一次拉取配置中心配置的请求。
为了确认这个是否特殊情况,他对比了改回原来的检测方式的实例的日志。原有检测方式的实例并没有5分钟拉取一次配置中心的日志!
但健康检测和配置中心看起来也是没有半毛钱关系呀?
线索汇合处
小二这时转过去查看之前的超时检测脚本,出现了超时情况!这落实了之前小二的想法。小二查看超时发生的时间,然后再翻看服务A对应实例的日志,跟拉取配置中心的时间重合了!
这绝对不是巧合!这极大概率的指向是,新健康检测策略会通过拉取配置中心的配置来检测是否健康。而每5分钟拉取一次则说明这个拉取策略可能有缓存!
而间歇性出现超时,可能是因为不止我这个服务,而是大范围的服务都同时应用了新检测机制,导致配置中心压力增大而偶现超时!而跑批时间段频繁出现检测失败则可能是因为大批量的批任务启动占用了较多的资源从而加剧了资源占用情况,导致监控检测失败加剧严重!
但这当然只是一个猜想,小二让管理员帮忙查看配置中心的资源使用情况,果然!配置中心及其后端依赖GITLAB的CPU使用率都在一个比较高的水平!
代码确认
确定了这个疑似问题点后,小二马上打开相关代码以确认最终问题,以下是代码的确认结果:
Actuator的Health检测接口是一个检测范围可扩展的接口,只要在Spring中注册上实现了检测接口的特定Bean,Health检测接口被调用时,就会调用该注册的监控检测Bean
而配置中心客户端也往Spring注册了这么一个检测配置中心健康状态的Bean,其确实会每5分钟请求一次配置中心
到此,问题的答案已经明了了。小二让管理员批量地把这个检测形式给回滚了回去,以免配置中心那边扛不住崩溃。
反思
若从编程思想层次反思这个问题的话,出现这个问题的根本原因是我们健康检测策略所需的健康检测接口是
应用能否提供服务的健康状态
而Actuator的健康检测接口,其提供的是
应用各个方面是否都正常运作的健康状态
这两个是有重叠,但不一致的两个方面。若我们将Actuator的健康检测替代用于“应用能否提供服务的健康状态”时,会出现 像上面一样,本来应用是可以正常提供服务的,但是由于部分非强依赖组件发生问题时,导致监控检测失败,从而应用发生重启。
一种更严重的情况是,应用拉取配置中心、注册中心数据后是可以依赖本地的数据缓存继续服务 而不管配置中心、注册中心是否存活的。但引入了该健康检测机制后,就会导致能提供服务状态下,被重启了,但其依赖的配置中心/注册中心 挂了,因此就无法启动了。
其实,这里就是SOLID原则里的单一职责原则。任何一个组件、接口、代码都应该只对一个目的负责,否则必然产生耦合,对后续开发、理解、重构产生不良的影响。
一些其他坑
顺便提一下,实际上,不仅仅我们用错了这个接口,在Eureka里,其有两种形式探测客户端是否健康
通过定时回报的心跳
通过调用特定的接口检测,默认为Actuator的Health检测接口
当使用第二种时,很可能你的应用依然能提供服务,但部分组件报告不健康,于是Eureka认为你不能处理请求了,于是就将你的状态标志为不健康。
至于上述问题中GITLAB及SpringCloud Config Server的CPU占用高还有一个原因是因为,在SpringCLoud F版本之前,每次有客户端向配置中心请求配置时,都会跟gitlab尝试更新一下本地数据,因此导致了资源占用高。在F版本之后,可以设置一个CACHE的时间,能降低资源的占用。
如果这些坑也是你之前不知道的,麻烦帮忙转发下,让更多人知道!