集群
单个 Wippy 节点是一个完整的运行时。集群将多个节点连接为一个协调系统:进程可以在任意节点上命名和访问,通过锁和组进行协调,并依赖共享的共识核心——无需更改代码中的生成、发送或监管方式。
集群是可选的(cluster.enabled)。本页介绍代码所见的模型;拓扑、配置和运维请参阅集群指南。
模型
节点通过八卦协议(SWIM)相互发现——节点通过指向一个种子节点加入,成员关系和故障检测无需协调者即可收敛。在八卦协议之上是一个规模固定的小型 Raft 核心:固定的投票节点集提供线性化共识,其余节点依托八卦协议运行。大多数节点从不承担共识负载,因此集群可以横向扩展,同时为需要单一真实来源的事物保留唯一权威。
集群为代码提供的能力归结为三个概念:命名、路由和协调原语。
命名
进程通常通过 PID 寻址。在集群中,进程还可以注册一个名称,并从任意位置通过该名称访问。唯一需要决策的是作用域——所需的一致性保证与成本之间的权衡:
| 作用域 | 可见范围 | 保证 | 适用场景 |
|---|---|---|---|
| Local | 本节点 | 即时,无需协调 | 节点本地辅助进程 |
| Eventual | 集群范围 | 经八卦协议收敛;冲突解决后通知失败方 | 服务、组和有限的在线状态名称 |
| Consistent | 集群范围 | 通过 Raft 实现线性化单例 | 标准的集群范围命名服务 |
| Strong | 集群范围 | 在 Consistent 基础上,名称激活前所有存活节点均须确认 | 控制面单例和锁 |
作用域形成严格排序——Local < Eventual < Consistent < Strong——对应一致性与成本的权衡轴。选择仍能满足所需保证的最弱作用域。名称通过 process.registry 注册,拥有该名称的进程退出(或其节点离开)时自动释放。
路由
命名只有在能可靠定位到正确进程时才有意义。路由将两者连接起来,遵循以下一致规则:
- 读取在本地完成。 每个节点从自身副本或八卦协议传播的缓存中解析名称——查找名称无需网络往返。这使解析保持高效,并在分区期间继续正常工作。
- 解析顺序固定。 名称按平面顺序解析——Consistent(Raft),然后 Eventual(八卦协议),然后 Local——因此集群范围的名称会遮蔽同字符串的本地名称。
- 写入路由到权威节点。 Consistent 或 Strong 注册经由 Raft 领导者处理;非领导者节点将写入转发并等待结果。提交后,活跃绑定通过八卦协议传播,使所有节点(包括不在 Raft 核心中的节点)此后都能在本地解析该名称。
- 消息通过 PID 路由。 当你向名称
process.send时,名称解析为 PID,中继将消息投递到拥有该进程的节点。无论进程位于本节点还是其他节点,代码的寻址方式相同——位置透明。
效果:注册和查找名称无需关心哪个节点持有权威,消息跨集群找到目标的方式与本地相同。
原语
集群提供一组小型构建块。每个原语均有独立页面完整介绍;此处说明它们支持构建的概念:
- 成员关系与身份 — 存活节点集合以及本节点的身份和角色。用于发现对等节点或分片工作。参见
system.cluster和system.node。 - 共识状态 — Raft 领导者、任期以及本节点角色,用于诊断和感知领导者的逻辑。参见
system.raft。 - 集群范围命名 — 按名称和作用域注册及解析进程,是所有其他功能的基础。参见
process.registry。 - 分布式锁 — 集群范围的互斥,最多一个持有者,持有者故障时自动释放。参见
system.lock。 - 进程组 — 加入命名组,向所有节点的所有成员广播,Erlang 风格。参见进程组。
这些原语刻意保持简单:锁和命名单例基于 Strong 命名作用域,进程组基于八卦协议,全部构建在上述相同的成员关系和路由之上——因此它们可以可预测地组合,而非各自发明独立的分发机制。