脑裂(Split-Brain)是Linux高可用集群中最危险的一类故障。所谓脑裂,是指集群中的节点因心跳通信中断而互相认为对方已经失效,于是多个节点同时升级为主节点、争抢同一份资源的现象。一旦发生脑裂,两个主节点会同时向共享存储写入数据,轻则数据错乱,重则整个文件系统损毁,业务被迫中断。这篇文章从脑裂的产生机理讲起,分析常见诱因,并给出完整的预防方案与恢复流程。

脑裂是怎么产生的:从心跳机制说起
高可用集群的核心是心跳(Heartbeat)机制。以Pacemaker配合Corosync的典型架构为例,每个节点会通过指定的心跳链路周期性向其他节点广播存活信息。当一个节点连续若干个心跳周期收不到对方的报文,就会触发超时判定,认为对方已经宕机,进而执行资源接管。
问题就出在这个判定逻辑上。心跳丢失并不等于节点真的死了,它可能只是心跳网卡故障、心跳交换机端口异常、网线松动,或者是系统负载过高导致心跳进程无响应。如果A、B两个节点之间的心跳链路整体断开,A收不到B的心跳,B也收不到A的心跳,双方都会得出“对方挂了,我来接管”的结论,于是两个主节点同时存在,脑裂就发生了。
脑裂的危害远比单点故障严重。如果两个节点同时挂载同一个共享存储的LUN并向其写入数据,EXT4或XFS这类传统文件系统并不支持多节点并发写,元数据会迅速损坏。即便没有共享存储,两个节点同时争抢一个虚拟IP(VIP),也可能导致ARP缓存混乱,客户端请求被随机分发到两个节点上,会话状态错乱。可以用以下命令检查Corosync层面的令牌丢失情况:
# 查看corosync的令牌丢失统计,token后面是超时毫秒数 corosync-cmapctl | grep -i token # 查看成员关系变化日志 grep -i "membership" /var/log/cluster/corosync.log
脑裂的常见诱因与诊断方法
实际运维中,脑裂的诱因可以归纳为三类。第一类是心跳链路单点故障,很多团队为了节省成本只配置了一条心跳线,一旦这条链路抖动,脑裂风险立刻暴露。第二类是仲裁缺失,两节点集群没有第三方的仲裁者,双方各执一词时无法裁决谁该让位。第三类是资源竞争型故障,比如交换机广播风暴、网卡驱动缺陷、服务器CPU被持续打满导致心跳线程饿死,这类问题往往被误判为对端宕机。
诊断脑裂时,首先要确认每个节点的身份认知。在各个节点上分别执行下面的命令,如果两个节点都显示自己是某个资源的Master角色,或者各自都持有同一个VIP,基本可以判定脑裂已经发生:
# 查看各节点对资源角色的认知 pcs status resources # 检查本机是否持有VIP ip addr show | grep 192.168.10.100 # 查看fence设备是否已触发(stonith相关事件) pcs stonith status journalctl -u pacemaker | grep -i stonith
其次要看日志中的时间线。Pacemaker的日志通常位于/var/log/pacemaker/pacemaker.log,重点搜索CRIT和stonith-ng关键字。如果日志里出现节点被fence成功踢出的记录,说明防护机制生效,未必是真脑裂;如果两边日志都显示对方“not crediting us with a ticket”之类的成员关系冲突,则脑裂实锤。
预防脑裂:多路心跳、仲裁与fence三件套
预防脑裂的第一层防线是心跳链路冗余。建议至少配置两条独立的心跳路径,一条走业务网络之外的独立心跳网段,另一条走串口线或者另一块物理网卡。Corosync支持在配置中声明多个ring,当ring 0故障时自动切换到ring 1,两条链路同时失效的概率远低于单链路。配置示例如下:
# /etc/corosync/corosync.conf 关键片段
totem {
version: 2
token: 3000
rrp_mode: passive
interface {
ringnumber: 0
bindnetaddr: 10.0.1.0
mcastaddr: 239.255.1.1
}
interface {
ringnumber: 1
bindnetaddr: 10.0.2.0
mcastaddr: 239.255.1.2
}
}第二层防线是仲裁机制。两节点集群强烈建议引入第三方仲裁,常见做法有三种:增加一台轻量级的qnetd仲裁服务器、使用共享的仲裁磁盘、或者干脆部署三节点集群让奇数原则发挥作用。以Corosync的qdevice为例,它作为一个轻量守护进程运行在第三个节点上,当两个数据节点意见不合时,由qdevice投票决定谁留在集群中,票数少的一方主动放弃资源。这种方案的部署成本很低,一台1核1GB的虚拟机就能胜任。
第三层也是最关键的防线是fence(STONITH)机制。STONITH的全称是Shoot The Other Node In The Head,翻译过来就是“直接打死对方节点”。它的逻辑很直接:在接管资源之前,必须先通过带外手段(IPMI、iLO、iDRAC、智能PDU断电等)确认对端已经被物理关闭或隔离,确认成功才允许接管。很多团队觉得fence配置麻烦就把它关掉,这是高可用设计中的大忌,没有fence的集群等于把数据完整性交给运气。下面是一个基于IPMI的fence设备配置示例:
# 创建基于ipmilan的fence设备
pcs stonith create ipmi_fence fence_ipmilan \
ip=10.0.0.11 lanplus=1 username=admin password=AdminPass \
pcmk_host_list=node1 node2 \
action=reboot op monitor interval=60s
# 开启资源接管前必须先fence的约束
pcs property set stonith-enabled=true脑裂发生后的恢复流程
假如脑裂已经发生,恢复时的核心原则是:先隔离,再定主,后修复数据,切忌在两个节点都活着的情况下贸然恢复服务。第一步是人工介入判断哪个节点拥有最新的有效数据。通常依据业务写入时间戳、数据库事务日志序号来判断,确定“权威节点”之后,立即在另一个节点上停止集群服务和资源:
# 在被放弃的节点上执行,先停心跳避免它再抢资源 systemctl stop pacemaker corosync # 释放可能被错误持有的VIP ip addr del 192.168.10.100/24 dev eth0 # 强制卸载共享存储,防止继续写入脏数据 umount -f /mnt/shared
第二步是对共享存储做一致性校验。如果使用的是DRBD,可以通过drbdadm status查看两个节点的数据状态标记,确认哪边是UpToDate,然后让过期的一方执行drbdadm primary --force强制以权威数据为准重新同步。如果是共享文件系统,建议先执行xfs_repair -n或e2fsck -n以只读模式预检,确认损坏范围后再决定修复策略。数据库场景则应该以主库的事务日志为准,对备库执行重建或基于PITR的恢复。
第三步是恢复集群成员关系并复盘。在确认数据一致后,先启动权威节点的集群服务,验证资源正常托管,再以standby方式重新加入另一个节点,观察同步完成后再取消standby状态。最后一定要复盘脑裂根因:检查心跳链路的冗余性、仲裁是否生效、fence是否成功触发。可以在测试环境主动拔掉心跳线做故障演练,验证整套防护机制是否真的能挡住脑裂。高可用系统从来不是配置完就一劳永逸,定期的故障演练才是让脑裂防护体系保持可靠的根本手段。