跳转至

09 健康检测:这个节点都挂了,为啥还要疯狂发请求?

你好,我是何小锋。上一讲我们介绍了超大规模集群“服务发现”的挑战,服务发现的作用就是实时感知集群IP的变化,实现接口跟服务集群节点IP的映射。在超大规模集群实战中,我们更多需要考虑的是保证最终一致性。其实总结来说,就一关键词,你要记住“推拉结合,以拉为准”。接着昨天的内容,我们再来聊聊RPC中的健康检测。

因为有了集群,所以每次发请求前,RPC框架会根据路由和负载均衡算法选择一个具体的IP地址。为了保证请求成功,我们就需要确保每次选择出来的IP对应的连接是健康的,这个逻辑你应该理解。

但你也知道,调用方跟服务集群节点之间的网络状况是瞬息万变的,两者之间可能会出现闪断或者网络设备损坏等情况,那怎么保证选择出来的连接一定是可用的呢?

从我的角度看,终极的解决方案是让调用方实时感知到节点的状态变化,这样他们才能做出正确的选择。这个道理像我们开车一样,车有各种各样的零件,我们不可能在开车之前先去挨个检查下他们的健康情况,转而是应该有一套反馈机制,比如今天我的大灯坏了,那中控台就可以给我提示;明天我的胎压不够了,中控台也能够收到提示。汽车中大部分关键零件的状态变化,我作为调用方,都能够第一时间了解。

那回到RPC框架里,我们应该怎么设计这套机制呢?你可以先停下来想想汽车的例子,看看他们是怎么做的。当然,回到我们RPC的框架里,这事用专业一点的词来说就是服务的健康检测。今天我们就来详细聊聊这个话题。

遇到的问题

在进一步讲解服务健康检测之前,我想先和你分享一个我曾经遇到过的线上问题。

有一天,我们公司某个业务研发团队的负责人急匆匆跑过来,让我帮他解决个问题。仔细听完他的描述后,我才明白,原来是他们发现线上业务的某个接口可用性并不高,基本上十次调用里总会有几次失败。

查看了具体的监控数据之后,我们发现只有请求具体打到某台机器的时候才会有这个问题,也就是说,集群中有某台机器出了问题。于是快刀斩乱麻,我建议他们先把这台“问题机器”下线,以快速解决目前的问题。

但对于我来说,问题并没有结束,我开始进一步琢磨:“接口调用某台机器的时候已经出现不能及时响应了,那为什么RPC框架还会继续把请求发到这台有问题的机器上呢?RPC框架还会把请求发到这台机器上,也就是说从调用方的角度看,它没有觉得这台服务器有问题。”

就像警察破案一样,为了进一步了解事情的真相,我查看了问题时间点的监控和日志,在案发现场发现了这样几个线索:

  1. 通过日志发现请求确实会一直打到这台有问题的机器上,因为我看到日志里有很多超时的异常信息。
  2. 从监控上看,这台机器还是有一些成功的请求,这说明当时调用方跟服务之间的网络连接没有断开。因为如果连接断开之后,RPC框架会把这个节点标识为“不健康”,不会被选出来用于发业务请求。
  3. 深入进去看异常日志,我发现调用方到目标机器的定时心跳会有间歇性失败。
  4. 从目标机器的监控上可以看到该机器的网络指标有异常,出问题时间点TCP重传数比正常高10倍以上。

有了对这四个线索的分析,我基本上可以得出这样的结论:那台问题服务器在某些时间段出现了网络故障,但也还能处理部分请求。换句话说,它处于半死不活的状态。但是(是转折,也是关键点),它还没彻底“死”,还有心跳,这样,调用方就觉得它还正常,所以就没有把它及时挪出健康状态列表。

到这里,你应该也明白了,一开始,我们为了快速解决问题,手动把那台问题机器下线了。刨根问底之后,我们发现,其实更大的问题是我们的服务检测机制有问题,有的服务本来都已经病危了,但我们还以为人家只是个感冒。

接下来,我们就来看看服务检测的核心逻辑。

健康检测的逻辑

刚刚我们提到了心跳机制,我估计你会想,搞什么心跳,是不是我们把问题复杂化了。当服务方下线,正常情况下我们肯定会收到连接断开的通知事件,在这个事件里面直接加处理逻辑不就可以了?是的,我们前面汽车的例子里检测都是这样做的。但咱们这里不行,因为应用健康状况不仅包括TCP连接状况,还包括应用本身是否存活,很多情况下TCP连接没有断开,但应用可能已经“僵死了”。

所以,业内常用的检测方法就是用心跳机制。心跳机制说起来也不复杂,其实就是服务调用方每隔一段时间就问一下服务提供方,“兄弟,你还好吧?”,然后服务提供方很诚实地告诉调用方它目前的状态。

结合前面的文章,你也不难想出来,服务方的状态一般会有三种情况,一个是我很好,一个是我生病了,一个是没回复。用专业的词来对应这三个状态就是:

  1. 健康状态:建立连接成功,并且心跳探活也一直成功;
  2. 亚健康状态:建立连接成功,但是心跳请求连续失败;
  3. 死亡状态:建立连接失败。

节点的状态并不是固定不变的,它会根据心跳或者重连的结果来动态变化,具体状态间转换图如下:

这里你可以关注下几个状态之间的转换箭头,我再给你解释下。首先,一开始初始化的时候,如果建立连接成功,那就是健康状态,否则就是死亡状态。这里没有亚健康这样的中间态。紧接着,如果健康状态的节点连续出现几次不能响应心跳请求的情况,那就会被标记为亚健康状态,也就是说,服务调用方会觉得它生病了。

生病之后(亚健康状态),如果连续几次都能正常响应心跳请求,那就可以转回健康状态,证明病好了。如果病一直好不了,那就会被断定为是死亡节点,死亡之后还需要善后,比如关闭连接。

当然,死亡并不是真正死亡,它还有复活的机会。如果某个时间点里,死亡的节点能够重连成功,那它就可以重新被标记为健康状态。

这就是整个节点的状态转换思路,你不用死记,它很简单,除了不能复活,其他都和我们人的状态一样。当服务调用方通过心跳机制了解了节点的状态之后,每次发请求的时候,就可以优先从健康列表里面选择一个节点。当然,如果健康列表为空,为了提高可用性,也可以尝试从亚健康列表里面选择一个,这就是具体的策略了。

具体的解决方案

理解了服务健康检测的逻辑,我们再回到开头我描述的场景里,看看怎么优化。现在你理解了,一个节点从健康状态过渡到亚健康状态的前提是“连续”心跳失败次数必须到达某一个阈值,比如3次(具体看你怎么配置了)。

而我们的场景里,节点的心跳日志只是间歇性失败,也就是时好时坏,这样,失败次数根本没到阈值,调用方会觉得它只是“生病”了,并且很快就好了。那怎么解决呢?我还是建议你先停下来想想。

你是不是会脱口而出,说改下配置,调低阈值呗。是的,这是最快的解决方法,但是我想说,它治标不治本。第一,像前面说的那样,调用方跟服务节点之间网络状况瞬息万变,出现网络波动的时候会导致误判。第二,在负载高情况,服务端来不及处理心跳请求,由于心跳时间很短,会导致调用方很快触发连续心跳失败而造成断开连接。

我们回到问题的本源,核心是服务节点网络有问题,心跳间歇性失败。我们现在判断节点状态只有一个维度,那就是心跳检测,那是不是可以再加上业务请求的维度呢?

起码我当时是顺着这个方向解决问题的。但紧接着,我又发现了新的麻烦:

  1. 调用方每个接口的调用频次不一样,有的接口可能1秒内调用上百次,有的接口可能半个小时才会调用一次,所以我们不能把简单的把总失败的次数当作判断条件。
  2. 服务的接口响应时间也是不一样的,有的接口可能1ms,有的接口可能是10s,所以我们也不能把TPS至来当作判断条件。

和同事讨论之后,我们找到了可用率这个突破口,应该相对完美了。可用率的计算方式是某一个时间窗口内接口调用成功次数的百分比(成功次数/总调用次数)。当可用率低于某个比例就认为这个节点存在问题,把它挪到亚健康列表,这样既考虑了高低频的调用接口,也兼顾了接口响应时间不同的问题。

总结

这一讲我给你分享了RPC框架里面的一个核心的功能——健康检测,它能帮助我们从连接列表里面过滤掉一些存在问题的节点,避免在发请求的时候选择出有问题的节点而影响业务。但是在设计健康检测方案的时候,我们不能简单地从TCP连接是否健康、心跳是否正常等简单维度考虑,因为健康检测的目的就是要保证“业务无损”,所以在设计方案的时候,我们可以加入业务请求可用率因素,这样能最大化地提升RPC接口可用率。

正常情况下,我们大概30S会发一次心跳请求,这个间隔一般不会太短,如果太短会给服务节点造成很大的压力。但是如果太长的话,又不能及时摘除有问题的节点。

除了在RPC框架里面我们会有采用定时“健康检测”,其实在其它分布式系统设计的时候也会用到“心跳探活”机制。

比如在应用监控系统设计的时候,需要对不健康的应用实例进行报警,好让运维人员及时处理。和咱们RPC的例子一样,在这个场景里,你也不能简单地依赖端口的连通性来判断应用是否存活,因为在端口连通正常的情况下,应用也可能僵死了。

那有啥其他办法能处理应用僵死的情况吗?我们可以让每个应用实例提供一个“健康检测”的URL,检测程序定时通过构造HTTP请求访问该URL,然后根据响应结果来进行存活判断,这样就可以防止僵死状态的误判。你想想,这不就是咱们前面讲到的心跳机制吗?

不过,这个案例里,我还要卖个关子。加完心跳机制,是不是就没有问题了呢?当然不是,因为检测程序所在的机器和目标机器之间的网络可能还会出现故障,如果真出现了故障,不就会误判吗?你以为人家已经生病或者挂了,其实是心跳仪器坏了…

根据我的经验,有一个办法可以减少误判的几率,那就是把检测程序部署在多个机器里面,分布在不同的机架,甚至不同的机房。因为网络同时故障的概率非常低,所以只要任意一个检测程序实例访问目标机器正常,就可以说明该目标机器正常。

课后思考

不知道看完今天的分享之后你有何感触,你在工作中会接触到健康检测的场景吗?你可以在留言区给我分享下你是怎么做的,或者给我的方案挑挑毛病,我会第一时间给你反馈。

当然,也欢迎你留言和我分享你的思考和疑惑,期待你能把今天的所学分享给身边的朋友,邀请他一同交流。我们下节课再见!

精选留言(15)
  • 楼下小黑哥 👍(47) 💬(3)

    以前做过类似健康检查。 我们有个服务,需要通过银行安装在我们后台软件向银行分发交易。这个软件我们在使用过程发现会无故挂掉,为了能及时检测这种情况。 我通过 openresty 写了一个小插件、通过 http 接口访问银行软件,查看银行软件是否挂了。然后接入 钉钉 webhook,触发机器人报警。 这个健康检查啊我有个特别深刻记忆。由于当时是将 openresty 跟银行软件部署在同一台物理机器上去。某一天,整台机器挂了,报警机制当然也失效了。 通过这件事,我现在每次部署服务时,会注意将服务部署在不同物理机器上,防止意外发生。

    2020-03-11

  • etdick 👍(29) 💬(2)

    成功次数/调用总次数,建议加上总次数阀值。如果2次,一次成功,一次失败,就可能误判。例如调用总数>10次以上,成次数/调用次数<50%,才比较准确

    2020-03-12

  • 魔曦 👍(16) 💬(3)

    心跳检测需要分两个纬度,一个机器本身的,一个是应用,单纬度肯定会出问题

    2020-03-27

  • Darren 👍(13) 💬(1)

    之前使用过返回状态保存在MQ中,有专门的消费者去消费消息,其中要是失败率大于阈值,直接调用注册中心,下线该服务,同时使用agent机制,自动重启有问题的服务,之后要是还会出现失败,则报警发出,人工介入。

    2020-03-17

  • 嘻嘻 👍(7) 💬(2)

    老师,几个问题: 1.上面说的基于失败率统计的方案,不就是熔断吗? 2.心跳检测,这个从调用方发心跳到服务方,会不会太重,基于熔断是不是就可以了 3.后面又说心跳检测可以放在多台机器去综合判断,刚刚不是说由调用方发起心跳吗?又变成第三方心跳检测了? 我理解这里有注册中心做心跳,再加熔断,失败重试等就可以了,不知道对不对

    2020-04-24

  • 👍(3) 💬(5)

    老师我认为心跳检测不应该接口的调用方来检测,这样的话调用接口的客户端量很大时,只是心跳检测就会把服务提供方的资源打满,而且当接口服务提供方很多时,客户端每个ip去健康检测也是不可能的

    2020-03-10

  • 🌀Pick Monster 🌀 👍(3) 💬(1)

    老师,内容看明白了,可用率这个指标具体怎么实现呢?因为一般使用RPC框架都是三方框架,我们是需要自己对三方接口进行重新实现吗?

    2020-03-09

  • ant 👍(2) 💬(1)

    心跳检测,单一纬度的标准始终差点意思。还是要结合业务场景,多维度判断,来保证结果准确性。例如 连续失败是最直接的纬度,可以综合考虑变更为 单位时间内失败次数,或单位次数的成功率

    2020-04-13

  • 每天晒白牙 👍(2) 💬(2)

    想请教老师,RPC 框架的心跳检测怎么做的呢?只听说过心跳检测这个概念,但在代码层面如何做,没有概念。看到老师在最后提到检测应用是否可用,可以在应用实例中开一个 url 供检测程序发 http 请求检测。但非应用级别的心跳检测也是这样做的吗?

    2020-03-09

  • dancer 👍(1) 💬(1)

    有一个地方没想明白,希望老师能解答一下。如果在多个机房部房部署探活程序,如果部分检测有问题 部分检测没有问题,这个状态需要怎么判断,又该怎样同步给所有探活程序?

    2020-04-30

  • 有米 👍(0) 💬(1)

    你以为别人挂了其实是自己挂了😢

    2020-05-04

  • 雨霖铃声声慢 👍(0) 💬(1)

    看完这篇文章的感触是健康检查这套逻辑需要业务和运维的配合实现,业务要提供heath check的endpoint,运维要调用这个endpoint来查看服务的情况,所以在进一步,一些通用的框架会自动集成health check的功能并可以通过配置打开,当新服务上线的时候,监控检查功能会自动提供。

    2020-03-10

  • ple 👍(0) 💬(1)

    想问下。心跳检测文中说是调用方定时去看看提供方是否存活。但是平常好像不是这么做的,会有一台专门做健康检查的机器定时去调用健康检查接口,是说这两种方式都是可以的么 觉得第一种会不会做起来成本比较高?

    2020-03-10

  • jiemoon 👍(0) 💬(2)

    健康检测是不是也要检测自己

    2020-03-09

  • Reason 👍(0) 💬(1)

    接触的印象最深刻的检测机制是spring boot actuator的health端点了。有个问题没太理解请老师解惑,文末说检测程序可以分机房机架部署,检测程序是单独rpc框架的一套程序么?我理解一般是在rpc框架里的一个线程,不知道理解的对不?

    2020-03-09