5.2 集群与高可用:因果一致性与读写路由 本节摘要:单机的天花板有两个:读写吞吐与硬件故障。Neo4j 的因果集群用"单主写 + 多从读 + 因果一致性"回答两者:写永远落在主库,读按路由分散到从库,书签机制保证"读到自己的写入"。本节拆解角色分工、路由协议、故障转移的表现,以及"什么时候真的需要集群"的冷静判断。 4.1 节埋了一个伏笔:书签机制为什么只在集群上才显出价值。本节把它接到底层数据流上。 一、角色分工:单主写,多从读 因果集群(Causal Cluster)的核心设计是只有一个主库(System)承担写,其余从库(Read Replica)异步复制并扩展读: 为什么写要收敛到单主?因为多主写会立刻撞上"同一条关系被两个主同时改"的合并难题。
本节摘要:单机的天花板有两个:读写吞吐与硬件故障。Neo4j 的因果集群用"单主写 + 多从读 + 因果一致性"回答两者:写永远落在主库,读按路由分散到从库,书签机制保证"读到自己的写入"。本节拆解角色分工、路由协议、故障转移的表现,以及"什么时候真的需要集群"的冷静判断。
4.1 节埋了一个伏笔:书签机制为什么只在集群上才显出价值。本节把它接到底层数据流上。
因果集群(Causal Cluster)的核心设计是只有一个主库(System)承担写,其余从库(Read Replica)异步复制并扩展读:
集群拓扑(典型三核 + 两副本): core-1(主) ←── 选举与复制(Raft) ──→ core-2、core-3 │ 异步事务日志流 ├──→ replica-1(只读) └──→ replica-2(只读) 写请求:任意入口进来,最终都路由到当前主库 读请求:按负载均衡散到各从库(或主库)
为什么写要收敛到单主?因为多主写会立刻撞上"同一条关系被两个主同时改"的合并难题。单主 + Raft 复制把一致性换算成了多数派确认:写事务在多数核成员落盘后才算提交——代价是写吞吐的上限就是主库的上限。
"因果"指的是事件之间的先后关系:你的写入是因,紧随其后的读取是果,系统承诺果不见到因就不算发生。机制上就是 4.1 的书签:
时间线: t1 客户端写入(主库提交) → 服务器返回书签 B7 t2 客户端带 B7 发起读 → 驱动检查:哪个实例的复制进度已越过 B7? → 只有越过的实例才有资格服务这次读 → 可能是主库,可能是已追上的从库
不带书签的读呢?驱动按负载均衡随便挑一台——你可能读到三秒前的数据。这在"用户刚改完头像,刷新却看到旧头像"的场景里就是工单。要因果就传书签,要吞吐就裸读,两个都想要就分级:写后立刻读的路径传书签,普通浏览页裸读。

主库宕机时,核成员重新选举(秒级)。应用侧的表现不是"全部报错",而是一小段时间内的可重试错误:
故障转移时间线: t0 主库失联 t0+1s 核成员选举完成,新主产生 └─ 期间写入收到 TransientError(会话过期/无可用主库) t0+2s 驱动刷新路由表,重试自动命中新主 t0+3s 一切恢复 —— 前提:写入都在托管事务里(4.1 的重试纪律)
这正是 3.2 与 4.1 反复强调"事务短小 + 重试幂等"的最后一层理由:故障转移的秒级抖动,靠应用层的重试机制吸收。任何"事务里做外部调用"(发邮件、调第三方接口)的写法,都会让这段抖动变成事故。
集群引入运维复杂度与授权成本,先过四道检验再决定:
| 检验项 | 单机够用 | 考虑集群 |
|---|---|---|
| 读吞吐 | 单机扛得住 | 读 QPS 明显超出单机 |
| 数据量 | 页缓存装得下 | 装不下且无法裁剪 |
| 可用性要求 | 分钟级可接受 | 秒级故障转移是刚需 |
| 写吞吐 | 写入洪峰可控 | 注意:集群不提升写吞吐 |
最后一行值得强调:因果集群不是写扩展方案——写永远收敛单主。写瓶颈的正确出路是分库(按业务域拆多个集群)或写入批优化,不是加从库。
驱动侧的读路由有讲究,默认策略不是唯一选项:
neo4j:// 协议下的读写路由: 写会话 → 主库 读会话 → 从库(默认负载均衡) 想读主库?neo4j:// 协议 + access mode 配置,或直接 bolt:// 指向主库
什么时候故意读主库?两类场景:写后立刻读且懒得传书签的简化路径;从库复制明显滞后但业务要求"最新数据"的查询。代价是把读压力重新压回主库——所以它应当是例外而非默认。
集群值班比单机多看三样东西:
| 信号 | 健康表现 | 异常动作 |
|---|---|---|
| 复制延迟 | 秒级以内 | 查网络与从库磁盘 IO |
| 选举频率 | 长期为零 | 频繁选举说明核成员不稳,查心跳配置 |
| 路由表一致性 | 驱动无"会话过期"告警 | 查路由协议与集群地址配置 |
复制延迟直接决定裸读从库的数据新鲜度,它是 5.2 开头那张因果图的"时间刻度"。延迟飙高的第一嫌疑人是批量导入——大批写数据时,从库追日志会明显吃力,这属于预期行为,导入窗口内避免强一致读即可。
问:从库能写吗?
不能。写请求会被路由协议送回主库。别试图在从库上跑写入——它只读,这是设计而非缺陷。
问:核成员数量怎么定?
奇数(3 或 5)。Raft 多数派要求下,3 核容忍 1 台故障,5 核容忍 2 台。核成员越多写确认越慢——多数核成员是"可用性保险",不是"写扩展手段"。
从单机迁到集群,应用代码几乎不用改(这就是驱动抽象的红利),但要检查四件事:
1. 连接串换成 neo4j:// 协议 + 全部核成员地址 (驱动自动发现路由,别只给一个地址) 2. 写事务全部走托管事务(execute_write)—— 故障转移的重试吸收依赖它 3. 写后读的路径补书签——4.1 的代码直接复用 4. 长事务排查:SHOW TRANSACTIONS 找出超长会话, 集群上长事务的代价被多数派确认放大
第 2 条是迁移质量的分水岭:单机上"从不重试也没事"的侥幸写法,上集群后第一次主库切换就会现形。迁移前先把单机环境的重试纪律补齐,切换那天才会平静。
问:从库挂了会怎样?
读流量被驱动重新均衡到其余实例,写路径完全不受影响——从库是"可随时替换的读扩展",这也是"多从读"设计的工程意义所在。
内核与部署讲完了。图攒多了之后干什么?下一节请出图算法。