Skip to content

PostgreSQL 脑裂预防机制

核心概念

脑裂的定义

脑裂(Split Brain)是指在高可用集群中,由于网络分区或节点故障,导致集群中的节点无法相互通信,从而形成多个独立的子集群,每个子集群都认为自己是唯一的活动集群,进而导致数据不一致和业务中断的现象。

脑裂的危害

  1. 数据不一致:多个主节点同时接受写操作,导致数据冲突和不一致
  2. 业务中断:应用无法确定哪个主节点是有效的,导致业务中断
  3. 数据丢失:解决脑裂时可能需要丢弃部分数据,导致数据丢失
  4. 恢复困难:脑裂后的恢复过程复杂,需要人工干预

脑裂产生的原因

  1. 网络分区:主从节点之间的网络连接中断
  2. 节点故障:主节点或从节点硬件故障
  3. 心跳超时:心跳检测机制配置不当,导致误判
  4. 资源耗尽:节点资源(CPU、内存、磁盘)耗尽,无法响应心跳
  5. 配置错误:集群配置错误,导致节点间通信失败

PostgreSQL中的脑裂场景

在PostgreSQL复制集群中,脑裂主要发生在以下场景:

  1. 异步复制:从节点无法及时检测到主节点故障,可能在主节点恢复后出现双主
  2. 手动故障转移:手动操作不当,导致新旧主节点同时在线
  3. 自动故障转移工具:repmgr、Patroni等工具配置不当,导致误判和双主
  4. 网络分区:主从节点之间网络中断,导致从节点提升为主节点,而原主节点仍在运行

脑裂预防机制

1. 同步复制

使用同步复制可以确保主节点的写操作在至少一个从节点确认后才返回成功,从而减少脑裂的风险:

sql
-- 修改postgresql.conf配置
ALTER SYSTEM SET synchronous_commit = 'on';
ALTER SYSTEM SET synchronous_standby_names = 'ANY 1 (standby1, standby2)';

-- 应用配置
SELECT pg_reload_conf();

2. 仲裁机制

仲裁机制通过引入第三方节点(仲裁节点)来决定哪个节点应该成为主节点,避免双主情况:

三节点仲裁

使用奇数个节点(通常为3个)组成集群,当网络分区发生时,拥有多数节点的分区将继续运行:

节点A (主) --- 节点B (从)
    |           |
    +--- 节点C (见证节点)

外部仲裁服务

使用外部仲裁服务(如etcd、Consul、ZooKeeper)来协调集群状态:

bash
# 使用Patroni配置etcd仲裁
echo "scope: postgres_cluster
ttl: 30
retry_timeout: 10
elected-leader: 30
sync-period: 10

consul:
  host: 127.0.0.1:8500

etcd:
  host: 127.0.0.1:2379

zookeeper:
  host: 127.0.0.1:2181
  session_timeout: 30
  operation_timeout: 10
" > patroni.yaml

3. STONITH机制

STONITH(Shoot The Other Node In The Head)机制通过硬件或软件方式,在检测到脑裂时强制关闭有问题的节点:

使用fence设备

配置硬件fence设备(如IPMI、iLO、DRAC)或软件fence(如fence_ssh、fence_ipmi):

bash
# 安装fence-agents
sudo apt-get install -y fence-agents

# 配置fence设备
cat > /etc/fence.d/fence_vmware.conf <<EOF
---
devices:
  vm1:
    host: 192.168.1.100
    username: root
    password: password
    vmname: postgres-vm1
  vm2:
    host: 192.168.1.100
    username: root
    password: password
    vmname: postgres-vm2
EOF

4. 脑裂检测机制

repmgr脑裂检测

repmgr通过监控节点状态和复制延迟,检测和防止脑裂:

ini
# 在repmgr.conf中配置脑裂检测
failover='automatic'
failover_validation_command='/path/to/failover_validation.sh'

Patroni脑裂检测

Patroni使用分布式一致性算法(基于RAFT协议)来防止脑裂:

yaml
# 在patroni.yaml中配置
loop_wait: 10
retry_timeout: 10
maximum_lag_on_failover: 1048576

5. 资源锁定

通过锁定共享资源(如共享存储、IP地址)来防止多个节点同时成为主节点:

共享存储锁定

使用共享存储(如NFS、SAN)作为仲裁资源,只有获得存储锁定的节点才能成为主节点:

sql
-- 创建共享存储表空间
CREATE TABLESPACE shared_ts LOCATION '/mnt/shared_storage';

VIP(虚拟IP)机制

使用虚拟IP地址作为服务入口,只有主节点才能绑定VIP:

bash
# 配置keepalived实现VIP漂移
cat > /etc/keepalived/keepalived.conf <<EOF
vrrp_script chk_postgres {
    script "/usr/local/bin/check_postgres.sh"
    interval 2
    weight 2
}

vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.1.100/24 dev eth0 label eth0:0
    }
    track_script {
        chk_postgres
    }
}
EOF

脑裂检测与处理

1. 脑裂检测方法

监控复制状态

sql
-- 检查所有节点的复制状态
SELECT 
    application_name,
    state,
    sync_state,
    client_addr,
    write_lag,
    flush_lag,
    replay_lag
FROM pg_stat_replication;

-- 检查节点角色
SELECT pg_is_in_recovery();

使用repmgr检测

bash
repmgr cluster show

使用Patroni检测

bash
patronictl list

2. 脑裂处理流程

当检测到脑裂时,应按照以下流程处理:

  1. 立即停止业务写入:防止数据进一步不一致
  2. 隔离所有主节点:暂时停止所有主节点的写操作
  3. 评估数据一致性:比较各节点的数据,确定最新和最完整的数据
  4. 选择新主节点:选择数据最完整的节点作为新主节点
  5. 重新同步其他节点:将其他节点重新同步到新主节点
  6. 恢复业务:在确认数据一致后,恢复业务写入
  7. 分析原因:调查脑裂产生的原因,采取预防措施

3. 脑裂恢复示例

假设我们有两个节点:node1(原主节点)和node2(从节点提升为主节点),发生了脑裂:

bash
# 1. 停止所有节点的PostgreSQL服务
sudo systemctl stop postgresql@15-main

# 2. 评估数据一致性
# 在node1上执行
pg_controldata /var/lib/postgresql/15/main > node1_controldata.txt

# 在node2上执行
pg_controldata /var/lib/postgresql/15/main > node2_controldata.txt

# 比较两个节点的LSN位置
# 假设node2的LSN更新

# 3. 将node1重新同步到node2
# 在node1上执行
sudo rm -rf /var/lib/postgresql/15/main/*
pglogical_backup -h node2 -U repmgr -d postgres -D /var/lib/postgresql/15/main

# 4. 启动node2作为主节点
sudo systemctl start postgresql@15-main

# 5. 启动node1作为从节点
sudo systemctl start postgresql@15-main

# 6. 使用repmgr重新注册集群
repmgr primary register -f /etc/repmgr.conf --force
repmgr standby register -f /etc/repmgr.conf --force

最佳实践

1. 架构设计最佳实践

  • 使用奇数个节点:3节点或5节点集群,避免平票情况
  • 分离网络:将管理网络和复制网络分离,减少网络分区风险
  • 使用可靠的网络设备:使用冗余网络设备,确保网络可靠性
  • 部署在不同可用区:将节点部署在不同的可用区或数据中心

2. 配置最佳实践

  • 合理设置心跳参数:根据网络环境调整心跳检测间隔和超时时间
  • 启用同步复制:对于关键业务,使用同步复制或半同步复制
  • 配置仲裁机制:使用repmgr、Patroni等工具的仲裁功能
  • 定期测试故障转移:每月至少测试一次自动故障转移功能

3. 监控与告警最佳实践

  • 监控复制状态:实时监控复制延迟和状态
  • 监控节点状态:监控节点的CPU、内存、磁盘和网络使用情况
  • 设置告警规则:当检测到复制延迟过高或节点状态异常时触发告警
  • 配置日志监控:监控PostgreSQL和集群管理工具的日志,及时发现问题

4. 操作最佳实践

  • 建立操作流程:制定详细的故障转移和恢复操作流程
  • 培训团队:确保团队成员熟悉脑裂的检测和处理方法
  • 定期演练:定期进行脑裂场景的演练,提高团队处理能力
  • 文档记录:详细记录集群配置和操作流程

常见问题(FAQ)

Q1:如何区分正常的故障转移和脑裂?

A1:正常的故障转移是在确认主节点真正故障后进行的,而脑裂是在主节点仍在运行时发生的。可以通过以下方法区分:

  1. 检查原主节点是否仍在运行:sudo systemctl status postgresql@15-main
  2. 检查网络连接:ping 主节点IP
  3. 检查复制状态:SELECT * FROM pg_stat_replication;
  4. 检查集群管理工具日志:查看repmgr或Patroni的日志

Q2:如何配置repmgr防止脑裂?

A2:可以通过以下配置防止脑裂:

  1. 在repmgr.conf中配置failover_validation_command,确保主节点真正故障
  2. 使用见证节点(witness node)来避免平票情况
  3. 合理设置monitor_interval_secsreconnect_attempts参数
  4. 启用hot_standby_feedback,防止主节点VACUUM删除从节点需要的数据

Q3:Patroni如何防止脑裂?

A3:Patroni使用以下机制防止脑裂:

  1. 基于RAFT协议的分布式一致性算法
  2. 只有获得多数投票的节点才能成为主节点
  3. 定期发送心跳,检测节点状态
  4. 配置maximum_lag_on_failover,防止从延迟过高的从节点提升为主节点

Q4:如何在网络分区时防止脑裂?

A4:可以采取以下措施:

  1. 使用奇数个节点的集群架构
  2. 配置外部仲裁服务(如etcd、Consul)
  3. 使用STONITH机制,在检测到脑裂时强制关闭有问题的节点
  4. 配置合理的心跳检测参数,减少误判

Q5:脑裂后如何恢复数据一致性?

A5:恢复数据一致性的步骤:

  1. 停止所有节点的写操作
  2. 评估各节点的数据,确定最新和最完整的数据
  3. 选择数据最完整的节点作为新主节点
  4. 将其他节点重新同步到新主节点
  5. 在确认数据一致后,恢复业务写入

Q6:如何监控脑裂?

A6:可以通过以下方法监控脑裂:

  1. 监控集群管理工具的状态:repmgr cluster show或patronictl list
  2. 监控复制状态:SELECT * FROM pg_stat_replication;
  3. 监控节点角色:SELECT pg_is_in_recovery();
  4. 配置日志监控,当出现"split brain"或"conflict"关键字时触发告警
  5. 使用Prometheus + Grafana监控集群状态

Q7:使用异步复制时如何防止脑裂?

A7:异步复制的脑裂风险较高,可以采取以下措施:

  1. 配置合理的复制延迟监控和告警
  2. 使用repmgr或Patroni等工具进行自动故障转移
  3. 配置见证节点,避免平票情况
  4. 定期测试故障转移功能
  5. 对于关键业务,考虑使用同步或半同步复制

Q8:如何防止手动操作导致的脑裂?

A8:可以采取以下措施:

  1. 建立严格的操作流程和审批机制
  2. 使用自动化工具(如Ansible、Terraform)执行集群操作
  3. 配置操作审计,记录所有集群操作
  4. 培训团队,提高操作技能
  5. 定期进行操作演练,减少误操作风险