本文已收录在Github关注我,紧跟本系列专栏文章,咱们下篇再续!

  • 🚀 魔都架构师 | 全网30W技术追随者
  • 🔧 大厂分布式系统/数据中台实战专家
  • 🏆 主导交易系统百万级流量调优 & 车联网平台架构
  • 🧠 AIGC应用开发先行者 | 区块链落地实践者
  • 🌍 以技术驱动创新,我们的征途是改变世界!
  • 👉 实战干货:编程严选网

服务发现的作用:实时感知集群IP的变化,实现接口跟服务集群节点IP的映射。超大规模集群更要考虑最终一致性。“推拉结合,以拉为准”。

1 健康检测

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

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

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

回到RPC框架里,专业一点的词来就是服务的健康检测。

2 生产事故

线上业务的某个接口可用性并不高,十次调用总有几次失败。

查看监控数据后,发现只有请求具体打到某台机器才有这问题,即集群中有某台机器异常。建议他们先“问题机器”下线。

接口调用某台机器时已出现不能及时响应了,为何RPC框架还继续把请求发到这台有问题机器?即在调用方角度,它没有觉得这台服务器异常。

于是查看问题时间点的监控和日志:

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

基本可得:那台问题服务器在某些时间段出现网络故障,但也还能处理部分请求。即它处于半死不活状态。但还没彻底“死”,还有心跳,这样,调用方就觉得它还正常,所以就没有把它挪出健康状态列表。

所以,更大问题是服务检测机制有问题,有的服务本来都已新冠晚期,但还以为人家只是普通感冒。

3 健康检测的逻辑

当服务方下线,正常情况下我们肯定会收到连接断开的通知事件,在这事件里直接加处理逻辑不就行?

是的,汽车案例里检测都是这样做的。但这不行,应用健康状况不仅包括TCP连接状况,还包括应用本身是否存活,很多情况下TCP连接没断开,但应用可能已“僵死”。

所以,常用检测方法即心跳,服务调用方定时问服务提供方,“兄弟,还好吧?”,然后服务提供方如实地告诉调用方它目前状态。

服务方的状态

① 健康状态

我很好。建立连接成功,并且心跳探活也一直成功

② 亚健康状态

我生病了。建立连接成功,但是心跳请求连续失败

③ 死亡状态

没回复。建立连接失败。

节点的状态会根据心跳或重连结果而动态变化:

拒绝“失联”节点!深入RPC健康检测机制与实践_IP

初始化时,若建立连接成功,那就是健康状态,否则死亡状态,没有亚健康中间态。

若健康状态的节点连续出现不能响应心跳请求情况,就标记为亚健康,即服务调用方会觉得它生病了。

生病之后(亚健康状态):

  • 若连续几次都能正常响应心跳请求,就可转回健康状态,证明病好了
  • 若病一直好不了,断定为死亡节点,死后还需善后,如关闭连接

死亡还有复活机会。如某时间点,死亡节点能重连成功,就重新标记为健康态。

当服务调用方通过心跳了解节点状态后,每次发请求时,优先从健康列表里选择一个节点。若健康列表为空,为提高可用性,也可尝试从亚健康列表里面选个。

3 具体解决方案

一个节点从健康状态过渡到亚健康状态的前提:“连续”心跳失败次数必须到达某一个阈值,如3次。

而场景里,节点心跳日志间歇性失败,时好时坏,失败次数根本没到阈值,调用方觉得它只是“生病”,并且很快就好了。怎么解决?改下配置,调低阈值?是的,这是最快解决方法,但治标不治本:

  • 调用方跟服务节点之间网络状况瞬息万变,出现网络波动的时候会导致误判
  • 负载高情况,服务端来不及处理心跳请求,由于心跳时间很短,会导致调用方很快触发连续心跳失败而造成断开连接

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

但又发现新麻烦:

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

找到可用率突破口,应该相对完美。可用率的计算方式:

某一个时间窗口内,接口调用成功次数的百分比(成功次数/总调用次数)

当可用率低于某个比例就认为该节点异常,挪到亚健康列表:

  • 既考虑了高低频的调用接口
  • 兼顾接口响应时间不同的问题

5 加完心跳机制,就万事大吉了吗

不是,因为检测程序所在机器和目标机器之间的网络可能还会故障,就会误判。你以为人家已经生病或者挂了,其实是心跳仪器坏了。

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

6 总结

健康检测帮助调用方应用来管理所有服务提供方的连接,并动态维护每个连接的状态,方便服务调用方在每次发起请求的时候都可以拿到一个可用的连接。

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

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

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

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

处理应用僵死

可让每个应用实例提供一个“健康检测”的URL,检测程序定时HTTP请求该URL,根据响应结果进行存活判断,就可防止僵死状态误判,即心跳机制!

FAQ

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

A:还是需要分场景对待的,没有最好的,只有最合适的。

心跳检测分两维:

  • 机器本身
  • 应用

单纬度肯定出问题。

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

A:失败率统计是难点,需要考虑是否有网络设备坏或不同idc问题

Q:健康检测:调用方向服务方发送心跳检测,如果超过3次(阈值可以设置)未响应则认为服务节点挂掉。会存在问题:

  • 服务方心跳正常响应,但服务间歇性响应超时(亚健康状态),导致调用方误判;借助可用率的思路来解决
  • 调用方心跳机制出现问题,导致误判服务方挂掉;可借助调用方集群部署,其中一台调用显示正常则认为正常的办法来减少误判

像Dubbo通过IdleStateHandler设置定时任务,服务空闲发送心跳,实现健康检测:http:///zh-cn/blog/dubbo-heartbeat-design.html。

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

A:不一定要接口纬度,一般情况下多个接口直接会共享tcp连接的,可以用tcp连接纬度

Q:

基于失败率统计的方案,不就是熔断?

心跳检测,从调用方发心跳到服务方,会不会太重?基于熔断是不是即可?

后面又说心跳检测可放在多台机器去综合判断,刚刚不是说由调用方发起心跳吗?又变成第三方心跳检测?

我理解有注册中心做心跳,再加熔断,失败重试等即可?

A:调用方到提供方之间的心跳正常,才能保证链路没有问题。

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

Q:RPC 框架心跳检测咋做?只听过心跳检测概念,但代码层咋做,没概念。看最后提到检测应用是否可用,可在应用实例中开一个 url 供检测程序发 http 请求检测。但非应用级别的心跳检测也这样做吗?

A:定时发心跳消息是最简单的方法,通过判断是否正常响应。

Q:可用率指标咋实现?因为一般使用RPC框架都是三方框架,我们是需要自己对三方接口进行重新实现吗?

A:看看有没有插件支持。

心跳探活核心要点:

  • 及时
  • 准确
  • 全面,网络环境瞬息万变,需要考虑探活机器本身是否OK?探活的机制是否OK?

探活机制的设置应视情况而定,可以是节点级?也可以是服务级?为了防止因网络通信问题误判,可以设置多个探活节点。为了使探活更准确,可以假如可用率,调用次数等维度。

Q:你以为别人挂了其实是自己挂了?

A:检测的目的防止别人挂

Q:如在多机房部署探活程序,如部分检测有问题,部分检测没问题,这状态咋判断,又咋同步给所有探活程序?

A:多机房主要担心跨机房网络问题,只要有存活的就认为存活。

健康检查需业务和运维配合:

  • 业务提供heath check的endpoint
  • 运维调用这个endpoint来查看服务的情况

进一步,一些通用框架会自动集成health check的功能并可通过配置打开,当新服务上线,监控检查功能会自动提供

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

A:用其他机器检查反映的结果不一定准,可能中间经过的网络设备不同。