前言
在分布式系统中,心跳检测是一种用于监控远程节点是否正常运行的机制。心跳检测用于确保服务提供者和消费者之间的连接健康。通过定期发送心跳包,双方可以检测到对方是否存活,以及网网络连接是否正常。
1、合理设置心跳检测间隔:太短的间隔可能导致不必要的网络开销,太长的间隔则可能影响系统稳定性。根据实际情况调整心跳检测间隔,以平衡系统性能和稳定性。
2、开启超时重试机制:针对重要的远程服务调用,开启超时重试可以有效提高系统的健壮性。根据业务场景和系统性能要求,合理配置超时时间和重试次数。
3、监控与告警:监控心跳检测和超时重试相关的指标,如心跳包成功率、重试次数等。当出现异常情况时,及时触发告警通知相关人员进行处理。
4、优化网络环境:确保网络连接稳定可靠,降低因网络波动导致的心跳异常和超时重试情况的发生。
5、异常处理与日志记录:在代码中添加异常处理逻辑,记录详细的日志信息,以便于问题排查和定位。
Dubbo心跳机制
dubbo默认使用Netty作为通讯工具,Netty在消费端和服务端之间建立长连接。当建立长连接后,需要使用心跳机制判断双方是否在线。
一、实现原理
Dubbo 的请求机制和心跳发送主要由 Netty 的 IdleStateHandler 对象处理。以下是这个过程的详细步骤:
1、IdleStateHandler 对象的角色:IdleStateHandler 是一个处理器,配置在 Netty 的责任链中。无论是发送请求还是接收响应,都会经过这个处理器。
2、空闲检测定时器的创建:在双方通信开始后,IdleStateHandler 对象会创建一些空闲检测定时器。这些定时器用于检测读事件(收到请求会触发读事件)和写事件(连接、发送请求会触发写事件)。
3、超时事件的触发:如果在指定的空闲时间内没有收到读事件或写事件,IdleStateHandler 会触发超时事件。
4、超时事件的处理:IdleStateHandler 将超时事件交给责任链里面的下一个处理器处理。这个处理器可能是 NettyClientHandler 或者 NettyServerHandler。
5、NettyClientHandler 是客户端使用的处理器。当它收到超时事件后,会向对方发送一个心跳事件的请求。
6、NettyServerHandler 是服务端使用的处理器。当它收到超时事件后,会关闭超时的连接。
二、发送心跳
Dubbo 的服务端的 Netty 创建和初始化是由 NettyServer 类完成的。以下是这个过程的详细步骤和代码注释:
1、创建 NettyServerHandler 对象:NettyServerHandler 对象是用于处理服务器端的网络事件的处理器。在下面的代码中,我们首先创建了一个 NettyServerHandler 对象,并获取了其通道集合。
final NettyServerHandler nettyServerHandler = new NettyServerHandler(getUrl(), this);
channels = nettyServerHandler.getChannels();
2、配置 bootstrap 对象:bootstrap 对象是 Netty 用于配置服务器的工具。在下面的代码中,我们配置了 bootstrap 对象的线程组、通道类型、TCP 参数和子处理器
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childOption(ChannelOption.TCP_NODELAY, Boolean.TRUE)
.childOption(ChannelOption.SO_REUSEADDR, Boolean.TRUE)
.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
int idleTimeout = UrlUtils.getIdleTimeout(getUrl());
NettyCodecAdapter adapter = new NettyCodecAdapter(getCodec(), getUrl(), NettyServer.this);
if (getUrl().getParameter(SSL_ENABLED_KEY, false)) {
ch.pipeline().addLast("negotiation",
SslHandlerInitializer.sslServerHandler(getUrl(), nettyServerHandler));
}
ch.pipeline()
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
//将IdleStateHandler加入责任链
.addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
//将NettyServerHandler加入责任链
.addLast("handler", nettyServerHandler);
}
});
在这段代码中,我们首先设置了 bootstrap 对象的线程组、通道类型和 TCP 参数。然后,我们创建了一个新的 ChannelInitializer 对象,并在其 initChannel 方法中配置了通道的处理器链。这个处理器链包括了解码器、编码器、IdleStateHandler 对象和 NettyServerHandler 对象
3、配置 IdleStateHandler 对象:IdleStateHandler 对象是用于处理空闲状态的处理器。在下面的代码中,我们创建了一个新的 IdleStateHandler 对象,并将其添加到了处理器链中。
.addLast("server-idle-handler", new IdleStateHandler(0, 0, idleTimeout, MILLISECONDS))
在这段代码中,我们设置了 IdleStateHandler 对象的读空闲时间、写空闲时间和所有空闲时间。这些参数的单位是毫秒。
4、配置 NettyServerHandler 对象:NettyServerHandler 对象是用于处理服务器端的网络事件的处理器。在下面的代码中,我们将 NettyServerHandler 对象添加到了处理器链中。
.addLast("handler", nettyServerHandler);
5、IdleStateHandler 参数解释:IdleStateHandler 的构造函数接受四个参数:readerIdleTime、writerIdleTime、allIdleTime 和 unit。这些参数的含义如下:
readerIdleTime:读空闲超时检测定时任务会在每 readerIdleTime 时间内启动一次,检测在 readerIdleTime 内是否发生过读事件,如果没有发生过,则触发读超时事件 READER_IDLE_STATE_EVENT,并将超时事件交给 NettyClientHandler 处理。如果为 0,则不创建定时任务。
writerIdleTime:与 readerIdleTime 作用类似,只不过该参数定义的是写事件。
allIdleTime:同时检测读事件和写事件,如果在 allIdleTime 时间内即没有发生过读事件,也没有发生过写事件,则触发超时事件 ALL_IDLE_STATE_EVENT。
unit:表示前面三个参数的单位,就上面代码来说,表示的是毫秒。
服务端创建的 IdleStateHandler 设置了 allIdleTime,所以服务端的定时任务需要检测读事件和写事件。定时任务的启动时间间隔是参数 heartbeat 设置值的 3 倍,heartbeat 默认是 1 分钟,也可以通过参数 heartbeat.timeout 设置定时任务的启动时间间隔。单位都是毫秒。
客户端的 Netty 初始化:客户端初始化 Netty 是在 NettyClient 的 doOpen 方法中完成,下面来看一下代码:
ch.pipeline()
.addLast("decoder", adapter.getDecoder())
.addLast("encoder", adapter.getEncoder())
.addLast("client-idle-handler", new IdleStateHandler(heartbeatInterval, 0, 0, MILLISECONDS))
.addLast("handler", nettyClientHandler);
NettyClient 创建 IdleStateHandler 只设置了 readerIdleTime 入参,表示客户端启动定时任务只检测读事件。定时任务的时间间隔由参数 heartbeat 指定,默认是 1 分钟。
IdleStateHandler 的定时任务创建和超时事件检测:当 Channel 被激活的时候,也就是连接被建立起来之后,会调用 IdleStateHandler 的 initialize 方法。这个方法会创建用于检测读超时事件、写超时事件和读写超时事件的定时任务。当定时任务运行时,如果在指定的空闲时间内没有发生过读事件,就会创建读超时事件,并将超时事件交给责任链里面的下一个 handler,也就是 NettyClientHandler 或 NettyServerHandler。
5、服务端和客户端对超时事件的处理:服务端和客户端对超时事件的处理方式不同。服务端收到超时事件后,会关闭对应的连接。而客户端收到超时事件后,会发送心跳报文以维持连接。
6、超时重连:当客户端发现某个连接长时间没有收到响应数据时,dubbo 在 exchange 信息交换层提供了类 HeaderExchangeClient 会对该连接进行超时重连。HeaderExchangeClient 在创建对象的过程中创建定时任务 ReconnectTimerTask,默认定时任务每 1 分钟执行一次,发现不可用的连接或者默认 1 分钟内没有收到消息的连接都会进行重连。
总结:
Dubbo 对超时的检测是借助 Netty 的 IdleStateHandler 完成的,一旦发生超时,则创建超时事件并交给 NettyClientHandler 或 NettyServerHandler 处理,服务端是关闭连接,客户端是发送心跳报文继续维持连接。服务端和客户端对超时后作出的不同操作也反映了双方不同的策略。因为连接占用系统资源,服务端要尽可能的将资源留给其他请求,对于服务端来说,如果某个连接长时间没有数据传输,说明与该客户端的连接已经断开,或者客户端访问已经结束最近不需要再次访问,无论哪种情况,对于服务端来说最好的处理都是断开与客户端的连接。而客户端则不同,客户端想尽全力保证连接的可用,因为客户端访问服务时最希望的是尽快得到响应,因此客户端最好是时时刻刻保持连接的可用,这样访问服务时可以省去建立连接的时间消耗。另外一点也要主要,服务端和客户端启动定时任务的时间是不同的,默认服务端是 3 分钟,客户端是 1 分钟,dubbo 要求服务端定时任务的启动时间间隔最小是客户端的 2 倍。
redis心跳机制
Redis的主从复制和哨兵系统都是基于心跳机制来维护数据同步和系统监控的。
主从复制
Redis主从复制是一种数据同步机制,当新的slave连接到master时,它们之间的数据同步流程大致如下:
- 从服务器连接到主服务器,发送SYNC命令。
- 主服务器接收到SYNC命令后,开始执行BGSAVE命令生成RDB文件。
- 主服务器BGSAVE执行完毕后,将RDB文件发送给从服务器。
- 从服务器收到RDB文件后,载入并应用此RDB文件。
- 之后,主服务器每执行一个写命令,都会将写命令发送给从服务器。
心跳机制
- 主服务器每秒定时发送INFO命令给从服务器。
- 从服务器接收到INFO命令后,返回相关信息。
哨兵(Sentinel)
Redis哨兵系统是用来实现Redis高可用性的方案,哨兵系统通过心跳机制和投票机制实现故障检测和故障转移。
心跳机制
- 每个哨兵定时向主服务器和其他哨兵发送PING命令。
- 主服务器和其他哨兵响应PING命令。
- 如果一个哨兵没有在规定时间内收到主服务器的PONG响应,则可能认为主服务器宕机。
投票机制:
- 当哨兵认为主服务器宕机时,开始进行投票选主。
- 每个哨兵将自己的投票发送给其他哨兵。
- 获得多数投票的哨兵节点将成为新的主服务器。
Sentinel的工作方式
1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。
5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。
6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。
7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。
若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
zookeeper 心跳机制
Zookeeper的心跳机制是通过Zookeeper集群中的各个节点之间相互发送心跳包来实现的。每个Zookeeper节点都会定期向其他节点发送心跳包,以表明自己的存活状态。如果某个节点在一定时间内没有收到其他节点的心跳包,则会认为该节点已经失效,并将其标记为不可用。
Zookeeper的心跳机制保证了集群中各个节点的状态及时更新,确保了集群的高可用性和稳定性。如果某个节点出现了故障或者网络断开,其他节点会及时感知到并进行相应的处理,从而保证了整个集群的正常运行。
1.tickTime:通信心跳数,Zookeeper服务器心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒。
它用于心跳机制,并且设置最小的session超时时间为两倍心跳时间。(session的最小超时时间是2*tickTime); 大于2倍的心跳时间,就是超时了;
2.initLimit:LF初始通信时限
集群中的follower跟随者服务器(F)与leader领导者服务器(L)之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限。
投票选举新leader的初始化时间
Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。
Leader允许F在initLimit时间内完成这个工作。
3.syncLimit:LF同步通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,
Leader认为Follwer死掉,从服务器列表中删除Follwer。
在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。
如果L发出心跳包在syncLimit之后,还没有从F那收到响应,那么就认为这个F已经不在线了。
Nacos心跳机制
1、服务与客户端心跳机制
心跳机制是一种用于监测和管理微服务可用性的机制,它用来维护注册中心和服务提供者之间的连接状态,并及时更新服务实例的状态信息。
在这种情况下,心跳机制主要用于服务的健康检查。客户端(通常是服务提供者或消费者)会定期向 Nacos 服务端发送心跳,以表明它们仍然是活动的。如果 Nacos 服务端在一段时间内没有收到来自某个客户端的心跳,它会认为该客户端的服务已经下线或者出现故障,从而更新服务列表。这种机制有助于 Nacos 服务端维护最新的服务状态,从而提供准确的服务发现和路由。
心跳机制包括两个主要组件:心跳发送方(客户端)和心跳接收方(服务端)。
每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。发包方可以是客户也可以是服务端
心跳发送方(Heartbeat Sender):每个微服务都会定期发送称为心跳消息的请求到一个中央位置(例如注册中心或负载均衡器)。这个心跳消息包含有关该微服务的健康信息,如服务是否正常运行、负载情况、资源消耗等。心跳消息的频率可以根据需求进行配置,通常是以固定的时间间隔发送。
心跳接收方(Heartbeat Receiver):中央位置上的组件(如注册中心或负载均衡器)负责接收并处理微服务发送的心跳消息。它会记录每个微服务的心跳,并根据心跳消息的到达情况和内容来判断微服务的可用性。如果心跳消息超过一定时间没有到达,或者心跳消息中报告了错误状态,中央位置可以采取相应的措施,如将该微服务标记为不可用、重新分配负载或发送警报通知等。
Nacos 中的健康检查机制不能主动设置,但健康检查机制是和 Nacos 的服务实例类型强相关的。 也就是说 Nacos 中的两种服务实例分别对应了两种健康检查机制:
临时实例(也可以叫做非持久化实例):对应的是客户端主动上报机制。
永久实例(也可以叫做持久化实例):服务端反向探测机制。
2、服务集群之间的心跳机制
-
分布式存储的服务注册信息:在 Nacos 中,服务的注册信息是分布式存储的,每个 Nacos 节点都会保存所有的服务注册信息。这使得无论客户端连接到哪个 Nacos 节点,都可以获取到所有的服务信息。
-
高可用性设计:由于每个节点都保存有所有的服务注册信息,即使某个节点宕机,也不会影响服务的发现和注册,从而保证了 Nacos 集群的高可用性。
-
无主架构:Nacos 并没有采用主从架构,也没有类似 ZooKeeper 的选主过程。所有的 Nacos 节点都是对等的,可以提供完全一样的服务。这种无主的设计使得 Nacos 集群可以更好地应对节点故障,提高了系统的可靠性。
-
心跳机制同步服务注册信息:Nacos 集群中的节点之间需要通过心跳机制来同步服务的注册信息。每个节点都会定期向其他节点发送心跳消息,这个心跳消息中包含了该节点上的服务注册信息。当一个节点收到其他节点的心跳消息后,会更新自己的服务注册信息,以保持与其他节点的一致性。
-
心跳机制维持节点活动状态:在 Nacos 集群模式下,集群中的每个节点都会定期向其他节点发送心跳,以表明它们仍然是活动的。这些心跳消息通常包含节点的状态信息,如其 IP 地址、端口号、负载信息等。
-
心跳机制触发故障恢复:如果一个节点在一段时间内没有收到另一个节点的心跳消息,它会认为该节点已经失败,从而触发集群的故障恢复机制。这可能包括重新选举领导者、重新分配任务、更新路由表等。
eureka心跳机制
1、心跳机制
Eureka Server同时也是一个Eureka Client,在不禁止Eureka Server的客户端行为时,它会向它配置文件中的其他Eureka Server进行拉取注册表、服务注册和发送心跳等操作。
1.服务器启动成功,等待客户(服务)端注册,在启动过程中如果我们配置了集群,集群之间会同步注册表,每一个Eureka serve都会存在这个集群完整的服务注册表信息
2.Eureka client 启动时根据配置信息,去注册到指定的注册中心
3.Eureka client会每30秒向Eureka server 发送一次心跳请求,证明该客户端服务正常
4.当Eureka server90s内没有接受客户端服务正常,注册中心会认为该节点失效,会注销该实列 (从注册表中删除注册信息)
5.单位时间内如果服务端统计到大量客户端没有发送心跳,则认为网络异常,进去自我保护机制,不在剔除没有发送心跳的客户端
6.当客户端恢复正常之后,服务端就会退出自我保护模式
7.客户端定时全量或增量从注册中心获取服务注册表,并且会缓存到本地
8.服务调用时,客户端会先从本地缓存找到调用服务,如果调取不到 先从注册中心刷新注册表,在同步到本地
9.客户端获取不到目标服务器信息发起服务调用
10.客户端程序关闭时向服务端发送取消请求,服务器将实例从注册表中删除
2、自我保护机制的工作机制
如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为自己出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:
(1)Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
(2)Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
(3)当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。
Eureka自我保护机制,通过配置 eureka.server.enable-self-preservation来true打开/false禁用自我保护机制,默认打开状态,建议生产环境打开此配置。