【AI翻译】分布式系统中的心跳机制

Title: 分布式系统中的心跳机制

URL Source: https://arpitbhayani.me/blogs/heartbeats-in-distributed-systems/

Published Time: 2025-11-12T00:00:00.000Z

Markdown Content:
在分布式系统中,一个最根本的挑战就是如何判断一个节点或服务是否存活并正常运行。与所有内容在单一进程中运行的单体应用相比,分布式系统跨越多台机器、网络和数据中心。当这些节点地理上分离时,这一问题更加突出。这也正是心跳机制发挥作用的地方。

想象这样一个服务器集群,每天协同处理数百万次请求。如果其中一个服务器悄无声息地崩溃了,系统需要多快能够检测到这一故障并作出反应?我们又如何区分一个真正“死掉”的服务器与因网络拥塞暂时变慢的服务器?这些问题汇聚成为什么心跳机制至关重要的核心。

什么是心跳消息

在最基本的层面上,心跳是一种定期从分布式系统中的一个组件向另一个组件发送的信号,用于表明发送方依然存活且工作正常。你可以把它看作一条简单的信息:“我还活着!”

心跳消息通常体积小而轻量,通常只包含时间戳、序列号或标识符。其关键特性是定期、固定间隔地发送,形成可预测的模式,便于其他组件监控。

该机制通过一个简单的协议实现:发送方和接收方。发送方承诺每隔固定时间(如2秒)广播一次自己的心跳。接收方监测这些心跳并记录上一次心跳的收到时间。如果在预期时间内未收到心跳,接收方可以合理地认为对方出问题了。

class HeartbeatSender:  def __init__(self, interval_seconds):  self.interval = interval_seconds  self.sequence_number = 0def send_heartbeat(self, target):  message = {  'node_id': self.get_node_id(),  'timestamp': time.time(),  'sequence': self.sequence_number  }  send_to(message, target)  self.sequence_number += 1def run(self):  while True:  self.send_heartbeat(target_node)  time.sleep(self.interval)

当一个节点崩溃、停止响应或由于网络分区而被孤立时,心跳便不再到达。此时,监控系统可以采取适当措施,比如将故障节点从负载均衡池中移除、将流量重定向到健康节点或触发故障转移流程等。

心跳系统的核心组件

第一个组件是心跳发送者。即周期性生成并传输心跳信号的节点或服务。在大多数实现中,发送者运行于独立线程或作为后台任务,以避免干扰主应用逻辑。

第二个组件是心跳接收者或监控者。该组件监听收到的心跳,并跟踪每条心跳的接收时间。监控者会维护所有被跟踪节点的状态,通常为每个节点存储最后一次收到心跳的时间戳。在评估节点健康状况时,会将当前时间与最后一次心跳进行对比,判断节点是否故障。

class HeartbeatMonitor:  def __init__(self, timeout_seconds):  self.timeout = timeout_seconds  self.last_heartbeats = {}  def receive_heartbeat(self, message):  node_id = message['node_id']  self.last_heartbeats[node_id] = {  'timestamp': message['timestamp'],  'sequence': message['sequence'],  'received_at': time.time()  }  def check_node_health(self, node_id):  if node_id not in self.last_heartbeats:  return False  last_heartbeat_time = self.last_heartbeats[node_id]['received_at']  time_since_heartbeat = time.time() - last_heartbeat_time  return time_since_heartbeat < self.timeout  def get_failed_nodes(self):  failed_nodes = []  current_time = time.time()  for node_id, data in self.last_heartbeats.items():  if current_time - data['received_at'] > self.timeout:  failed_nodes.append(node_id)  return failed_nodes

第三个参数是心跳间隔,决定心跳发送的频率。这个间隔在分布式系统中具有基础性的权衡关系。心跳发送过于频繁会浪费带宽和CPU资源;发送过于稀疏又会导致故障检测迟缓。大部分系统根据业务需求和网络特性选择1秒到10秒不等的间隔。

第四个参数是超时/失效阈值。这决定了监控者在多久未收到心跳后,就会判定节点失效。

注意,超时值的选择需要权衡:快速故障检测与容忍临时网络延迟或进程停顿。经验法则通常是设置为心跳间隔的2~3倍,允许丢失部分心跳再判定故障。

心跳间隔与超时的决策

如果系统采用极短的心跳间隔,比如每500毫秒就发送一次,可以实现非常快的故障检测。不过,这样做也有代价,每一条心跳都消耗网络带宽。在含有成百上千节点的大集群中,心跳消息可能占据大量流量。同时,超短的间隔也提升了对瞬时故障(如临时的网络拥塞、GC暂停等)的敏感度。

举个例子:有1000个节点,每个节点每500毫秒向中心监控发送心跳,每秒就有2000条心跳消息,仅用于健康检查。在生产环境下,这样的开销可能影响实际业务流量。

反过来,若心跳间隔过长,比如30秒,则系统检测故障就会变慢。节点崩溃后,系统可能要30秒乃至更久才发现此异常。期间,故障节点可能仍然接收请求,给用户带来错误。

同样,超时时间也必须结合网络特性考虑。在跨数据中心的分布式系统中,网络时延波动较大。比如从加州节点到弗吉尼亚监控端,正常情况下心跳RTT为80毫秒,拥塞时甚至达200毫秒。

因此,如果超时设置得过于激进,这些短时延迟就会触发误报。

实际做法通常是测量网络实际RTT作为基线。许多系统遵循“超时至少为RTT的10倍”原则。例如平均RTT为10毫秒,则超时应设为至少100毫秒以应对波动。

def calculate_timeout(round_trip_time_ms, heartbeat_interval_ms):  # 超时设为RTT的10倍  rtt_based_timeout = round_trip_time_ms * 10  # 超时至少为心跳间隔的2-3倍  interval_based_timeout = heartbeat_interval_ms * 3  # 取二者的大值  return max(rtt_based_timeout, interval_based_timeout)

另一个重要点是“多次丢失心跳后才判断失效”。不要因为一次心跳丢失就立即标记节点死亡,通常要连续丢失几次后再判定。这样可以减少因网络丢包或暂时延迟引起的误报。

比如,我们每2秒发送一次心跳,要求累计丢失3次才判断失效,则节点需连续6秒无响应才被标记为失效。这在快速故障检测和容忍瞬时异常之间找到平衡。

推模式与拉模式的心跳模型

心跳可通过两种通信模式实现:推(Push)和拉(Pull)。

在推模式中,被监控节点会主动、定期地向监控系统发送心跳。节点主动报告自己的健康状态,通常只是启动一个后台线程定期发送心跳即可。

class PushHeartbeat:  def __init__(self, monitor_address, interval):  self.monitor_address = monitor_address  self.interval = interval  self.running = False  def start(self):  self.running = True  self.heartbeat_thread = threading.Thread(target=self._send_loop)  self.heartbeat_thread.daemon = True  self.heartbeat_thread.start()  def _send_loop(self):  while self.running:  try:  self._send_heartbeat()  except Exception as e:  logging.error(f"Failed to send heartbeat: {e}")  time.sleep(self.interval)  def _send_heartbeat(self):  message = {  'node_id': self.get_node_id(),  'timestamp': time.time(),  'status': 'alive'  }  requests.post(self.monitor_address, json=message)

推模式很适用许多场景,但也有局限。如果节点完全不可用或崩溃,自然也就无法发送心跳。而且,在有严格防火墙的网络里,被监控节点可能无法向外主动发包给监控系统。

  • Kubernetes节点心跳
  • Hadoop YARN的NodeManager向ResourceManager推送心跳
  • Celery与Airflow的worker向调度器推送心跳

在拉模式中,监控系统会以固定间隔主动查询各节点的健康状况,而不是被动等待心跳。监控端会“Ping”节点,询问“你还活着吗?”,节点需暴露健康检查接口,响应这些查询。

class PullHeartbeat:  def __init__(self, nodes, interval):  self.nodes = nodes  # 节点列表  self.interval = interval  self.health_status = {}  def start(self):  self.running = True  self.poll_thread = threading.Thread(target=self._poll_loop)  self.poll_thread.daemon = True  self.poll_thread.start()  def _poll_loop(self):  while self.running:  for node in self.nodes:  self._check_node(node)  time.sleep(self.interval)  def _check_node(self, node):  try:  response = requests.get(f"http://{node}/health", timeout=2)  if response.status_code == 200:  self.health_status[node] = {  'alive': True,  'last_check': time.time()  }  else:  self.mark_node_unhealthy(node)  except Exception as e:  self.mark_node_unhealthy(node)

拉模式让监控端有更大主动性,在某些场景下更稳定可靠。监控方主动发起连接,无对称性限制,因此在有网络割裂、NAT等环境里效果更好。但大集群下需定期轮询成百上千台节点,会加重监控端负载。

  • 负载均衡器会主动探测后端服务器
  • Prometheus定期拉取各目标端的metrics
  • Redis Sentinel主动发PING监控目标实例

实际上,许多真实系统都采用混合方案:节点主动推心跳,监控系统也会定期主动拉取关键节点状态,实现冗余,提高整体可靠性。

故障检测算法

基础的心跳机制虽然简单有效,但有区分真实故障与临时卡顿上的困难。这时更复杂的故障检测算法就派上用场了。

最简单的算法是固定超时。如果在指定超时时间内未收到心跳,节点被判为失效。虽然实现简单,但这种二元判定(活/死)在网络抖动较大的环境下容易出现误判。

class FixedTimeoutDetector:  def __init__(self, timeout):  self.timeout = timeout  self.last_heartbeats = {}  def is_node_alive(self, node_id):  if node_id not in self.last_heartbeats:  return False  elapsed = time.time() - self.last_heartbeats[node_id]  return elapsed < self.timeout

Phi累积故障检测

一种更智能的方式是 Phi累积故障检测(phi accrual failure detector),最早应用于Cassandra数据库。它不是输出活/死二元状态,而是输出一个连续变化的怀疑度(suspicion value)。Phi越高,节点越可能失效。

Phi值通过对历史心跳到达间隔做统计分析得出。算法维护一个滑动窗口记录最近心跳间隔,并基于这些数据估算下一个心跳应当到达的概率分布。当心跳延迟时,Phi值会逐渐升高,而不是一下跳到失效。

Phi值反映了节点失效的置信度。例如Phi=1约等于90%置信度,Phi=2约99%,Phi=3约99.9%。

基于Gossip协议的心跳

当分布式系统规模增长,中心化心跳监控会成为瓶颈。一台监控节点追踪上千服务器会形成单点故障,且极难扩展。这时Gossip协议成为解决方案。

Gossip协议将故障检测的责任分散到所有节点。每个节点定期与随机选取的少数节点交换心跳信息。随着时间推移,所有节点健康信息在集群中像“八卦”一样扩散。

基本的gossip算法:每个节点维护一个本地成员列表,记录所有节点及其心跳计数器。节点定期选择1到若干随机节点与之交换成员列表。收到对方列表时,将其与本地数据合并,保留最新的数据。

class GossipNode:  def __init__(self, node_id, peers):  self.node_id = node_id  self.peers = peers  self.membership_list = {}  self.heartbeat_counter = 0  def update_heartbeat(self):  self.heartbeat_counter += 1  self.membership_list[self.node_id] = {  'heartbeat': self.heartbeat_counter,  'timestamp': time.time()  }  def gossip_round(self):  # 更新本节点心跳  self.update_heartbeat()  # 随机挑选3个节点gossip  num_peers = min(3, len(self.peers))  selected_peers = random.sample(self.peers, num_peers)  # 发送成员列表  for peer in selected_peers:  self._send_gossip(peer)  def _send_gossip(self, peer):  try:  response = requests.post(  f"http://{peer}/gossip",  json=self.membership_list  )  received_list = response.json()  self._merge_membership_list(received_list)  except Exception as e:  logging.error(f"Failed to gossip with {peer}: {e}")  def _merge_membership_list(self, received_list):  for node_id, info in received_list.items():  if node_id not in self.membership_list:  self.membership_list[node_id] = info  else:  # 保留心跳计数更高的条目  if info['heartbeat'] > self.membership_list[node_id]['heartbeat']:  self.membership_list[node_id] = info  def detect_failures(self, timeout_seconds):  failed_nodes = []  current_time = time.time()  for node_id, info in self.membership_list.items():  if node_id != self.node_id:  time_since_update = current_time - info['timestamp']  if time_since_update > timeout_seconds:  failed_nodes.append(node_id)  return failed_nodes

Gossip协议消除了单点故障,因为每个节点都参与故障检测。无论集群规模如何,每个节点发送的消息数量都是常数,极具扩展性。同时,即使部分节点故障,信息仍可继续扩散。

Gossip协议当然也有复杂度:信息分散传播会造成所有节点获知故障有延迟,形成“最终一致性”模型,期间各节点感知的集群状态可能有偏差。协议总流量也会高于中心化方案,但因单条消息极小,通常无碍。

许多生产系统采用了Gossip方案。比如Cassandra每个节点每秒与至多3个随机节点gossip。每条gossip都携带心跳生成号和版本号等,以应对多种故障场景;还包括处理网络分区、避免脑裂的机制。

实现考虑因素

一个重要实现细节是传输协议的选择。

心跳应用TCP还是UDP?TCP保证可靠有序但开销高、握手慢;UDP则轻量快速但可能丢包、乱序。许多系统心跳选择UDP,因为偶发丢包可容忍,监控者只需连续多次无心跳才判定失效。

若心跳消息携带关键状态且必须到达,则通常优先选TCP。

另一个考量是网络拓扑。在跨数据中心系统中,网络延迟和可靠性因路径而异。同一数据中心的节点往返时间可能1毫秒,跨洲或跨国则达百毫秒以上。应当为本地与远程节点配不同超时。

class AdaptiveHeartbeatConfig:  def __init__(self):  self.configs = {}  def configure_for_node(self, node_id, location):  if location == 'local':  config = {  'interval': 1000,  # 1秒  'timeout': 3000,   # 3秒  'protocol': 'UDP'  }  elif location == 'same_datacenter':  config = {  'interval': 2000,  # 2秒  'timeout': 6000,   # 6秒  'protocol': 'UDP'  }  else:  # remote_datacenter  config = {  'interval': 5000,  # 5秒  'timeout': 15000,  # 15秒  'protocol': 'TCP'  }  self.configs[node_id] = config  return config

另一个重要的实现细节是确保心跳消息处理路径无阻塞操作。心跳处理应当迅速完成,较重的任务应交给线程池/异步任务。

资源管理同样关键。大规模系统如每个节点都单独维护线程或定时器,很快就会耗尽系统资源。应优先采用事件驱动或线程池,提升并发效率。连接池也能显著减少频繁连接建立的开销。

网络分区与脑裂问题

网络分区(Network Partition)是指网络中断,把集群分为两个或多个互相隔离的子集。每个分区内部的节点可通信,但彼此间不可达。

分区期间,每个分区中的节点都收不到来自其它分区的心跳,这会造成双方均认为对方故障。如未妥善设计,极易进入脑裂——两个分区各自独立运作、最终导致数据不一致或资源冲突。

比如有三节点分布于两数据中心的数据库集群。如果数据中心间链路断裂,各自形成孤岛。如果无保护机制,双边都可能选出新主节点并接受写入,造成分歧。

常见处理方法是基于法定人数(Quorum)。Quorum是执行特定操作所需的最小同意节点数。例如五节点集群,法定数为3,只有取得至少3节点支持才能进行主节点选举或接受写入。

分区时,只有法定数(如3)及以上的分区可正常工作;少数分区则检测到失去法定数,拒绝服务。

class QuorumBasedFailureHandler:  def __init__(self, total_nodes, quorum_size):  self.total_nodes = total_nodes  self.quorum_size = quorum_size  self.reachable_nodes = set()  def update_reachable_nodes(self, node_list):  self.reachable_nodes = set(node_list)  def has_quorum(self):  return len(self.reachable_nodes) >= self.quorum_size  def can_accept_writes(self):  return self.has_quorum()  def should_step_down_as_leader(self):  return not self.has_quorum()

真实世界中的应用

Kubernetes集群中,每个节点都运行kubelet Agent,定期向API Server汇报节点状态。默认每10秒推送一次状态。如果40秒内未收到节点汇报,就会将节点标记为NotReady。

Kubernetes还在Pod层面实现了liveness和readiness探针。Liveness探针检查容器是否正常运行,连续失败时K8s会重启容器;Readiness探针判断容器是否可以接收流量,未就绪时会把Pod移出服务端点。

apiVersion: v1  
kind: Pod  
metadata:  name: example-pod  
spec:  containers:  - name: app  image: myapp:latest  livenessProbe:  httpGet:  path: /healthz  port: 8080  initialDelaySeconds: 15  periodSeconds: 10  timeoutSeconds: 2  failureThreshold: 3  readinessProbe:  httpGet:  path: /ready  port: 8080  initialDelaySeconds: 5  periodSeconds: 5  timeoutSeconds: 2

Cassandra分布式NoSQL数据库采用Gossip式心跳维持集群成员关系。每个节点每秒最多与三个随机节点Gossip。消息中包含心跳生成号(节点重启即递增)和版本号(每轮Gossip递增)。

Cassandra利用Phi累积故障检测来判断节点下线,默认Phi阈值为8,即算法对节点失效置信度约为99.9999%时才认为节点失效。这种自适应方式使Cassandra可以可靠运行于各种网络环境下。

etcd分布式KV存储(Kubernetes的底座)在Raft一致性协议中用心跳。Raft Leader默认每100毫秒向Followers发送心跳。若Follower在选举超时时间(通常1000毫秒)内未收到心跳,会主动触发新一轮Leader选举。

注释

心跳在分布式系统中至关重要。从简单周期信号到高级自适应算法,心跳机制让系统能随时感知各组件健康,快速响应故障。

有效心跳设计的关键是权衡:快速故障检测需要高频心跳和激进超时,但带来带宽和误报压力;慢速检测节省资源、减少误判,但会延长故障暴露时间。

设计分布式系统时,务必及早考虑心跳机制。其间隔、超时、故障检测算法等的选择,会极大影响系统在异常下的行为。

无论构建何种系统,心跳始终是保持可靠性的关键工具。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/964820.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

“ArcGIS Pro制图-模型构建器-ArcPy开发-AI-无人机实操”系列培训班预告

“ArcGIS Pro制图-模型构建器-ArcPy开发-AI-无人机实操”系列培训班预告各企事业单位: 随着信息技术的飞速发展,地理信息系统(GIS)作为战略性新兴产业,近年来发展迅猛,预计未来几年产业规模将突破万亿元,成为全…

送女生礼物推荐:如何才能送到心坎里?

送女生礼物推荐:如何才能送到心坎里?为女生挑选礼物时,结合她的兴趣、性格和使用场景,同时融入2025年的流行趋势与实用细节,能让心意更显独特。以下是综合市场热度、情感价值和实用性的精选推荐,涵盖不同预算与场景:…

代码随想录Day9_字符串2

代码随想录Day9_字符串227. 移除元素 - 力扣(LeetCode)class Solution { public:int removeElement(vector<int>& nums, int val) {int Slow=0;for(int Fast=0;Fast<size(nums);Fast++){if(nums[Fast]!…

2025年西北地区新媒体运营公司最新TOP5评测:AI赋能陕西甘肃品牌增长新引擎

随着数字经济的蓬勃发展,新媒体运营已成为企业提升品牌影响力、实现流量变现的核心战略。本榜单基于技术实力、服务能力、行业经验、客户口碑四大维度,结合市场调研及行业数据分析,权威解析2025年西北地区五家领先的…

降本增效语音机器人评测榜:2025年大模型通话企业优选品牌

临近2025年的业务规划期,企业CFO和运营总监面临的首要压力,是如何在服务体验不降级的前提下,有效控制日益攀升的人力成本。根据行业数据显示,重复性、流程化的语音咨询占据了客服中心超过70%的资源。AI语音机器人(…

chrono模块

1.类型说明类型 说明 常用创建方法NaiveDate 仅日期(无时区) NaiveDate::from_ymd_opt(y, m, d)NaiveTime 仅时间(无日期) NaiveTime::from_hms_opt(h, m, s)NaiveDateTime 日期 + 时间(无时区) NaiveDateTime::…

Linux 中awk命令如何从文本中提取偶数列和奇数列

001、提取偶数列[root@pc1 test]# ls a.txt [root@pc1 test]# cat a.txt ## 测试文本 01 02 03 04 05 06 07 08 09 10 …

控制领域常用希腊字母表

序号 大写 小写 英文注音 国际音标注音 对应英文字母 中文读音 控制领域核心意义1 A α Alpha a:lfa a 阿尔法 姿态角(如横滚角辅助描述)、系统系数、比例系数2 B β Beta Beta b, v 贝塔 反馈系数、姿态角(辅助描述…

Windows 修改hosts不生效

路径 C:\Windows\System32\drivers\etc 问题 修改hosts原有ip不生效,ping命令与浏览器解析域名仍然使用的原来ip. 解决方法 受管理员权限影响(账号在管理员组中,但部分操作无法以管理员权限进行,也无法申请管理员权…

2025年陕西人工智能教育服务商最新TOP5评测:引领智能教育新时代

随着人工智能技术在教育领域的深度融合,市场对专业服务商的需求日益增长。本榜单基于技术实力、行业适配性、服务效能三大核心维度,结合行业权威数据与用户反馈,全面解析2025年陕西五大人工智能教育品牌综合实力,为…

重练算法(代码随想录版) day9 - 字符串part2

今日刷题量:4 当前刷题总量:45 Easy: 26 Mid: 18 Hard: 1 Day9 解题思想 1.整体翻转+部分翻转的多次翻转思想来原地解决字符串左/右旋问题 2.KMP算法思想,比较难理解,记忆getNext模版代码(next数组保持原样不-1版)…

Spring Boot 进阶:企业级性能与可观测性指南

扩展 Spring Boot 应用不仅仅是添加更多服务器。它关乎工程效率——在水平扩展之前,从现有硬件中榨取每一分性能。 在本文中,我们将探讨如何为高性能、云原生环境调优、扩展和分析 Spring Boot 应用——包含实践示例…

早就下好了IEDA,也算是差生文具多了

IEDA安装与试用 IEDA是什么——集成开发工具(IDE) IDEA官网:https://www.jebrains.com/进行简单的“hello,world!”运行

GLM4.6 测评

测试题: 8米长的竹竿能否通过高4米宽3米的门? ---》能,立体的情况下可以通过 GLM 4.6 非思考 ---》 失败GLM 4.6 思考 ---》 成功可以使用任何数字符号,但不能改变数字位置,怎样让这个等式成立: 6 5 4 1 = 24 -…

Pyinstaller - Python桌面应用打包的首选工具 - 详解

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

DNS record types: AAAA vs AA All In One

DNS record types: AAAA vs AA All In One DNS AAAA 记录将域名与 IPv6 地址进行匹配,这与 A 记录相似,后者对 IPv4 地址进行同样的操作。DNS record types: AAAA vs AA All In OneDNS AAAA 记录将域名与 IPv6 地址进…

关于Langchain更新解决Memory的引用

结合我之前的文章,关于调用memory的部分改成如下所示(老版本也可用,只不过不适配新版本的Langchain) langchain版本均采用最新版本,同时,Python版本要在3.10以上,详情可参考Langchain官方文档 from langchain_c…

win7 打开 icmp-ping 回显

需要放开防火墙 ICMP 规则选项: 执行以下命令:netsh firewall set icmpsetting 8type - ICMP 类型2 - 允许出站数据包太大。3 - 允许出站目标不可访问。4 - 允许出站源抑制。5 - 允许重定向。8 - 允许入站…

旋转矩阵在导航与机器人中的应用

在组合导航、无人机控制、机器人运动学等领域,旋转矩阵是连接参考坐标系与载体坐标系的核心工具,而欧拉角旋转顺序则决定了姿态描述的逻辑与精度。本文将聚焦最常用的三种旋转顺序,从基础原理、旋转矩阵推导到实际应…