
用 Neo4j 社区版(Community Edition)跑生产的同学都绕不开一个尴尬:社区版没有集群能力 。因果集群(Causal Clustering)、副本、自动故障转移这些能力全部锁在企业版许可里,社区版只有"单进程 + 本地存储"一种形态------进程挂了服务就断,磁盘坏了数据就没了。图数据库在生产上往往承载的是配置管理、拓扑关系、依赖图谱这类数据(我在《Neo4j 内嵌模式实战》里对比过两个版本的集群能力差异),看着写多读少、丢了似乎也能重建,真到磁盘报废那一刻,"重新灌一遍数据"的代价和风险都会被打脸。
买企业版当然是最省心的路,但不一定有预算。本文给出一套零许可成本的双节点冷备方案 :用 DRBD 在块设备层做同步复制 ,用 Keepalived 管理虚拟 IP ,两台机器一主一备,主节点故障时按 SOP 把服务切到备节点,RPO=0(同步复制协议下不丢数据)、RTO=分钟级。方案来自笔者的生产环境实践(承载配置管理类图数据,Neo4j 社区版 4.4.13),文中所有命令与输出均为生产验证过的真实流程,主机名、IP 已替换为示例。
环境说明:2 节点(neo4j-node1/neo4j-node2,示例网段 192.168.10.x),DRBD 双主备块复制 + Keepalived VRRP 虚拟 IP(VIP 示例 192.168.10.20),Neo4j 社区版 4.4.13 部署在 DRBD 挂载目录
/data/drbd下,DRBD 资源名neo4j、块设备/dev/drbd0。
一、方案选型:社区版补高可用的三条路
动手前先想清楚"高可用"要解决到什么程度。社区版补 HA 常见三条路线:
| 路线 | 思路 | RPO | RTO | 主要代价 |
|---|---|---|---|---|
| 企业版因果集群 | 官方多副本 + 自动选主 | 0 | 秒级 | 许可费 |
| 应用层双写 | 应用同时写两个社区版实例 | 可能不一致 | 秒级 | 开发改造大,一致性自己兜 |
| 存储层复制(本文) | DRBD 块级同步 + 冷备切换 | 0(协议 C) | 分钟级 | 两台机器 + 一套 SOP |
三条路里本文这条最适合"读多写少、能接受分钟级中断、但一分钱数据都不能丢"的场景,理由有三:
- 对 Neo4j 零侵入:应用照常连 bolt 端口,感知不到底下是 DRBD;不需要改一行代码;
- 一致性由内核态保证:DRBD 是块设备层的复制,复制的是"字节流",Neo4j 的任何存储格式变更(升级版本、改配置)都天然被覆盖,不存在"应用层双写漏了一张表"这类问题;
- 成本只有两台机器:对已经为生产准备了备份机的现场,几乎零新增成本。
它的短板也必须先说透:这是冷备,不是自动故障转移。备节点平时不跑 Neo4j 进程,切换靠 SOP(人工或脚本触发),RTO 取决于响应速度;想要秒级自动切换,请直接走企业版。想清楚这一点再往下看,能避免后面最大的坑------为了"自动化"把冷备改成双活,最后数据分叉。
二、架构:双节点一主一备,VIP 只有一份
text
应用端(bolt / http)
│
▼
VIP:192.168.10.20(Keepalived/VRRP)
┌─────────┴─────────┐
│ 当前漂移在 node1 │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ neo4j-node1 │ │ neo4j-node2 │
│ 角色:Primary │ │ 角色:Secondary│
│ ┌──────────┐ │ │ ┌──────────┐ │
│ │ Neo4j 进程│ │ │ │ 无进程 │ │
│ │ (对外服务)│ │ │ │ (待命) │ │
│ └──────────┘ │ │ └──────────┘ │
│ /data/drbd │ │ /data/drbd │
│ drbd0 已挂载 │ │ drbd0 未挂载 │
└──────┬───────┘ └───────┬──────┘
└──── 块级同步复制 ────┘
(Primary → Secondary)
三个组件各司其职,边界非常清晰:
| 组件 | 职责 | 关键约束 |
|---|---|---|
| DRBD | 把主节点的块设备变更同步到备节点 | 同一资源任一时刻只能有一个 Primary;只有 Primary 能挂载文件系统 |
| Neo4j 社区版 | 单进程对外服务,数据目录落在 DRBD 挂载点 | 只在 Primary 节点启动;备节点进程不启动 |
| Keepalived | VRRP 协议维护虚拟 IP,让应用永远连一个固定地址 | VIP 跟着"服务所在节点"走;切换时配合 DRBD 提升 |
这里有个容易混淆的点值得单独强调:Keepalived 的 VIP 漂移是自动的,DRBD 的 Primary 提升不是 。Keepalived 只认网络心跳,主节点 Neo4j 进程挂了但机器还活着时,VIP 不会动(这就是纯 VRRP 方案对"进程级故障"无感的原因)。所以本方案的正确姿势是:VIP 当"稳定的接入地址"用,故障切换时由人(或脚本)先完成 DRBD 提升 + Neo4j 启动,再让 VIP 过去,顺序不能反------VIP 先漂过去而服务没起来,应用会连上一个"空壳节点"。
三、DRBD 三分钟速览:块级复制与状态机
没接触过 DRBD 的同学只需要建立三个概念,就够看懂后面的 SOP 了。
1. 它复制的是块设备,不是文件。 DRBD 在内核里给两台机器各虚拟出一块设备(/dev/drbd0),Primary 上的每一次写盘都会按协议同步到 Secondary 的对应块设备。对上层(文件系统、Neo4j)完全透明------/dev/drbd0 用 ext4/XFS 格式化并挂载到 /data/drbd 后,Neo4j 把它当一块普通磁盘用。
2. 角色与数据状态是两回事。 drbdadm status 的输出里,role 是角色(Primary 能读写挂载,Secondary 只接收复制),disk/peer-disk 是两份数据各自的状态。健康的同步副本是 UpToDate;初次建副本时备端是 Inconsistent,同步完成后转为 UpToDate;断连后重连,数据落后的一方会经历 Inconsistent → SyncTarget → UpToDate 的回补过程。
3. 复制协议决定 RPO。 协议 A(异步)、B(内存半同步)、C(磁盘同步):生产配置类数据无脑选协议 C------本地和远端都落盘才向应用返回写成功,网络抖动会拖慢写入,但任何时刻备端的数据都不落后。本文场景写入量小,协议 C 的代价可以忽略。
四、日常启停 SOP:顺序就是生命线
以下命令全部来自生产环境,逐条讲解。这套 SOP 的顺序经得起推敲,不要凭直觉调整------每一步都依赖上一步的状态。
4.1 启动:DRBD → 提主 → 挂载 → Neo4j → Keepalived
第一步,起 DRBD 资源并确认状态。 节点重启后 DRBD 资源不会自动就绪,先在每台节点查状态:
bash
[root@neo4j-node1 ~]# drbdadm status neo4j
neo4j: No such resource
Command 'drbdsetup-84 status neo4j' terminated with exit code 10
注意这个输出不是报错,是"资源未启动"的正常形态 ------exit code 10 配合 No such resource,表示该节点的 DRBD 资源还没 attach。此时启动资源:
bash
drbdadm up neo4j
两台节点都 up 之后再看状态,健康的形态长这样:
text
[root@neo4j-node1 ~]# drbdadm status neo4j
neo4j role:Primary
disk:UpToDate
peer role:Secondary
replication:Established peer-disk:UpToDate
三个判据缺一不可:本端 role:Primary、disk:UpToDate,对端 peer-disk:UpToDate,以及 replication:Established ------最后一行说明两端的复制链路已经协商建立。如果显示 StandAlone 或 Off,先排查两端 network 配置与防火墙,链路没 Established 就继续往下走,等于裸奔。
第二步,若本端不是 Primary,强制提主并挂载。 计划内重启后偶尔会遇到本端角色停在 Secondary(比如另一端先起来占了主),在确认对端没有活的 Neo4j 进程的前提下提主:
bash
drbdadm --force primary neo4j
mount /dev/drbd0 /data/drbd
--force 在这里的作用是"对端不可达/未就绪时也允许提升"。这个参数是把双刃剑,唯一安全的用法是"确认对端服务已停、仅剩副本在同步"的场景,脑裂场景下它是灾难放大器,第六节展开。
第三步,启动 Neo4j。 数据目录就在挂载点上:
bash
cd /data/drbd/neo4j-community-4.4.13/bin
./neo4j start
第四步,起 Keepalived 并验证 VIP。 每台机器都要执行(VRRP 是双方协作的协议):
bash
service keepalived start
验证虚拟 IP 确实落在本节点:
bash
ip addr | grep 192.168.10.20
能 grep 到 VIP 即对外恢复服务。最后用应用侧连一下 bolt 端口做端到端确认。
4.2 停止:完全反向,先摘流量再动存储
计划内停机(维护、迁移)按启动的严格逆序执行,每一步等上一步完成:
bash
# 1. 先摘接入层:停本节点 keepalived,VIP 漂走或摘除,应用不再进来
service keepalived stop
# 2. 停 Neo4j 进程,确认端口释放
/data/drbd/neo4j-community-4.4.13/bin/neo4j stop
# 3. 卸载 DRBD 挂载点
umount /data/drbd
# 4.(如需维护存储)降级并停资源
drbdadm secondary neo4j
drbdadm down neo4j
为什么顺序不能乱:带着 Neo4j 进程直接 drbdadm down,等价于抽走一块正在写的磁盘 ------轻则中断时留下需要恢复的事务日志,重则存储文件损坏。先停进程再卸载,让 Neo4j 走干净的 shutdown(checkpoint、释放 store 文件锁),DRBD 上的最后一批写也会被协议 C 确认到对端。这套"先摘流量、再停进程、后动存储"的次序,和笔者之前整理的《Hadoop 集群日常运维 SOP》里"ZK 先于 Hadoop"是同一个道理:依赖方的状态永远比被依赖方先动。
五、故障切换与回切:一次真实的接管流程
5.1 主节点宕机,备节点接管
假设 neo4j-node1 整机失联(断电、内核 panic)。在 neo4j-node2 上执行:
bash
# 第零步:确认主节点真的下线了------ping 不通、管理口也连不上。
# 看得到主节点 console 就不要走下面的流程,这是防脑裂的根闸。
# 第一步:起 DRBD 并确认副本状态
drbdadm up neo4j
drbdadm status neo4j
# 关注两点:本端 disk:UpToDate;对端显示 Offline(连接不上,符合预期)
# 第二步:确认本端数据最新后提升为主并挂载
drbdadm --force primary neo4j
mount /dev/drbd0 /data/drbd
# 第三步:启动 Neo4j
cd /data/drbd/neo4j-community-4.4.13/bin && ./neo4j start
# 第四步:Keepalived 接管 VIP
service keepalived start
ip addr | grep 192.168.10.20
五步做完,服务恢复。全程的关键是第零步 :--force primary 之前必须确认旧主已经"死透"(进程不可能再写盘)。只要旧主还有一口气、还在往它的 drbd0 写数据,而备端同时提主接管,两边的数据副本就开始各自分叉------这就是脑裂,第六节讲怎么收场。
5.2 原主恢复:先回补数据,再计划内回切
neo4j-node1 修好上线后,不要急着把服务切回去 。此时 node1 的 DRBD 作为 Secondary 接入,会自动把宕机期间落后的数据从 node2 回补(drbdadm status 里 node1 侧经历 SyncTarget → UpToDate)。等两端都 UpToDate 且 replication:Established,再按"停止 SOP"把 node2 上的服务停下、按"启动 SOP"在 node1 上拉起。Keepalived 的 priority 把 node1 配高一点,回切时 VIP 自然回漂。
数据回补期间服务照常跑在 node2 上,回补的量取决于宕机窗口内的写入量------这也是为什么该方案适合写少的场景:写越少,回补越快,RTO 越可控。
六、脑裂防范:这套方案最贵的一课
冷备方案 90% 的事故都来自同一个念头:"能不能让切换自动化?"先看脑裂是怎么发生的,再说自动化怎么做才安全。
6.1 双主是怎么发生的
DRBD 的双主防线只有一条:Secondary 不能挂载。但注意,这个防线挡不住下面的序列:
- 心跳线抖动,node1、node2 互相认为对方挂了(实际都活着);
- 运维在 node2 上执行
drbdadm --force primary neo4j并挂载、启动 Neo4j; - node1 上的 Neo4j 还在跑------两份块设备副本从这一刻起各自接收写入;
- 心跳恢复,DRBD 检测到 split brain,中断复制并把双方置为
StandAlone,等人裁决丢哪边。
有个细节容易让人放松警惕:Neo4j 的 store 文件锁防不住这个场景。文件锁只能挡"同一文件系统上的第二个进程",而两台机器通过 DRBD 看到的是两份独立块设备------两边各自的锁文件互不可见,两边都能"成功启动"。所以不要指望数据库自己兜底,存储层的双主必须用存储层的纪律去防。
6.2 三道防线
第一道,提主前先隔离(fence 优先)。 --force primary 之前,把旧主"打晕":管理口断电、iptables 封 DRBD 端口、或至少登上去 kill Neo4j 进程并 drbdadm down。原则是宁可过度隔离,不可带病提主 ------旧主被误关的代价是一次重启,双主的代价是裁决丢数据。这类"两边都活着但互相失联"的故障形态,和笔者在《Hadoop YARN 任务全部挂掉排查实录》里遇到的"两个 ResourceManager 都认为自己该 Active"同构:分布式系统里,自动抢主必须有仲裁,没有仲裁的抢主就是赌对面已经死了。
第二道,让 Keepalived 感知进程,而不是只感知心跳。 在 vrrp_instance 里挂 track_script,检测本机 bolt 端口或 Neo4j 进程存活,进程死了主动把优先级降下来、让 VIP 漂走。这样进程级故障(机器活着、Neo4j 挂了)也能触发接入层切换,配合接管脚本缩短 RTO。
第三道,接管脚本要"先 fence 再 promote",永远不要做成"检测不到对端就自动 force primary"。 自动化冷备的正确形态是:检测故障 → 尝试隔离旧主(管理口断电等带外手段)→ 隔离确认成功 → 再提升备端。隔离手段不可靠的现场,就把"确认旧主下线"留给人------人工敲一次回车,换来的是绝不脑裂。
6.3 真脑裂了怎么收场
万一已经双主,收场流程:先停掉写入少的一侧的 Neo4j(保留数据多的一侧),然后 drbdadm disconnect / connect 触发 DRBD 的 split brain 裁决,选择弃掉一侧的变更(被弃一侧会整体重同步);最后对被保留一侧做数据核对。DRBD 只能告诉你"两边不一致",不能告诉你哪边是对的------业务侧的核对没有捷径。这也是写这篇文章最想传达的一句:脑裂的处理成本极高,值得把所有预算花在"不让它发生"上。
七、监控巡检清单:四个信号每天看一眼
方案上线后,巡检只需要盯四个信号,一个 shell 循环就能覆盖:
bash
# 1. 复制链路健康:必须是 Established
drbdadm status neo4j | grep -c "replication:Established"
# 2. 两端数据状态:Primary 侧看 peer-disk,Secondary 侧看 disk,非 UpToDate 即告警
drbdadm status neo4j | grep -E "disk:|peer-disk:"
# 3. VIP 在期望的节点上
ip addr | grep -c 192.168.10.20
# 4. Neo4j 进程与 bolt 端口存活
ps -ef | grep -v grep | grep -c neo4j
巡检要点:replication 出现 Off/StandAlone 说明两端失联,此时 Primary 仍在单边写入 (数据落后于对端想象),要当最高优先级处理;peer-disk:Inconsistent 持续不转 UpToDate 说明回补卡住,先看带宽与 syncer 配置。把这四项接进现有监控平台做告警,冷备方案的日常成本就只剩每周一次的切换演练------演练不是可选项,SOP 三个月没人敲过,真故障时一定会手生出错。
八、选型边界:什么时候不该用这套方案
最后泼一盆冷水,这套方案不是万能的:
| 场景 | 是否适合 | 原因 |
|---|---|---|
| 配置管理、拓扑关系等读多写少的图数据 | 适合 | 写入小,协议 C 代价可忽略,分钟级 RTO 可接受 |
| 高并发写入、写入是核心链路 | 不适合 | 每次写都等对端落盘,延迟放大;且写入大意味着回补窗口长 |
| 需要读副本做水平扩展 | 不适合 | 冷备没有第二个可读实例,扩展需求请上企业版集群 |
| 数据可随时全量重建(纯缓存类) | 不必 | 定时备份 + 重建更简单,两台机器都省了 |
一句话总结选型:DRBD + Keepalived 买的是"零数据丢失的廉价保险",卖的是"分钟级中断和单实例性能上限"。想明白业务对这三样的容忍度,选型就定了。
写在最后
社区版 Neo4j 的高可用不是玄学,本质是"存储层复制 + 接入层漂移 + 严格 SOP"三件事的组合拳:DRBD 保证备节点永远有一份最新数据,Keepalived 保证应用永远有一个固定入口,而人(或带 fence 的脚本)保证任何时刻只有一个 Primary 在写。方案本身不难,难的是对顺序和纪律的敬畏------启动五步、停止四步、切换先隔离,每一步背后都是"数据不丢"这四个字。
同一个生产环境的 Neo4j 性能表现(7 层拓扑、百万级节点关系下的写入/查询实测),我整理在《Neo4j 性能测试报告》;社区版与内嵌模式、服务端部署的取舍对比见《Neo4j 内嵌模式实战》,配合本文食用更佳。
你们生产上的 Neo4j 是单实例裸奔、双机冷备,还是上了企业版集群?切换 SOP 平时多久演练一次?评论区聊聊你的方案。