Patroni+ETCD实现PostgreSQL高可用:从原生PG到Vastbase/GaussDB迁移实践
去年下半年我接手了一个数据库切换项目:业务原本跑在原生 PostgreSQL 14 上,因为资源池和运维体系调整,要逐步迁到 Vastbase 和 GaussDB。SQL 兼容性测试阶段一切顺利,真正让我头疼的是高可用方案。原环境用的是老一套"keepalived 飘 VIP + 手工 promote 脚本",这套组合在原生 PG 上还算凑合,但换到 Vastbase、GaussDB 之后,脚本里的 pg_ctl、pg_rewind 这些命令有的不存在、有的行为不一样,切一次库比做一次变更还紧张。最后我们没有继续打补丁,而是把底座整体换成了 Patroni + ETCD。我把整个演进过程重新梳理了一遍,整理出这篇记录,重点聊三件事:这套架构为什么能解决传统主备方案解决不了的问题;从原生 PG 迁到 Vastbase、GaussDB 时哪些兼容点必须先核实;以及落地和排障过程中真正值得记住的细节。适合正在做 PostgreSQL 高可用改造,或者正在评估兼容型数据库替换的 DBA、运维和架构师参考。
1. 为什么高可用不能只靠"主备脚本 + 飘一个 VIP"
1.1 流复制解决的是数据通道,不是决策问题
PostgreSQL 的 streaming replication 从 9.x 开始就足够成熟,WAL 日志从主库实时传到备库,备库通过 apply 进程把变更刷进数据文件。很多人以为"我有主备同步了,天然就是高可用",这是最常见的误解。流复制只解决一个问题:数据从 A 点流到 B 点。它不会告诉你 A 点是不是真的挂了,不会决定 B 点要不要升主,更不会在 B 点升主之后替你把应用流量切过去。
做过手工切换的人都有体会:主库宕机之后,你要先人工确认故障——网络不通不代表实例挂了,实例挂了不代表数据盘坏了——然后决定哪台备库延迟最小、数据最完整,再登录上去执行 promote,最后还要处理老主库回切。整个流程里每一次判断都依赖人,而故障场景下人的判断恰恰是最不可靠的。数据库高可用这件事,拆开来看至少有五个环节:故障检测、切换决策、执行切换、数据保护、故障库修复与回切。PG 流复制只覆盖了第一个环节的一部分,剩下四个环节全部要靠外部机制补齐。
1.2 传统 VIP 漂移方案的软肋
传统方案最常见的形态:两台库做主备流复制,上层用 keepalived 或者 heartbeat 挂一个虚拟 IP,主库挂了之后 VIP 漂到备库,再执行 promote。这个方案看起来简单,但存在几个结构性问题。
首先是脑裂。keepalived 检测主库存活靠的是心跳和探活脚本,而探活脚本只问"数据库进程在不在",回答不了"这个数据库现在能不能安全接收写入"。一旦主备之间网络抖动,keepalived 会认为主库死了,把 VIP 飘到备库并 promote,然而老主库其实还活着、还能写数据。两个节点同时接受写的状态一旦出现,数据就在物理层面分叉了,之后任何"找补"都要靠人工从 WAL 时间线里挑一个一致的截断点,恢复时间以小时计,而且不一定能完整找回。
其次是修复链路过长。老主库恢复后要重新加入集群,PG 10 之后可以用 pg_rewind 把老主库倒带回新主库的时间线,但 pg_rewind 有一个硬性前提:数据库必须开启了 wal_log_hints 或者 data checksums,否则只能全量重建备库。很多老环境压根没开这两个参数,于是一次故障切换后,DBA 还要花时间跑一遍 pg_basebackup 全量克隆,这段时间里系统处于单点运行状态,风险极高。
还有个容易被忽视的问题:切换完全依赖人的反应时间。监控告警、逐级上报、登录跳板机、执行命令,每一步都可能出错。我见过最夸张的一次,主库凌晨两点挂了,值班同事确认故障花了四十分钟,等真正 promote 完已经快三点,业务方早就炸了。手工方案的问题不在某个具体命令,而在整个决策链路没有自动化兜底。
1.3 ETCD 在这里不是"数据库",而是"裁判"
Patroni + ETCD 这套方案之所以能成为 PostgreSQL 生态里事实上的高可用标准,关键不在 Patroni 本身写得多好,而在它把"谁当主"这个问题交给了一个有共识能力的第三方组件来裁决。ETCD 基于 Raft 协议,提供了三个能力正好是选主所必需的:分布式一致性、租约(lease)、事务化的 compare-and-swap 操作。
用大白话讲:每个数据库节点上的 Patroni 进程,都向 ETCD 的同一个 key 发起竞争,谁在这个 key 上成功写入"我是主",谁就拥有一个带有效期的租约;主节点要持续续租,租约到期后其他节点才有资格来抢。这套机制天然避免了"两个人都觉得自己是主"的局面,因为 ETCD 是单边裁决的:在同一个 key 上只有一个写操作能成功。
很多初学者会把 ETCD 理解成一个"配了高可用的数据库",然后问"为什么不用 PG 自己来存元数据"。核心区别在于:ETCD 解决的是分布式共识问题,而"分布式共识"恰恰是数据库本身不擅长的事——这就像一场比赛的裁判必须站在场外,不能由参赛选手自己兼任。Patroni 在不同 DCS 之间的选择也说明了这个逻辑,我把常见选项整理成一张表:
| 维度 | ETCD | ZooKeeper | Consul |
|---|---|---|---|
| 一致性协议 | Raft | ZAB | Raft |
| 与 Patroni 集成历史 | 最主流,官方文档默认 DCS | 支持但配置链路长 | 支持但社区资料少 |
| 运维复杂度 | 单二进制,etcdctl 简单 | 依赖 JVM,节点角色多 | 功能偏服务发现,略重 |
| 典型用户心智 | Kubernetes 都用它 | 传统大数据生态 | 微服务配置中心 |
从实际维护成本看,ETCD 是最省心的选择,这也是 Patroni 默认首选它的原因。
2. Patroni + ETCD 是怎么把"拍脑袋切库"变成"共识决策"的
2.1 一次自动故障切换的完整链路
先看一个典型的 3 节点架构:3 个 ETCD 节点组成仲裁集群,2 个(或更多)PG 节点上跑着 Patroni 和数据库实例。正常状态下,Patroni 会按照 loop_wait(默认 10 秒)的周期做一轮状态检查,并做两件事:一是主节点向 ETCD 续租 leader key;二是所有节点把自身的位置(比如备库 replay 到哪个 LSN)写进 ETCD。
当主库真的故障后,完整链路是这样的:
- 主库上的 Patroni 停止续租,leader key 的 TTL(默认 30 秒)开始倒计时。
- ETCD 上的 leader key 到期或主节点被标记失联,所有备库成为候选人。
- 各备库同时向 ETCD 发起写 leader key 的事务请求,ETCD 保证只有一个请求能成功。
- 成功抢到 key 的备库,先检查自己的 WAL 延迟,确认不超过 maximum_lag_on_failover 的限制,然后执行 promote。
- Patroni 更新 ETCD 中集群的全局配置,记录新任 leader;其他备库发现 leader 已变更,自动把复制源切换到新主库。
- 接入层(VIP、HAProxy 或应用连接池)通过 Patroni 的 REST API 健康检查发现新的主库,把流量切换过去。
整个过程不需要 DBA 登录任何一台机器,从主库故障到应用恢复写入,理想情况在 30 到 60 秒内完成。这里面其实处处是细节:TTL 和 loop_wait 的比值、lag 阈值、健康检查的探测机制,任何一个设置不够合理,自动切换都会变成"自动事故"。
2.2 TTL、loop_wait、retry_timeout 这三个数字别乱调
Patroni 配置里最容易被忽略的参数是 bootstrap.dcs 下的 ttl、loop_wait 和 retry_timeout。它们之间的关系不能随意摆放。默认值 ttl=30、loop_wait=10、retry_timeout=10,含义是:Patroni 每 10 秒做一轮状态检查和 DCS 交互,主库每隔一段时间续租一次,租约有效期 30 秒,如果一次 DCS 操作失败,最多重试 10 秒。
为什么默认把 TTL 设成 loop_wait 的三倍?因为主库可能只是和 ETCD 之间的网络抖动了几秒,数据库实例本身完全健康;如果 TTL 设得太短(比如 10 秒),一次短暂的网络抖动就会触发一轮选主,造成无谓的切换——切换本身就是一次小型的故障演练。反过来说,TTL 也不能太长,因为它直接决定了 RTO 的下限:主库死了之后,至少要等 TTL 到期 + 选主 + promote + 应用探测恢复,TTL 如果是 120 秒,那一次故障切换至少两分钟起步。
我见过不少团队把 TTL 改到 90 秒,理由是"怕网络抖动",但完全没有配合调整 HAProxy 和连接池的超时,最后故障切换 RTO 被拉到 3 分钟以上,业务方直接投诉。我的建议是:先保持默认值,再结合你的网络环境做切换演练,用演练数据反过来调整这三个参数,而不是坐在会议室里拍脑袋定数字。
2.3 防脑裂不是靠"拔网线",而是靠租约降级
传统双机高可用防脑裂的思路是"我抢不到资源就让自己死掉",比如通过串口心跳或 stony 设备。Patroni 的思路不同:每个节点上的 Patroni 都独立判断"我还能不能续租",一旦主节点发现自己无法连上 ETCD,它会进入所谓的 zombie 状态,并在超时后主动把本机的 PostgreSQL 停掉,避免继续接受写入。
这套机制有一个前提你需要理解:Patroni 本身不是数据库的代理,它是在数据库旁边"看着"数据库的管家。主库和 ETCD 失联后,如果主库还能被应用访问,应用依然能写入;Patroni 只能尽力停掉数据库,但如果数据库因为极端负载或文件系统卡死而停不下来,那窗口期依然存在。所以在生产环境里,我还会叠加两层防护:一层是 Patroni 的 watchdog 模式,另一层是在接入层用 HAProxy 同时检查 /primary 和 /replica 接口,主库失联后立刻把它从后端列表里摘掉。
2.4 运维入口:REST API 和 patronictl
Patroni 每个节点都起了一个 8008 端口的 REST API,这个接口既是运维入口也是接入层健康检查的核心。常用的路径包括 /cluster、/primary、/replica、/read-only、/leader、/health,返回 JSON 格式的状态信息。HAProxy 一般配置成请求 /primary 和 /replica,能正常返回并且内容正确,就把这个节点保留在对应后端组里;返回 503 则摘除。
日常运维主要用 patronictl 命令,最常用的几条:
list 命令会输出一个表格,包含每个成员的节点名、host、角色、状态和延迟。switchover 是计划内切换,failover 是强制切换,两者都支持 --master 和 --candidate 参数,可以在切换前指定目标和源。这里有一个运维习惯我要强调:任何一次计划内切换前,先跑 edit-config 查看当前配置存档,再跑 list 确认所有节点都是 streaming 状态,最后再执行 switchover。
3. 换到 Vastbase / GaussDB 之前,先摸清这四条兼容边界
3.1 先搞清楚内核对 Patroni 的"隐藏依赖"
很多人以为 Patroni 兼容性只取决于"数据库是不是 PostgreSQL",这个认知在原生 PG 体系内成立,但放到 Vastbase、GaussDB 这类兼容型数据库上就要打个问号。Patroni 表面上是"管理 PostgreSQL 实例",实际上它默认依赖一整套 PostgreSQL 工具链和系统视图。
| Patroni 需要的工具/接口 | 作用 | 在 Vastbase 上的常见情况 | 在 GaussDB(openGauss 系)上的常见情况 |
|---|---|---|---|
| pg_ctl | 启动、停止、promote | 通常保留 | 常用 gs_ctl,命令名和参数可能有差异 |
| pg_basebackup | 克隆备库数据 | 通常可用 | 对应 gs_basebackup,参数不一一对应 |
| pg_rewind | 老主库回切 | 大多可用 | 未必提供,需要确认内核版本 |
| pg_controldata | 数据目录状态检查 | 通常可用 | 可能不叫这个名 |
| pg_is_in_recovery() 等系统函数 | 判断角色、计算延迟 | 兼容较好 | 老内核可能只有 pg_last_xlog_replay_location 等旧函数 |
| 复制槽系统视图 | WAL 保留管理 | 兼容较好 | 需要验证行为是否一致 |
这个表大家可以打印出来,作为迁移评估的检查单用。实际情况中,Vastbase 对 PostgreSQL 生态的兼容度较高,在 Vastbase 上部署 Patroni 通常会比在 GaussDB 上顺利,但"顺利"不等于"无脑跑",每一个工具都需要实际执行一遍验证。GaussDB 的社区内核 openGauss 可以直接下载做实验,用一套最小集群提前验证 Patroni 的适配点,比拿到生产环境再踩坑划算得多。
3.2 命令名可以软链,但参数差异软链解决不了
如果 GaussDB 环境里只有 gs_ctl、gs_basebackup,没有同名工具,一个常用技巧是在 bin 目录里建软链:
但这里有个大坑:Patroni 调用工具时会带上 PostgreSQL 标准的参数,比如 pg_basebackup 的 --wal-method=stream、--write-recovery-conf,如果内核自带的对应工具不支持这些参数,软链会立刻报错。所以正确的做法是:先手动在命令行执行一遍 Patroni 将要调用的完整命令,确认参数都被支持,再考虑软链或者别名。这一步省不得,我在第 5 节会具体讲一个"表面可用、实际不可用"的案例。
3.3 参数名和系统函数的新旧差异
openGauss 系内核的源头比较早,很多系统视图和函数保留的是 PostgreSQL 9.x 的命名。比如 Patroni 在计算备库延迟时会调用 pg_last_wal_replay_lsn()、pg_wal_lsn_diff(),老内核里对应的可能是 pg_last_xlog_replay_location()、pg_xlog_location_diff()。如果内核不提供新函数,Patroni 的健康检查就会报错,节点会一直处于 unknown 状态。
遇到这种情况,要么找数据库厂商提供的 Patroni 适配分支(很多厂商已经做了),要么自己给 Patroni 打补丁,在获取状态的 SQL 里做一层兼容映射。后者的维护成本很高,我不建议普通团队硬扛。迁移评估时,把"Patroni 默认执行的探测 SQL 集合"作为一项兼容性检查内容,比只看官方支持列表更靠谱。
3.4 版本不是越高越兼容,先做 POC
聊到这里,最关键的结论出来了:从原生 PG 迁移到 Vastbase、GaussDB 之前,不一定需要把 Patroni 的所有功能都跑通,但必须先用一个最小集群做 POC,验证四件事:备库克隆、备库追平延迟、手动 switchover、模拟主库宕机的自动 failover。这四件事如果都能在 20 分钟演练里通过,架构基本可用;如果第一件就卡住,不要急着改参数,先回头看工具链兼容性。
4. 落地实录:三节点 ETCD + 双节点 Patroni 的完整搭建
4.1 拓扑选择与节点规划
我这次的环境是三台物理机,两台跑数据库,三台都跑 ETCD,也就意味着第三台机器是 ETCD 的 witness 节点,它不承载数据库,只参与 ETCD 投票。这种"双活 + 见证"的拓扑在数据库场景里比较划算:数据库本身只有两副本,但仲裁由三节点 ETCD 完成,任何一台机器故障,ETCD 还能保持仲裁可用,数据库侧也还有一台存活的备机能接受切换。
| 节点 | IP | ETCD 角色 | PG/Patroni 角色 |
|---|---|---|---|
| node1 | 192.168.1.11 | etcd1 | 数据库初始主库 |
| node2 | 192.168.1.12 | etcd2 | 数据库初始备库 |
| node3 | 192.168.1.13 | etcd3 | 仅参与仲裁,不跑数据库 |
如果你只有两台机器,也不要硬塞一个两节点 ETCD。两节点 ETCD 在挂掉一个节点之后就没有法定人数,整个集群无法选主,等于高可用失效。宁可把第三个 ETCD 放在一台很轻的机器上(ETCD 对资源要求很低,1 核 2G 都够),也不要只做两节点。
4.2 ETCD 集群搭建与自检
ETCD 安装本身不复杂,下载对应版本的二进制解压即可。我的版本选择是 3.5.x 系列,稳定性经过大量验证。配置文件(以 node1 为例):
三个节点唯一区别就是 name 和 IP。启动后用两个命令自检:
如果出现某个 endpoint 不健康,先查端口和防火墙,再看三台机器的时钟是否同步。ETCD 对时钟偏差很敏感,生产环境务必用 chrony/ntp 做时间同步,这是很多新集群"时而健康时而不健康"的隐藏原因。另外如果你的 etcdctl 版本比较老,默认走 v2 API,记得先 export ETCDCTL_API=3。
4.3 Patroni 配置文件的几个关键点
Patroni 的核心配置是 YAML 文件,我贴一个经过裁剪但可直接参考的版本(node1 为例):
几个值得展开说明的点。第一,etcd 配置段我写的是 etcd3 而不是 etcd,这是为了明确使用 ETCD v3 API;如果你的 Patroni 版本较老,可能还停留在 v2 API 的 etcd 段,两者混用会连不上。第二,bootstrap.dcs 下写的 postgresql.parameters 是集群级参数,第一次初始化时会写入所有节点,后续用 patronictl edit-config 统一调整,不要在每台机器的 YAML 里单独改,否则集群状态会被覆盖。第三,initdb 里必须加上 data-checksums,这是 pg_rewind 能用的前提;wal_log_hints 也要开,双保险。第四,archive_command 这里先用最简单的 cp 到本地目录,生产环境建议换用 pgBackRest 或对象存储,重点是把 WAL 归档链路提前打通,否则复制槽一旦出问题,WAL 堆积就没人兜底。
4.4 针对 Vastbase / GaussDB 的参数调整
如果目标数据库是 Vastbase,且它的 bin 目录里工具名和 PG 一致,配置基本不用动,只需要把 bin_dir 指到对应路径。如果工具名不一致,按 3.2 的方法软链后再把 bin_dir 指过去。
如果是 GaussDB(openGauss 系),我的建议是先做两个动作:一是把 use_pg_rewind 显式改为 false,先验证基础链路能跑通;二是把 use_slots 保持 true,但要单独验证复制槽是否真的能阻止 WAL 删除,如果内核的复制槽行为与 PG 不一致,就要改用外部 WAL 归档策略来兜底。
关掉 pg_rewind 意味着老主库回切时必须走全量克隆,切换后重建备库的时间会变长。这是一个为了兼容性付出的代价,等厂商适配完善后再回收。
4.5 切换演练
集群起来之后,第一件事不是接业务,而是把切换演练跑几轮:
停主库这种方式模拟的是"实例异常退出",数据库进程没了但机器还活着,是最常见的演练方式;更狠的演练是直接拔网线、挂起虚拟机,模拟网络分区,这能验证脑裂防护逻辑。两轮演练都通过之后,再接入 HAProxy 和 VIP。
5. 演进路上我踩过的五个坑,以及排查链路
5.1 坑一:ETCD 节点数用了偶数,还以为是"双机热备"
有一回客户环境是两节点 ETCD,日志里时不时出现 leader 选举失败,应用侧数据库切换一直没触发。排查链路是这样的:先看 ETCD 状态,etcdctl endpoint health --cluster 显示两个节点互相 ping 不通;再看 ETCD 日志,发现一直选不出 leader。根因就是两节点根本没有法定人数,只要有一个节点网络抖动,整个 ETCD 就不可写了。
Patroni 依赖 ETCD 做所有决策,ETCD 不可写,leader key 就无法续租和竞争,自动切换自然永远不会发生。这个坑的本质是"按双机热备的思维去设计仲裁集群"。记住:仲裁节点数必须是奇数,3 节点容忍 1 台故障,5 节点容忍 2 台故障;偶数节点除了浪费资源,还可能在故障时形成僵局。
5.2 坑二:pg_rewind 在目标数据库上"表面可用、实际不可用"
在 Vastbase 上遇到过一次很奇怪的现象:use_pg_rewind 配置为 true,日常 list 状态也正常,但真正触发老主库回切时,日志里报 pg_rewind 执行失败,备库重建回退到了全量克隆。后来排查发现,Vastbase 虽然提供了 pg_rewind 同名命令,但某些版本对 timeline 文件的处理逻辑和 PG 不完全一致,只有在数据目录干净、时间线差异小的时候才走通。
排查链路:先在故障节点手动执行 pg_rewind 指向新主的 data 目录,观察是否报错;再检查所有节点的数据目录是否开启了 data checksums,用 pg_controldata -D $PGDATA | grep checksum 确认;最后看 Patroni 日志里的回退标记。根因确认后,我把 use_pg_rewind 改为 false,排除了这个不稳定因素。这件事也让我意识到,POC 阶段一定要把"老主库回切"单独列一项,跟新备库克隆分开验证。
5.3 坑三:复制槽导致 WAL 堆积,差点撑爆磁盘
Patroni 默认 use_slots=true,会在主库上为每个备库创建名为 patroni_replica_xxx 的复制槽。复制槽的作用是:备库即使暂时落后,主库也保留它需要的 WAL 不被清理。这本来是保护数据的手段,但反过来也容易埋雷。
有一次备库因为维护提前关停了一天,主库上对应复制槽的 restart_lsn 一直停留在很远的位置,WAL 目录持续增长,直到磁盘告警。排查过程很简单:
发现 patroni 复制槽 active=false 且 lag 巨大。根因是备库长期离线,Patroni 没有自动清理 slot。解决思路分两步:短期,确认备库不再需要后手动 drop slot 释放 WAL;长期,调整监控策略,对复制槽 restart_lsn 与当前 LSN 的差距做告警,而不是等磁盘满了才发现。需要说明的是,上面这条 SQL 用的是 PG 10+ 的函数命名;遇到老内核环境,把 pg_wal_lsn_diff 换成 pg_xlog_location_diff,把 pg_current_wal_lsn 换成 pg_current_xlog_location 即可。
在 GaussDB(openGauss 系)上,复制槽行为可能更严格,甚至不自动回收故障备库的 slot,这一步验证必不可少。
5.4 坑四:Patroni 升级后 etcd 配置段不认了
有一版 Patroni 升级之后,节点全部进入 maintenance 状态,日志里一直报 DCS 连接错误。排查发现,老版本配置文件里写的是 etcd 配置段(对应 ETCD v2 API),而新版本默认不再支持 v2 通信,需要把配置段改成 etcd3,并且 hosts 的写法也从"逗号分隔字符串"变成了 YAML 数组:
这类问题最难受的地方在于:Patroni 进程没挂,list 却完全不可用,很容易先把人引向网络问题排查。我的经验是,升级 Patroni 版本前先看 CHANGELOG 里关于 DCS 的 breaking change,升级后第一时间跑 patronictl list 验证,不要等到生产切换演练时才发现。
5.5 坑五:网络抖动引发"假切换",应用连接池却还连在老主库上
有一次演练拔网线模拟网络分区,Patroni 侧自动切换成功了,新主库也 promote 了,但应用侧依然报连接拒绝。排查链路:先看 patronictl list,发现新主已就绪;再 curl 新主的 /primary 接口,返回正常;最后查 HAProxy 配置,发现它探活的是数据库 5432 端口,而不是 Patroni 的 8008 健康检查接口,而老主库虽然和 ETCD 失联,PostgreSQL 进程却还活着,5432 端口还能正常 accept 连接,于是 HAProxy 一直不把老主库摘掉。
这个坑的本质是:Patroni 的共识决策和接入层的流量决策没有联动。正确的做法是 HAProxy 用 Patroni 的 REST 接口做健康检查,而不是直接探数据库端口:
同时把 Patroni 的 failsafe 机制打开,主库与 ETCD 失联后尽快将数据库降级或停止,减少窗口期。那次之后,我把所有接入层的健康检查都统一改成了走 Patroni REST API,再没出现过"切换成功但连不上"的怪事。
6. Patroni + ETCD 的边界在哪里,下一步往哪走
6.1 这套架构的适用边界
Patroni + ETCD 适合的是"我有一批物理机或虚拟机,数据库实例数量可控,希望有一个可靠的主备自动切换能力"的场景。它不是一个全自动的数据库运维平台,实例的创建、版本升级、参数变更、备份恢复仍然要 DBA 自己管理。如果团队的诉求是"点一个按钮自动交付一套高可用集群",那更合适的方案是 Kubernetes 生态的 Operator。
6.2 从 Patroni 到 Operator 的演进路径
Kubernetes 上的 PostgreSQL Operator(比如 CloudNativePG、Zalando Postgres Operator)核心思想跟 Patroni 一脉相承:同样依赖分布式共识来选主、同样用流复制做数据复制、同样有健康检查和自动恢复。区别在于,Operator 把"节点"从"你手动维护的机器"变成了"K8s 里的 Pod",把状态感知和故障恢复下沉到了控制器循环里。
如果你已经在 Patroni 上积累了经验,切到 Operator 并不是从零开始:Operator 里的很多概念(克隆、时间线、复制槽、同步模式)都可以对应到 Patroni 的配置项上。迁移的关键是先把应用层的连接方式从"直连 IP"改成"通过 Service / 负载均衡访问",这步不提前做,换什么架构都会迁得痛苦。至于 Vastbase、GaussDB 这类数据库,厂商生态里也陆续出现了各自的集群管理组件,有的直接基于 Patroni 改,有的自己实现选主。选型时不要只看宣传材料,先问清楚两个问题:选主逻辑是否支持同步多副本;故障切换后老主回切能否自动完成。这两个问题能过滤掉一大批画饼方案。
6.3 给正在做评估的团队几条实在建议
第一,把"自动切换"和"自动恢复"完全分开考核。很多方案宣传的是自动切换,真正难的是切换之后老主库的自动化回切。第二,RTO/RPO 要先定指标再做架构。比如你的业务能不能容忍丢失最后几秒的事务,决定了要不要开 synchronous_mode;如果必保零丢失,备库要配置成 synchronous standby,这会直接影响写入性能。第三,所有高可用参数都要以演练数据为准,不要让厂商的默认配置直接进生产。
最后分享一个小经验。我在这次从原生 PG 迁到 Vastbase、GaussDB 的项目里,最大的收获不是学会了一堆新参数,而是意识到:高可用架构的演进,本质上是从"靠人判断"走向"靠共识决策"的过程。Patroni + ETCD 的价值不在于它的代码有多炫,而在于它把一个最不可控的环节——故障切换时人的判断——变成了一个可预测、可演练、可度量的自动化流程。如果你的团队正准备做类似的改造,我的建议很朴素:先把切换演练跑够二十次,把每一次的日志、时长、问题都记录下来,再谈上线。这套流程走下来,你对 Patroni、ETCD 和数据库本身的理解,会比读十篇文章都深刻。