NATS-server 的架构既不是传统的主从(Leader-Follower)模式,也不是严格的多主(Multi-Leader)模式,而是采用了一种去中心化的集群模式,所有节点在逻辑上是对等的(Peer-to-Peer)
-
核心架构特点
- 对等节点(Peer-to-Peer):
- NATS 集群中的节点通过 routes 相互连接(如配置文件中 cluster.routes 指定的其他节点地址)。
- 所有节点可以接收客户端连接并处理消息,没有固定的主从角色划分。
- JetStream 的扩展模式
- 集群会选举一个 “元数据 Leader”(基于Raft)管理流(Stream)和消费者的状态,这类似“主从”但仅限于元数据管理。
- 数据存储和消息路由仍由所有节点共同处理。
- 对等节点(Peer-to-Peer):
-
为什么不是多主?
- 无并行写入冲突:
- NATS 的消息路由基于主题而非数据分片,不需要多主架构的冲突解决机制(如向量时钟)。
- 设计目标不同:
- 多主架构(如数据库)需支持高并发写入,而 NATS 侧重高吞吐消息传递,通过主题和队列组实现负载均衡
- 无并行写入冲突:
etcd是主从的,为何每个节点都可以写呢?
- 主从架构的核心机制
etcd 集群中,只有一个 Leader 节点,其他节点为 Follower 或 Candidate(选举时)。Leader 负责所有写操作的核心处理,包括日志复制和提交,而 Follower 仅同步数据并参与一致性投票。
- Leader 选举:通过 Raft 算法实现,当 Leader 失效时,集群会重新选举新 Leader
- 数据同步:Leader 将写操作日志复制到多数节点(Quorum),确保数据一致性后才提交
- 为何每个节点都能接收写请求?
虽然写操作必须由 Leader 处理,但 etcd 的客户端可以将写请求发送到任意节点(包括 Follower),这是通过以下机制实现的:
- 请求转发:非 Leader 节点收到写请求后,会将其转发给 Leader 处理
- 透明性:客户端无需感知 Leader 位置,集群内部自动完成路由,简化了客户端逻辑
- 强一致性的保证
所有写操作必须经过 Leader 的 Raft 协议流程:
- 1)日志复制:Leader 将操作写入日志,并同步到多数节点
- 2)提交确认:多数节点确认后,Leader 提交日志并通知客户端
- 3)状态机应用:所有节点按相同顺序应用日志,确保数据一致
关键点:
- Quorum 机制:写操作需要超过半数节点确认(如 3 节点集群需 2 个确认)
- 顺序性:Raft 保证日志顺序一致,避免冲突
- 与纯主从架构的区别
传统主从系统(如 MySQL)通常要求客户端直接连接主节点,而 etcd 的改进在于:
- 客户端无感知:通过内部转发隐藏 Leader 切换细节
- 分布式协调:即使非 Leader 节点接收请求,最终仍由 Leader 统一处理,确保强一致性
- 读写分离
etcd 使用 Raft 共识算法,集群中只有一个 Leader 节点负责处理所有写请求(需要共识的操作),而 Follower 节点可以处理读请求(尤其是非强一致性读请求)。这种设计天然实现了读写分离:
- 写请求:必须由 Leader 节点处理,写入数据后会通过 Raft 协议同步到 Follower 节点
- 读请求:可以由 Follower 节点处理,但根据一致性要求不同,分为以下几种模式:
- 线性一致性读(Linearizable Read,默认模式):需要经过 Leader 确认(通过 ReadIndex 机制),确保读到最新数据
- 串行读(Serializable Read):直接从本地状态机读取数据,可能读到旧数据,但延迟低、吞吐高
- 最终一致性读:直接从任意节点(包括 Follower)读取,不保证数据最新
nats-jetstream和kafka的区别
-
物理存储
- kafka:
- 一个 Partition 对应一个目录(
- ) - 目录内包含多个分段文件 (Segment Files),通常是 .log(实际数据)和 .index(索引)文件对
- 消息是顺序追加到当前活跃 Segment 文件的末尾。当 Segment 达到一定大小或时间后,会被关闭并滚动创建新 Segment。
- 一个 Partition 对应一个目录(
- jetstream:
- 一个 stream 对应一个目录
- 也使用分段文件 (Segment Files) 来存储消息,通常是 .dat(数据)和 .idx(索引)文件
- 消息也是顺序追加到当前活跃 Segment。
- kafka:
-
分片
- kafka
- 默认分区策略:
- 无Key时:采用轮询(Round-robin)策略,消息均匀分布到所有分区
- 有Key时:Kafka使用MurmurHash2算法对keyBytes哈希,再对分区数取模,公式为:
partition=Utils.toPositive(Utils.murmur2(keyBytes))%numPartitions
- 发送消息时,通过ProducerRecord指定key:
ProducerRecord<String, String> record = new ProducerRecord<>("topic", "key", "value"); producer.send(record);
- 注意,分区数发生变化的时候,hash会出现问题。分区数在kafka服务端配置文件server.properties中定义
- 默认分区策略:
- jetstream:
- 消息通过 Subject 路由到 Stream,无内置 Key 分区逻辑
- 可通过多 Subject 模拟分区(如 orders.1, orders.2),但需手动管理
- kafka
-
消费的并发数:
- Kafka消费时的并发数由Topic的分区数决定,最大并发数 ≤ 分区数:若消费者数超过分区数,多余的消费者会闲置。
- jetstream: 支持多消费者并发消费同一流(Stream)。
本文发表于 0001-01-01,最后修改于 0001-01-01。
本站永久域名「 jiavvc.top 」,也可搜索「 后浪笔记一零二四 」找到我。