📚 目录
降维剖析:MySQL HA vs 金仓 HA 的底层逻辑差异
基石:金仓 V9 流复制(Streaming Replication)深度配置
大脑:etcd 集群与金仓 kbha 组件实战(防脑裂核心)
工程实践:C# 高可用混沌演练引擎(Chaos Engineering)
性能调优与避坑指南:让切换真正做到 RPO=0
总结与避坑清单
一、降维剖析:MySQL HA vs 金仓 HA 的底层逻辑差异
🧠 魔性比喻:
MySQL 的主从复制 就像是 “抄作业”:主库把做过的题(Binlog SQL/Row 事件)发给备库,备库自己重新做一遍。如果备库做得慢,就会延迟。
金仓(PG内核)的流复制 就像是 “复印试卷”:主库直接把写好的物理数据页(WAL 日志,物理修改记录)通过 TCP 流式传给备库,备库直接“贴”到自己的数据文件上。速度极快,且保证物理级别的一致。
1.1 核心架构与防脑裂机制对比矩阵
维度 MySQL 高可用生态 人大金仓 (KingbaseES V9) 高可用生态 核心差异与踩坑点
复制机制 Binlog (逻辑/行级) WAL (Write-Ahead Log, 物理级) 金仓的流复制是物理拷贝,备库与主库数据文件完全一致(连 OID 都一样)。
主流 HA 方案 MHA, Orchestrator, InnoDB Cluster, Keepalived kbha (基于 etcd), sys_ha, Patroni, 读写分离集群 🔥 核心! 金仓绝对不要用 Keepalived!必须用基于分布式一致性(etcd)的 HA 组件。
防脑裂 (Fencing) STONITH (Shoot The Other Node In The Head),关机或断网 etcd 分布式租约 (Lease) + 触发器隔离 etcd 租约过期,老主库会自动降级为备库或自杀,从根本上杜绝双主。
VIP 漂移 Keepalived VRRP 协议 kbha 调用自定义 VIP 脚本 (通过 etcd 触发) 金仓的 VIP 漂移是由 HA 组件在确认主备状态后主动调用的,更安全。
数据一致性(RPO) 半同步复制 (Semi-sync) 同步流复制 (Synchronous Replication) 金仓的 synchronous_commit = remote_apply 可以保证 RPO=0,且备库立即可读。
二、基石:金仓 V9 流复制(Streaming Replication)深度配置
高可用的前提是数据能实时、准确地同步到备库。金仓的流复制配置比 MySQL 稍微复杂一点,但逻辑更严密。
2.1 主库配置(Master)
============================================================
kingbase.conf —— 主库流复制核心配置
⚠️ 修改后需要重启数据库(部分参数可 reload)
============================================================
WAL 级别(必须!)
💡 replica 支持物理流复制和归档;logical 额外支持逻辑订阅(CDC)。
生产环境高可用通常用 replica 即可,性能最好。
wal_level = replica
最大 WAL 发送进程数(主库发给备库的进程)
💡 建议设置为 备库数量 + 2(留余量给备份工具如 sys_basebackup)
max_wal_senders = 6
最大复制槽(Replication Slots)数量
💡 核心神器!复制槽可以防止主库在备库还没同步完时,就把 WAL 日志删除了。
这是保证 RPO=0 的底层基石!
max_replication_slots = 6
WAL 保留策略
wal_keep_size = 2GB # (PG13+/KBV9) 至少保留 2GB 的 WAL,防止网络抖动导致备库断流
如果是老版本用 wal_keep_segments = 128
同步提交级别(决定 RPO 的关键)
💡 remote_apply:主库提交事务时,必须等待至少一个同步备库接收并应用(回放) 了该 WAL。
这保证了主库宕机时,备库的数据和主库绝对一致(RPO=0),且备库立即可读(无延迟)。
性能代价:事务延迟会增加(网络 RTT + 备库回放时间)。
synchronous_commit = remote_apply
同步备库名称(与 standby 的 application_name 对应)
💡 ‘’ 表示任意一个同步备库确认即可。也可以指定具体名字如 ‘kb_node2’。
synchronous_standby_names = 'ANY 1 ()’
============================================================
sys_hba.conf —— 配置复制用户的网络访问权限
允许备库 IP (192.168.1.200) 使用 replica 用户进行流复制连接
💡 必须放在文件靠前的位置,优先匹配
host replication replica_user 192.168.1.200/32 scram-sha-256
– 在主库创建专用的复制用户
CREATE ROLE replica_user WITH REPLICATION LOGIN PASSWORD ‘YourStrongPassword123!’;
– 💡 创建物理复制槽(强烈建议!)
– 复制槽会“钉住”WAL 日志,即使备库宕机,主库也不会清理备库还没拉取的 WAL。
– 防止备库重启后因为缺少 WAL 而不得不全量重建!
SELECT sys_create_physical_replication_slot(‘slot_node2’);
2.2 备库搭建(Standby)—— 使用 sys_basebackup
!/bin/bash
备库一键初始化脚本
💡 核心工具:sys_basebackup (类似于 PG 的 pg_basebackup)
它会通过流复制协议,在线把主库的数据目录完整“克隆”到备库。
export KB_HOME=/opt/Kingbase/ES/V9
export PATH=KB_HOME/server/bin:PATH
备库数据目录(必须为空或不存在)
DATA_DIR=“/data/kingbase/data”
rm -rf DATA_DIR
执行基础备份(克隆数据)
💡 -Fp: 纯文本格式 (Plain)
💡 -Xs: 使用流式传输 WAL (Stream),保证备份期间产生的新数据也能带过来
💡 -P: 显示进度
💡 -R: 自动创建 standby.signal 文件并配置 kingbase.auto.conf (极其方便!)
💡 -S: 指定使用主库上创建的复制槽 ‘slot_node2’
sys_basebackup -h 192.168.1.100 -p 54321 -U replica_user
-D DATA_DIR
-Fp -Xs -P -R
-S slot_node2
修改备库的 kingbase.auto.conf (sys_basebackup -R 会自动生成,这里做微调)
cat >> DATA_DIR/kingbase.auto.conf << EOF
备库角色
primary_conninfo = ‘host=192.168.1.100 port=54321 user=replica_user password=YourStrongPassword123! application_name=kb_node2’
primary_slot_name = ‘slot_node2’
💡 热备模式:允许备库提供只读查询服务(读写分离必备)
hot_standby = on
EOF
启动备库
sys_ctl start -D DATA_DIR
验证流复制状态
ksql -U system -d prod_db -h 192.168.1.100 -c “SELECT client_addr, state, sync_state, sent_lsn, write_lsn, flush_lsn, replay_lsn FROM sys_stat_replication;”
💡 如果看到 state=‘streaming’, sync_state=‘sync’ (或 ‘quorum’),说明同步流复制搭建成功!
三、大脑:etcd 集群与金仓 kbha 组件实战(防脑裂核心)
这是防脑裂的灵魂!
Keepalived 是基于 VRRP 广播的,网络分区时,两边都以为对方死了,就会选出两个 Master(脑裂)。
etcd 是基于 Raft 协议的分布式 KV 存储,它要求“多数派(Quorum)”同意才能写入。网络分区时,少数派节点绝对无法获取 etcd 的 Leader 租约,从而从根本上杜绝脑裂。
金仓 V9 提供的 kbha(或企业版的 sys_ha)就是深度集成了 etcd 的高可用守护进程。
3.1 etcd 集群部署(3节点,防单点故障)
在 3 台服务器 (192.168.1.10, .20, .30) 上部署 etcd 集群
这里只展示 node1 的启动命令,其他节点类似
etcd --name etcd1
–data-dir /data/etcd
–listen-client-urls http://192.168.1.10:2379
–advertise-client-urls http://192.168.1.10:2379
–listen-peer-urls http://192.168.1.10:2380
–initial-advertise-peer-urls http://192.168.1.10:2380
–initial-cluster etcd1=http://192.168.1.10:2380,etcd2=http://192.168.1.20:2380,etcd3=http://192.168.1.30:2380
–initial-cluster-token kbha-cluster
–initial-cluster-state new
3.2 kbha 配置文件与 VIP 漂移脚本
============================================================
kbha.yml —— 金仓 HA 组件配置文件
scope: kb_prod_cluster # 集群名称(etcd 中的 key 前缀)
namespace: /service/ # etcd 命名空间
name: kb_node1 # 当前节点名称(必须与 kingbase.conf 里的 application_name 一致)
restapi:
listen: 192.168.1.100:8008
connect_address: 192.168.1.100:8008
etcd:
hosts: 192.168.1.10:2379,192.168.1.20:2379,192.168.1.30:2379
💡 核心:TTL (Time To Live) 租约时间。
如果节点在 30 秒内没有向 etcd 续约,etcd 就会认为它死了,触发 Failover。
ttl: 30
bootstrap:
dcs:
ttl: 30
loop_wait: 10 # 每 10 秒检查一次集群状态
retry_timeout: 10
maximum_lag_on_failover: 1048576 # 1MB,备库延迟超过 1MB 不允许被提升为主库
synchronous_mode: true # 💡 开启同步模式,保证 RPO=0
postgresql:
use_pg_rewind: true # 💡 开启 pg_rewind,老主库恢复后可以通过增量回滚快速变回备库,无需全量重建!
use_slots: true
postgresql:
listen: 192.168.1.100:54321
connect_address: 192.168.1.100:54321
data_dir: /data/kingbase/data
bin_dir: /opt/Kingbase/ES/V9/server/bin
authentication:
replication:
username: replica_user
password: YourStrongPassword123!
superuser:
username: system
password: YourSuperPassword!
💡 核心:回调脚本(Callbacks)
当节点角色发生变化时,kbha 会调用这些脚本。
我们在这里实现 VIP 漂移和告警!
callbacks:
on_start: /opt/scripts/vip_manage.sh
on_stop: /opt/scripts/vip_manage.sh
on_role_change: /opt/scripts/vip_manage.sh
!/bin/bash
/opt/scripts/vip_manage.sh —— VIP 漂移与 Fencing 脚本
💡 参数:1 = action (on_start, on_stop, on_role_change)
2 = role (master, replica)
3 = cluster_name
VIP=“192.168.1.88”
NET_IFACE=“eth0”
ACTION=1
ROLE=2
echo “(date) - Action: ACTION, Role: ROLE” >> /var/log/kbha_vip.log
if [ “ROLE” == “master” ]; then
# 💡 当前节点被提升为主库:绑定 VIP
ip addr add VIP/24 dev NET_IFACE label {NET_IFACE}:vip 2>/dev/null
# 发送免费 ARP (Gratuitous ARP),通知交换机更新 MAC 地址表
arping -q -A -c 3 -I NET_IFACE VIP
# 发送飞书/钉钉告警 curl -X POST "https://oapi.dingtalk.com/robot/send?access_token=xxx" \ -H 'Content-Type: application/json' \ -d "{"msgtype": "text", "text": {"content": "🚨 [金仓HA] (hostname) 已提升为主库!VIP: VIP"}}"elif [ “ROLE” == “replica” ] || [ “ACTION” == “on_stop” ]; then
# 💡 当前节点降级为备库,或停止:解绑 VIP
ip addr del VIP/24 dev NET_IFACE label {NET_IFACE}:vip 2>/dev/null
# 💡 Fencing (隔离) 动作:如果是老主库被踢下台,为了防止它“诈尸”继续写数据, # 最安全的做法是直接触发关机或断开网络(STONITH)。 # 这里为了演示,我们强制将数据库设为只读模式。 if [ "ACTION" == "on_role_change" ]; then su - kingbase -c "ksql -U system -d prod_db -c 'ALTER SYSTEM SET default_transaction_read_only = on; SELECT sys_reload_conf();'" fifi
四、工程实践:C# 高可用混沌演练引擎(Chaos Engineering)
🧠 金句:“没有经过‘拔网线’测试的高可用架构,就像没经过碰撞测试的汽车——你永远不知道它会在什么时候要了你的命。”
很多团队搭完 HA 就不管了。真正的工程实践是引入混沌工程(Chaos Engineering),用代码自动、随机地制造故障,验证 HA 的真实反应。
下面我用 C# .NET 8 写一个后台服务,它会定时执行“主库断网”、“主库进程强杀”等演练,并通过 etcd API 和数据库连接池验证切换时间(RTO)和数据一致性(RPO)。
4.1 混沌演练引擎(C# 核心代码)
// ============================================================
// 人大金仓高可用混沌演练引擎 (Chaos Engineering)
//
// 设计思想:
// 1. 使用 SSH.NET 远程执行故障注入命令(如 kill -9, iptables 断网)。
// 2. 使用 Npgsql 持续探测 VIP 的连通性和读写状态。
// 3. 使用 etcdnet 监控集群 Leader 的切换过程。
// 4. 精确计算 RTO(恢复时间目标)和验证 RPO(恢复点目标)。
//
// ⚠️ 警告:此代码仅用于测试/预发环境!严禁在未经审批的生产环境运行!
// ============================================================
namespace Xinchuang.Kingbase.ChaosEngine;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using Npgsql;
using Renci.SshNet;
using Microsoft.Extensions.Logging;
public class KingbaseChaosOrchestrator
{
private readonly ILogger _logger;
private readonly ChaosConfig _config;
public KingbaseChaosOrchestrator( ILogger<KingbaseChaosOrchestrator> logger, ChaosConfig config) { _logger = logger; _config = config; } /// <summary> /// 执行完整的“主库宕机”混沌演练 /// </summary> public async Task<ChaosReport> ExecuteMasterCrashDrillAsync(CancellationToken ct) { var report = new ChaosReport { DrillType = "Master_Crash", StartTime = DateTime.Now }; var sw = Stopwatch.StartNew(); try { // 1. 确认当前谁是主库(通过 VIP 连接查询) _logger.LogInformation("====== [Step 1] 探测当前主库 ======"); var currentMaster = await GetCurrentMasterAsync(ct); report.OriginalMaster = currentMaster; // 2. 在主库写入一条“哨兵数据”(用于验证 RPO) _logger.LogInformation("====== [Step 2] 写入哨兵数据 ======"); var sentinelId = await WriteSentinelDataAsync(ct); report.SentinelId = sentinelId; // 3. 启动后台持续探测任务(计算 RTO) _logger.LogInformation("====== [Step 3] 启动持续探测 ======"); var probeTask = StartContinuousProbingAsync(report, ct); // 4. 注入故障:强杀主库的 kingbase 进程 (kill -9) _logger.LogInformation("====== [Step 4] 注入故障:强杀主库进程 ======"); await InjectFaultAsync(currentMaster, "kill -9 $(head -1 /data/kingbase/data/postmaster.pid)", ct); report.FaultInjectedAt = DateTime.Now; // 5. 等待 VIP 漂移和备库提升(监听探测任务的结果) _logger.LogInformation("====== [Step 5] 等待 HA 自动切换 ======"); await probeTask; sw.Stop(); report.RTO_Milliseconds = sw.ElapsedMilliseconds; // 6. 验证 RPO:在新主库查询“哨兵数据”是否存在 _logger.LogInformation("====== [Step 6] 验证 RPO (数据一致性) ======"); report.RPO_Passed = await VerifySentinelDataAsync(report.NewMaster, sentinelId, ct); report.Success = report.RPO_Passed && report.RTO_Milliseconds < _config.MaxAcceptableRTO; } catch (Exception ex) { _logger.LogError(ex, "混沌演练发生异常!"); report.Success = false; report.ErrorMessage = ex.Message; } finally { // 7. 恢复现场(启动老主库,让它通过 pg_rewind 自动降级为备库) _logger.LogInformation("====== [Step 7] 恢复现场 ======"); await RecoverMasterAsync(report.OriginalMaster, ct); report.EndTime = DateTime.Now; } return report; } /// <summary> /// 持续探测 VIP 的可用性(计算 RTO 的核心) /// </summary> private async Task StartContinuousProbingAsync(ChaosReport report, CancellationToken ct) { int failCount = 0; bool switched = false; while (!ct.IsCancellationRequested && !switched) { try { await using var conn = new NpgsqlConnection(_config.VipConnectionString); await conn.OpenAsync(ct); await using var cmd = new NpgsqlCommand("SELECT inet_server_addr(), pg_is_in_recovery()", conn); await using var reader = await cmd.ExecuteReaderAsync(ct); if (await reader.ReadAsync(ct)) { var serverIp = reader.GetString(0); var isInRecovery = reader.GetBoolean(1); // false 表示是主库 // 💡 如果连上了,且不是恢复模式(说明是主库),且 IP 不是原来的主库 IP if (!isInRecovery && serverIp != report.OriginalMaster) { report.NewMaster = serverIp; report.VipRecoveredAt = DateTime.Now; switched = true; _logger.LogInformation("🎉 VIP 已漂移到新主库: {IP}", serverIp); } } failCount = 0; // 连接成功,重置失败计数 } catch (Exception) { failCount++; // 允许短暂的连接失败(VIP 漂移期间) } await Task.Delay(100, ct); // 每 100ms 探测一次,精确计算 RTO } } /// <summary> /// 通过 SSH 注入故障 /// </summary> private async Task InjectFaultAsync(string hostIp, string command, CancellationToken ct) { await Task.Run(() => { using var client = new SshClient(hostIp, _config.SshUser, _config.SshPassword); client.Connect(); var result = client.RunCommand(command); _logger.LogWarning("故障注入命令执行结果: {Result}", result.Result); client.Disconnect(); }, ct); } // ... 其他辅助方法 (GetCurrentMasterAsync, WriteSentinelDataAsync, etc.) 省略 ...}
public class ChaosConfig
{
public string VipConnectionString { get; set; } = “Host=192.168.1.88;Port=54321;Database=prod_db;Username=system;Password=xxx;Timeout=3;”;
public string SshUser { get; set; } = “root”;
public string SshPassword { get; set; } = “xxx”;
public int MaxAcceptableRTO { get; set; } = 30000; // 30秒
}
public class ChaosReport
{
public string DrillType { get; set; } = “”;
public bool Success { get; set; }
public string OriginalMaster { get; set; } = “”;
public string NewMaster { get; set; } = “”;
public Guid SentinelId { get; set; }
public bool RPO_Passed { get; set; }
public long RTO_Milliseconds { get; set; }
public DateTime StartTime { get; set; }
public DateTime FaultInjectedAt { get; set; }
public DateTime VipRecoveredAt { get; set; }
public DateTime EndTime { get; set; }
public string? ErrorMessage { get; set; }
}
五、性能调优与避坑指南:让切换真正做到 RPO=0
5.1 金仓高可用 10 大避坑清单(血泪总结)
坑 后果 正确做法
1 用 Keepalived 做金仓 HA 网络抖动导致双主脑裂,数据写花 必须使用基于 etcd/Raft 的 kbha 或 sys_ha 组件。
2 没配置同步流复制 主库宕机时,备库还没收到最新 WAL,导致丢数据 (RPO > 0) 核心业务必须配置 synchronous_commit = remote_apply。
3 没使用复制槽 (Replication Slot) 备库宕机重启后,主库 WAL 已清理,备库只能全量重建 必须创建物理复制槽,但需监控槽的积压,防止撑爆主库磁盘。
4 老主库恢复后直接启动 老主库带着“分叉”的数据启动,导致集群状态混乱 必须开启 use_pg_rewind,让老主库自动回滚分叉数据并降级为备库。
5 Fencing (隔离) 没做彻底 老主库进程没死透,继续接收写入 VIP 漂移脚本中必须包含 STONITH 动作(如 kill -9 或 ip link set down)。
6 etcd 集群节点数为偶数 (如 2 或 4) 无法形成多数派,脑裂风险高 etcd 集群必须是奇数节点(3 或 5)。
7 备库延迟过大时允许提升 提升了一个落后 1GB 数据的备库,导致大量数据丢失 配置 maximum_lag_on_failover,延迟超标时拒绝自动切换,转人工。
8 应用连接池没配置重试 VIP 漂移的 5 秒内,应用直接报错给用户 HikariCP / Npgsql 必须配置 Connection Timeout 和 Retry 机制。
9 大事务导致同步延迟 一个 10GB 的 DELETE 导致同步卡住,HA 误判主库宕机 拆分大事务;调整 wal_sender_timeout 和 wal_receiver_timeout。
10 没有定期做混沌演练 切换脚本因为 OS 密码过期或路径变更而失效 部署 C# 混沌演练引擎,每周自动执行一次“拔网线”测试。
5.2 监控与告警指标(Prometheus + Grafana)
金仓高可用核心监控指标 (通过 sys_stat 视图采集)
主备延迟 (字节数)
💡 如果 > 10MB,触发 P2 告警;如果 > 100MB,触发 P1 告警
SELECT pg_wal_lsn_diff(sent_lsn, replay_lsn) AS replay_lag_bytes
FROM sys_stat_replication WHERE application_name = ‘kb_node2’;
复制槽积压 (字节数)
💡 如果备库长期宕机,复制槽会积压大量 WAL,撑爆主库磁盘!
必须监控!如果 > 5GB,触发 P1 告警,必要时手动删除槽。
SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS slot_lag_bytes
FROM sys_replication_slots WHERE slot_name = ‘slot_node2’;
检查点频率
💡 频繁的检查点会导致 IO 毛刺,影响流复制性能
SELECT checkpoints_timed, checkpoints_req FROM sys_stat_bgwriter;
六、总结与避坑清单
6.1 墨夶的最后唠叨
老铁们,从 MySQL 切到人大金仓,高可用架构的重构是重中之重。
MySQL 的 HA 生态是“百花齐放”,你可以用 MHA,也可以用 Orchestrator,甚至自己写脚本。但金仓(PG系)的高可用,必须敬畏其底层的 WAL 机制和分布式一致性(etcd)原理。
用 Keepalived 搞金仓 HA,就像用透明胶带给飞机补漏——看着能飞,一上天就解体。
记住三句话:
防脑裂是高可用的底线。 抛弃 VRRP,拥抱 etcd 分布式租约。
RPO=0 必须靠同步流复制。 synchronous_commit = remote_apply 是核心业务的护身符。
没有演练的 HA 就是定时炸弹。 用代码自动“拔网线”,把故障消灭在演习中。