Neo4j 社区版高可用实战:DRBD 块级复制 + Keepalived 虚拟 IP 冷备切换(附启停 SOP 与脑裂防范)

用 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

三条路里本文这条最适合"读多写少、能接受分钟级中断、但一分钱数据都不能丢"的场景,理由有三:

  1. 对 Neo4j 零侵入:应用照常连 bolt 端口,感知不到底下是 DRBD;不需要改一行代码;
  2. 一致性由内核态保证:DRBD 是块设备层的复制,复制的是"字节流",Neo4j 的任何存储格式变更(升级版本、改配置)都天然被覆盖,不存在"应用层双写漏了一张表"这类问题;
  3. 成本只有两台机器:对已经为生产准备了备份机的现场,几乎零新增成本。

它的短板也必须先说透:这是冷备,不是自动故障转移。备节点平时不跑 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 不能挂载。但注意,这个防线挡不住下面的序列:

  1. 心跳线抖动,node1、node2 互相认为对方挂了(实际都活着);
  2. 运维在 node2 上执行 drbdadm --force primary neo4j 并挂载、启动 Neo4j;
  3. node1 上的 Neo4j 还在跑------两份块设备副本从这一刻起各自接收写入;
  4. 心跳恢复,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 平时多久演练一次?评论区聊聊你的方案。

相关推荐
爱吃辣椒的小豆豆1 小时前
Linux-sshd-实战-白名单与跳板机
linux·运维·服务器
强哥聊算力2 小时前
《算力江湖·国产GPU突围:摩尔线程故事》第四集:从图形到AI:摩尔线程为什么必须冲进大模型赛道?
运维·服务器·人工智能·ai·gpu算力
梅梅绵绵冰2 小时前
Nginx反向代理
运维·nginx
Python自动化直播2 小时前
自动化笔记:素材管理、命名、轮换的几条土办法
linux·运维·数据库
西安同步电子3 小时前
卫星信号丢失怎么办?NTP 网络校时服务器的守时能力
运维·服务器·网络
海宇服务3 小时前
零信任架构实战:基于海宇网上踪迹欺诈行为识别构建自动化电商大促营销补贴风控网关
运维·人工智能·架构·自动化
djjjx.3 小时前
Linux进程篇(四):内核托底与生死交错 ,深度硬核剖析“僵尸”与“孤儿”进程
linux·运维·服务器
m0_646429973 小时前
VS Code Remote SSH 使用 Codex:通过 SSH 转发本地代理
运维·ssh
会写代码的青蛙4 小时前
青蛙自动化
运维·自动化