在分布式系统中,多个节点同时提供服务时,数据一致性是核心挑战。在多个节点中,若其中一个节点的数据发生了修改,其他节点的数据都要进行同步。
一种比较简单粗暴的方法就是 集中式发散消息,简单来说就是一个主节点同时共享最新信息给其他所有节点,比较适合中心化系统。这种方法的缺陷也很明显,节点多的时候不光同步消息的效率低,还太依赖于中心节点,存在单点风险问题。
背景
分布式系统由多个节点组成,这些节点通常需要共享信息以保持一致性和协同工作。因此,不同的节点之间需要有一种机制来进行 数据/信息共享。
集中式发散消息
- 集中式发散消息 是一种简单粗暴的方式。在这种方式中,系统中有一个 主节点(也称为中心节点),它负责向其他所有节点广播最新的信息或数据。
- 在这种模式下,中心节点是 数据的源头,所有其他节点依赖这个中心节点来接收数据。
集中式发散消息的缺点
这种方法的缺陷也很明显:
- 效率低:如果节点数量非常多,中心节点需要将消息传递给每一个节点。这在节点多时会导致 消息传递效率低,并且可能造成网络瓶颈。
- 单点故障问题:所有的节点都依赖中心节点,若中心节点出现故障或者网络中断,整个系统的消息传递就会受到影响,造成 单点风险(即单个节点故障会影响整个系统的正常运行)。
Gossip协议的诞生
为了解决集中式发散消息的缺点,分布式系统引入了 Gossip 协议。Gossip 协议是一种 分散式发散消息 机制,它的基本思想是:
- 每个节点都参与信息的传播,节点之间相互传播数据。
- 信息的传播类似于“八卦、闲话、流言蜚语”(Gossip),节点间通过随机选择的方式与其他节点交换信息,最终全网节点都会共享到最新的信息。
Gossip 协议的优点
- 去中心化:不像集中式发散消息依赖于一个中心节点,Gossip 协议是 分散式的,每个节点都可以进行信息传播。
- 容错性好:如果某个节点故障,其他节点仍然可以通过其他途径获取到信息,因此避免了单点故障的风险。
- 扩展性好:Gossip 协议非常适合大规模的分布式系统,随着节点数量增加,信息传播的速度不会像集中式那样受到中心节点的限制。
Gossip协议介绍
Gossip 直译过来就是闲话、流言蜚语的意思。流言蜚语有什么特点呢?容易被传播且传播速度还快,你传我我传他,然后大家都知道了。
Gossip 协议 也叫 Epidemic 协议(流行病协议)或者 Epidemic propagation 算法(疫情传播算法),别名很多。不过,这些名字的特点都具有 随机传播特性 (联想一下病毒传播、癌细胞扩散等生活中常见的情景),这也正是 Gossip 协议最主要的特点。
Gossip 协议最早是在 ACM 上的一篇 1987 年发表的论文 《Epidemic Algorithms for Replicated Database Maintenance》中被提出的。根据论文标题,我们大概就能知道 Gossip 协议当时提出的主要应用是在分布式数据库系统中各个副本节点同步数据
正如 Gossip 协议其名一样,这是一种随机且带有传染性的方式将信息传播到整个网络中,并在一定时间内,使得系统内的所有节点数据一致。
在 Gossip 协议下,没有所谓的中心节点,每个节点周期性地随机找一个节点互相同步彼此的信息,理论上来说,各个节点的状态最终会保持一致。
下面我们来对 Gossip 协议的定义做一个总结:Gossip 协议是一种允许在分布式系统中共享状态的去中心化通信协议,通过这种通信协议,我们可以将信息传播给网络或集群中的所有成员
Gossip协议应用
NoSQL 数据库 Redis 和 Apache Cassandra、服务网格解决方案 Consul 等知名项目都用到了 Gossip 协议,学习 Gossip 协议有助于我们搞清很多技术的底层原理。
我们这里以 Redis Cluster 为例说明 Gossip 协议的实际应用。
我们经常使用的分布式缓存 Redis 的官方集群解决方案(3.0 版本引入) Redis Cluster 就是基于 Gossip 协议来实现集群中各个节点数据的最终一致性。
Redis Cluster
Redis Cluster 是一种基于分布式架构的 Redis 部署方式,它通过将数据分片到多个节点上来实现高可用和扩展性。在这种架构中,节点之间需要进行通信以同步状态和共享信息,Redis 使用 Gossip 协议 来保证节点间的有效通信。
Redis Cluster 是 Redis 的 分片集群部署方式,它使得 Redis 能够实现水平扩展,即通过将数据分散到多个节点上来提升集群的存储容量和处理能力。
1. 数据分片
- Redis Cluster 通过将数据 分片(Sharding)到不同的节点来扩展 Redis 的存储容量。每个节点只存储数据的一部分,而不是全部数据。
- Redis Cluster 使用 哈希槽(Hash Slots) 来决定数据的分布。共有 16384 个哈希槽(默认),每个节点负责一部分哈希槽。客户端将数据的键进行哈希计算,然后根据结果找到对应的哈希槽,再通过哈希槽定位到具体的节点。
2. 水平扩展
由于 Redis Cluster 支持分片,它可以通过增加更多的节点来 横向扩展(增加集群的节点数),进而提高集群的存储容量和吞吐量。这意味着,当系统负载增加时,可以简单地通过添加更多节点来扩展容量,而不需要替换现有的硬件。
3. 主从复制
- 在 Redis Cluster 中,每个分片不仅有一个 主节点(Master),还有多个 从节点(Slave)进行复制。主节点负责处理请求和存储数据,而从节点则复制主节点的数据,用于数据备份、读请求分担以及故障转移。
- 当主节点发生故障时,从节点可以自动提升为主节点,保证集群的高可用性。
4. 自动故障转移与重新分片
- Redis Cluster 支持 自动故障转移,如果某个主节点出现故障,集群会自动选举一个从节点提升为主节点,保证系统的高可用性。
- 重新分片(resharding)机制允许在集群中添加或移除节点时,自动将数据迁移到新的节点上,无需手动干预。
5. 无中心化设计
- 与传统的 Redis 部署不同,Redis Cluster 不依赖一个单独的 中心节点 来管理集群中的所有节点。每个节点在集群中都是平等的,它们都能处理客户端的请求。
总结
- Redis Cluster 是 Redis 的 分片集群部署方式,旨在提供 水平扩展性 和 高可用性。
- 它将数据分散到多个节点,通过哈希槽分配数据,同时支持 主从复制、自动故障转移 和 数据迁移,以保证数据的可靠性和系统的稳定性。
Redis Cluster 的节点间通信
Redis Cluster 是由多个 Redis 节点 组成的,每个节点负责一个数据分片。为了保持集群的健康和一致性,节点需要定期地交换状态信息,确保集群中的每个节点都能知道其他节点的情况。这就是 Gossip 协议 的作用,它提供了一种 分散式 的通信机制,确保集群节点之间能够共享和同步信息。
Gossip协议在Redis Cluster 中的作用
在 Redis Cluster 中,Gossip 协议用于 节点间的状态同步,使得集群中的每个节点都能够及时获知其他节点的状态,并对发生的事件做出相应的调整。每个 Redis 节点都维护着一份集群的状态信息,并根据这些信息做出决策,例如将某个节点标记为下线或故障。
节点间的状态同步
为了保证集群的健康和一致性,Redis Cluster 中的每个节点都需要知道其他节点的状态信息。这样一来,当集群中某个节点的状态发生变化时,其他节点能够及时了解并作出响应。
状态同步的内容包括:
- 节点是否在线
- 节点是否故障(例如,节点未响应或不可达)
- 节点是否处于下线状态(PFAIL)
- 节点是否已经下线(FAIL)
每个节点维护集群的状态信息
在 Redis Cluster 中,每个节点会维持一份关于整个集群的 状态信息。这份信息记录了集群中所有节点的健康状况、是否故障等信息。每个节点通过 Gossip 协议与其他节点通信,交换这些状态信息。具体来说,节点会定期向其他节点发送 PING 消息,以获取其他节点的健康状态。
例如,如果节点 A 发现节点 B 变得不可达或出现故障,它会将此信息通过 Gossip 协议广播给其他节点,通知其他节点更新对节点 B 的状态。这使得集群中的每个节点能够获得准确的集群信息。
做出决策(节点状态的调整)
当节点收到其他节点的状态信息后,它们可以基于这些信息做出决策。例如:
- 如果一个节点被标记为故障节点(FAIL),其他节点会进行相应的处理,如将其排除在外,避免将请求发送到故障节点。
- 如果节点的状态是 PFAIL(疑似下线),其他节点可能会开始 故障转移,将该节点的任务迁移到其他健康节点上。
- 如果某个节点从集群中下线,集群会通过 Gossip 协议通知其他节点,让集群调整数据分布和路由信息。
Redis Cluster 中常见的 Gossip 消息
Redis Cluster 中的节点会交换几种不同类型的 Gossip 消息:
1. MEET 消息
- MEET 消息用于 将新的 Redis 节点加入集群。
- 在 Redis Cluster 中,如果要将一个新的节点添加到集群,可以通过执行
CLUSTER MEET ip port
命令将新的节点 IP 和端口信息发送给集群中的某个节点。 - 该节点会广播一条 MEET 消息,通知其他节点有新的节点加入集群。
2. PING/PONG 消息
- PING 和 PONG 消息用于 节点间的状态检查和交换信息。
- 每个节点定期向集群中的其他节点发送 PING 消息,询问对方的健康状态。
- 接收到 PING 消息的节点会发送 PONG 消息作为回应,表示自己仍然在线并且可以正常工作。
- 这种机制用于检查节点的存活状态,并帮助集群监控节点是否出现故障。
3. FAIL 消息
- FAIL 消息用于 标记节点为故障节点。
- 当一个节点(比如 A)发现另一个节点(比如 B)可能出现故障时,A 节点会向集群广播一条 FAIL 消息,告知其他节点将 B 节点标记为故障节点。
- 在 Redis Cluster 中,如果一个节点(B)被标记为 PFAIL(疑似下线),并且在一段时间内没有恢复,那么集群中的半数以上节点会将其正式标记为 FAIL。
- 这种机制帮助集群快速识别故障节点并采取相应的措施,例如将故障节点的数据迁移到其他正常节点,保证数据的高可用性。
这张图片展示了 Redis Cluster(Redis 集群)的架构。以下是对其的详细解释:
- 组件说明:
- Clients:表示客户端,是与 Redis 集群进行交互的主体,可以向集群发起读写请求。
- Redis Master:图中红色标识的节点(M1、M2、M3),为主节点。主节点负责处理客户端的读写操作,能够对数据进行写入和读取处理。
- Redis Slave:图中灰色标识的节点(S1、S2、S3),为从节点。从节点主要用于处理客户端的读操作,同时通过主从复制机制(图中黑色箭头表示)同步主节点的数据,当主节点故障时,部分从节点有可能被选举为新的主节点。
- 通信方式:
- Redis gossip protocol:图中虚线表示节点之间通过 gossip 协议进行通信。每个 Redis 节点都会通过集群总线(cluster bus)与其他所有节点进行通信,以此来交换节点状态、槽(slot)信息等,从而维护集群的状态和数据分布的一致性。
- 客户端操作:
- 如右下角文字所述,客户端的写操作会发送到主节点;读操作既可以发送到主节点,也可以发送到从节点 。 这种设计可以在一定程度上分散读请求的压力,提高系统的并发处理能力。
有了 Redis Cluster 之后,不需要专门部署 Sentinel 集群服务了。Redis Cluster 相当于是内置了 Sentinel 机制,Redis Cluster 内部的各个 Redis 节点通过 Gossip 协议共享集群内信息
在 Redis Cluster 中,由于它内置了节点间的通信和状态同步机制(通过 Gossip 协议),每个 Redis 节点都能及时了解集群内其他节点的健康状态。如果某个节点发生故障,集群会自动进行故障转移,将一个从节点提升为主节点,确保系统的高可用性。这样一来,Redis Cluster 已经具备了类似 Redis Sentinel 的功能,不再需要单独部署 Sentinel 集群 来进行监控和故障恢复。
- Redis Sentinel 主要负责监控 Redis 主从节点的状态,处理故障转移和高可用性。
- Redis Cluster 则提供了 数据分片 和 高可用性,并且通过 Gossip 协议实现节点之间的状态同步和故障检测,自动执行故障转移,因此它不再依赖外部的 Sentinel 集群。
简而言之,Redis Cluster 自带了类似 Sentinel 的高可用性功能,所以你不需要单独配置 Redis Sentinel。
Gossip协议消息传播模式
Gossip 设计了两种可能的消息传播模式:反熵(Anti-Entropy) 和 传谣(Rumor-Mongering)。
反熵
根据维基百科:
熵的概念最早起源于物理学,用于度量一个热力学系统的混乱程度。熵最好理解为不确定性的量度而不是确定性的量度,因为越随机的信源的熵越大。
在这里,你可以把反熵中的熵理解为节点之间数据的混乱程度/差异性,反熵就是指消除不同节点中数据的差异,提升节点间数据的相似度,从而降低熵值。
具体是如何反熵的呢?集群中的节点,每隔段时间就随机选择某个其他节点,然后通过互相交换自己的所有数据来消除两者之间的差异,实现数据的最终一致性。
主要步骤
- 节点选择:每个节点会随机选择一个目标节点进行数据交换。这个选择并不是固定的,而是基于某些策略(如随机选择)。
- 数据交换:选定的节点之间会交换数据,以更新彼此的数据副本。
- 数据一致性:通过不断地交换信息,系统中的各个节点的数据最终趋于一致。
在实现反熵的时候,主要有推、拉和推拉三种方式:
- 推(Push)方式:节点主动将自己的数据发送给目标节点,修复目标节点的数据。
- 例如,节点 A 将自己存储的数据发送给节点 B,B 用 A 的数据更新自己的副本。
- 拉(Pull)方式:节点主动向目标节点请求数据,拉取对方的数据来修复自己的副本。
- 例如,节点 A 向节点 B 请求其存储的数据,A 使用 B 的数据更新自己的副本。
- 推拉(Push-Pull)方式:节点与目标节点进行双向数据交换,既推送自己的数据,又拉取对方的数据,以实现双方数据一致性。
- 例如,节点 A 和节点 B 相互交换数据,A 和 B 都更新自己的副本。
应用场景:
- 在某些场景下,不采用完全随机的节点进行反熵,而是可以设计成一个 闭环,使得各节点在一个确定的时间范围内实现数据一致性。这种方式减少了随机性的影响,可以更高效地实现数据的一致性。
- InfluxDB 就是采用了闭环方式来实现反熵,保证了集群数据的最终一致性。
- 节点 A 推送数据给节点 B,节点 B 获取到节点 A 中的最新数据。
- 节点 B 推送数据给 C,节点 C 获取到节点 A,B 中的最新数据。
- 节点 C 推送数据给 A,节点 A 获取到节点 B,C 中的最新数据。
- 节点 A 再推送数据给 B 形成闭环,这样节点 B 就获取到节点 C 中的最新数据。
优点:
- 最终一致性:反熵传播可以确保系统中的所有节点最终达成数据一致性。
- 减少差异性:通过不断交换数据,节点之间的差异性会逐渐减少,提升系统的整体一致性。
- 灵活性:节点选择的方式灵活,可以根据实际场景设计优化策略。
缺点:
- 带宽消耗:由于每次交换数据时,可能会涉及到大量的数据传输,可能消耗大量带宽。
- 延迟:反熵传播是通过定期的交换来完成数据同步,可能会导致数据一致性更新的延迟,尤其在节点数目较多时。
谣言传播
定义:
谣言传播是一种轻量级的消息传播机制,适用于节点数量庞大的分布式系统。在谣言传播模式下,节点之间只传播新增或变化的数据,而不是整个数据集,目的是减小信息传播的开销和带宽消耗。
工作原理:
在谣言传播模式中,节点之间不进行全量数据交换,而是只传播增量数据(即新增或修改的数据)。每个节点会将自己所知的变化或新增的数据以“谣言”的形式传播给其他节点。其他节点接收到这些“谣言”后,会根据这些信息更新自己的数据。
主要步骤:
- 节点选择:每个节点会向其他节点传播自己所知的新增或变更的数据,这通常是通过 传播谣言 的方式进行的。
- 数据传播:节点通过传递包含新增数据的消息,通知其他节点。其他节点收到这些信息后,更新自己的副本。
- 数据一致性:随着时间的推移,节点间会传播更多的变更信息,最终所有节点都会同步到一致的数据状态。
优点:
- 低开销:由于只传播新增的数据,而不是全量数据,所以每次消息传播的开销较低,特别适合大规模系统。
- 高效性:适合于节点数量较多且节点变化频繁的场景,谣言传播能够高效地将信息传递给系统中的所有节点。
- 去中心化:没有中心节点,所有节点都是平等的,信息可以迅速传播到集群中的每个节点。
缺点:
- 一致性延迟:由于仅传播增量数据,可能需要多个传播周期才能确保数据一致性,因此一致性收敛可能需要更长时间。
- 数据冗余:在某些情况下,多个节点可能会同时传播相同的数据,造成冗余的消息传输。
应用场景:
- 节点数量较多的场景,尤其适用于 节点动态变化频繁 或 节点数目庞大的分布式系统,例如社交网络、在线游戏服务器等。
- 适用于对一致性要求稍微宽松的场景,能够容忍较短时间内的一致性不完全。
总结与对比
特性 | 反熵传播(Anti-Entropy) | 谣言传播(Rumor-Mongering) |
---|---|---|
传播内容 | 所有数据 | 仅新增或修改的数据 |
适用场景 | 小规模节点集群或对一致性要求严格的场景 | 大规模节点集群,节点动态变化频繁 |
一致性收敛时间 | 较快,较容易保证最终一致性 | 较慢,可能需要多个周期才能达到一致性 |
带宽消耗 | 较高,因需要传输所有数据 | 较低,因只传输新增数据 |
优点 | 数据最终一致性高,适合小规模节点集群,能够较快速同步数据 | 节省带宽,高效适应大规模动态系统 |
缺点 | 带宽消耗大,延迟较高,适用于节点较少的场景 | 一致性延迟较大,可能导致数据冗余,适用于数据一致性要求较低的场景 |
总结:
- 反熵传播适合于节点数目较少且对数据一致性要求较高的场景,它能高效地消除节点之间的数据差异,但可能消耗较多带宽,且一致性收敛较慢。
- 谣言传播则适合于节点数量较多、节点变化动态的系统,它能够高效地将新增数据传播给所有节点,带宽消耗低,但一致性收敛较慢,适合于数据一致性要求不那么严格的场景。
Gossip协议的优势与缺陷
优势
-
理解简单:
- 与其他复杂的分布式协议相比,Gossip 协议的设计和实现相对简单。它的核心思想是节点之间定期交换状态信息,通过多轮传播达到最终一致性,而没有复杂的协调机制。因此,开发者更容易理解和实现。
-
容忍节点的增加、减少、宕机或重启:
- Gossip 协议本身是 去中心化的,也就是说没有一个固定的中心节点来负责信息传播,每个节点都是平等的。
- 如果有新的节点加入或现有节点宕机、重启,只要在理想情况下(节点能够正常通信),这些节点最终都会和其他节点同步状态。Gossip 协议的这种容错性和灵活性使得它特别适用于动态变化的分布式环境。
-
速度相对较快:
- 在节点数量较多的情况下,Gossip 协议通过 多播(每个节点向多个节点传播信息)而不是通过一个中心节点传播消息,这种方式比传统的中心化信息传播要更快。每个节点都能并行地向其他节点发送信息,从而加速了信息的扩散过程。
缺陷
-
消息传播需要多轮才能传播到整个网络:
- Gossip 协议是 最终一致性 的,它并不保证即时一致性。即,节点状态的传播需要通过多轮的信息交换才能传播到整个网络。这意味着,某些节点在短时间内可能会处于不一致的状态,直到信息最终传递到所有节点。
- 因此,不一致性 是 Gossip 协议的一个固有问题,无法完全避免,只有在一段时间内信息能达到一致性。
-
不允许存在恶意节点(拜占庭将军问题):
- 在 Gossip 协议中,拜占庭将军问题(即网络中存在恶意节点,可能会发送错误或虚假的消息)会导致信息传播的混乱。如果有恶意节点在 Gossip 协议中传递错误信息,可能导致整个系统的不一致。
- 因此,Gossip 协议假设所有节点都是 诚实 的,并且不容忍恶意节点的存在。
-
可能会出现消息冗余:
- 由于 Gossip 协议的消息传播是随机的,节点之间并不总是按最优路径传播信息。这样,在传播过程中,可能会出现重复的信息。一个节点可能会收到相同的消息多次,造成 冗余。
- 这种冗余可能会导致 带宽浪费 和 资源消耗,尤其在节点数目非常多的情况下,冗余信息可能成为一个问题。
Go语言实现简单的Gossip协议模拟
package main
import (
"encoding/json" // 用于序列化和反序列化 JSON 数据
"fmt" // 用于格式化输出
"math/rand" // 用于生成随机数
"net/http" // 用于实现 HTTP 服务
"sync" // 用于实现并发控制
"time" // 用于时间相关操作
)
// 节点配置结构体,表示一个节点的基本信息
type Node struct {
Name string // 节点的名称
State map[string]string // 节点的状态(例如版本号等数据)
Members map[string]string // 已知的其他节点(键为地址,值为名称)
ListenAddr string // 当前节点监听的地址
mu sync.Mutex // 用于保护状态的并发访问
}
// 创建一个新节点
func NewNode(name, addr string) *Node {
return &Node{
Name: name, // 设置节点名称
State: make(map[string]string), // 初始化状态信息
Members: make(map[string]string), // 初始化成员列表
ListenAddr: addr, // 设置节点监听地址
}
}
// 启动HTTP服务,处理来自其他节点的Gossip请求
func (n *Node) Start() {
http.HandleFunc("/gossip", n.handleGossip) // 当接收到 /gossip 请求时调用 handleGossip 方法
go http.ListenAndServe(n.ListenAddr, nil) // 启动HTTP服务,异步执行
}
// 处理Gossip请求,接收其他节点的状态,并与当前节点的状态合并
func (n *Node) handleGossip(w http.ResponseWriter, r *http.Request) {
var remoteState map[string]string
// 解码来自请求的状态数据
if err := json.NewDecoder(r.Body).Decode(&remoteState); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) // 解码错误时返回 400 错误
return
}
n.mu.Lock() // 锁定节点状态,以便并发安全地修改
defer n.mu.Unlock() // 确保在函数结束时释放锁
// 合并远程节点的状态
for k, v := range remoteState {
// 如果当前节点的状态中没有这个数据,或者远程数据的版本更高,更新状态
if current, exists := n.State[k]; !exists || v > current {
n.State[k] = v
}
}
}
// 随机选择一个其他节点,周期性地向其发送Gossip消息
func (n *Node) Gossip() {
for {
time.Sleep(2 * time.Second) // 每隔2秒进行一次状态传播
n.mu.Lock() // 锁定节点状态
members := make([]string, 0, len(n.Members)) // 创建一个空切片存储已知节点的地址
for addr := range n.Members {
members = append(members, addr) // 将成员地址添加到切片中
}
stateCopy := make(map[string]string, len(n.State)) // 创建当前状态的副本
for k, v := range n.State {
stateCopy[k] = v // 复制当前状态
}
n.mu.Unlock() // 释放锁
if len(members) == 0 {
continue // 如果没有已知成员,跳过本次循环
}
// 随机选择一个目标节点
target := members[rand.Intn(len(members))]
// 启动一个 goroutine 向目标节点发送Gossip消息
go func() {
resp, err := http.Post("http://"+target+"/gossip", "application/json",
json.NewEncoder(struct{ Data map[string]string }{Data: stateCopy})) // 向目标节点发送POST请求,携带状态数据
if err == nil {
defer resp.Body.Close() // 确保响应体在处理后关闭
}
}()
}
}
// 主程序,创建并启动多个节点
func main() {
rand.Seed(time.Now().UnixNano()) // 初始化随机数生成器
// 创建三个节点
nodes := []*Node{
NewNode("Node1", ":8080"), // 创建并初始化节点1
NewNode("Node2", ":8081"), // 创建并初始化节点2
NewNode("Node3", ":8082"), // 创建并初始化节点3
}
// 初始化每个节点的成员列表
for _, node := range nodes {
for _, other := range nodes {
if other.ListenAddr != node.ListenAddr {
node.Members[other.ListenAddr] = other.Name // 将其他节点加入当前节点的成员列表
}
}
node.Start() // 启动节点的HTTP服务
go node.Gossip() // 启动节点的Gossip机制
}
// 在Node1上设置初始值
nodes[0].mu.Lock()
nodes[0].State["version"] = "1.0.0" // 设置Node1的状态为version: 1.0.0
nodes[0].mu.Unlock()
// 监控各个节点的状态
for {
time.Sleep(3 * time.Second) // 每3秒打印一次状态
for _, node := range nodes {
node.mu.Lock() // 锁定节点状态
fmt.Printf("[%s] State: %v\n", node.Name, node.State) // 打印当前节点的状态
node.mu.Unlock() // 释放锁
}
fmt.Println("------------------") // 分隔符,便于输出查看
}
}
输出示例:
[Node1] State: map[version:1.0.0]
[Node2] State: map[]
[Node3] State: map[]
------------------
[Node1] State: map[version:1.0.0]
[Node2] State: map[version:1.0.0]
[Node3] State: map[version:1.0.0]
------------------
注释说明
-
Node 结构体:
Node
结构体表示每个节点的基本配置,包括节点名称、节点状态(State
)、已知的成员(Members
)、监听的地址等。
-
NewNode 函数:
- 创建一个新的节点并初始化它的状态和成员列表。
-
Start 方法:
- 启动节点的 HTTP 服务,并指定当接收到
/gossip
请求时调用handleGossip
方法处理。
- 启动节点的 HTTP 服务,并指定当接收到
-
handleGossip 方法:
- 处理其他节点的 Gossip 请求,接收远程节点的状态并将其与当前节点的状态合并。只有在远程节点的数据版本比当前节点更高时才会更新。
-
Gossip 方法:
- 每隔 2 秒钟,节点随机选择一个成员节点,通过 HTTP 请求将自己的状态发送给它。这是节点与其他节点交换状态的核心逻辑。
-
main 函数:
- 创建并初始化 3 个节点,每个节点都会启动 HTTP 服务并进行 Gossip。初始化时,第一个节点 (
Node1
) 设置一个初始的版本信息。 - 每 3 秒钟打印每个节点的状态信息,以监控状态同步情况。
- 创建并初始化 3 个节点,每个节点都会启动 HTTP 服务并进行 Gossip。初始化时,第一个节点 (