Patroni + etcd + PostgreSQL 高可用架构(二):同步复制、pg_rewind 与流量切换的数据层取舍

举报
yd_220455628 发表于 2026/11/10 09:54:02 2026/11/10
【摘要】 控制平面选出新主只是前半程。同步复制的零丢失承诺受限于“同步备库存活”前提;pg_rewind 的可用性由故障前的 wal_log_hints/checksums 决策预先锁定;HAProxy 的 /primary 健康检查与连接回收决定应用感知的是短暂闪断还是连接串失效。本文拆解这三层数据取舍。

第一期讨论了 Patroni 如何借助 etcd 的领导者锁,在控制平面上回答“谁是主库”。但“不脑裂”只解决了问题的一半。另一半在数据层:切换发生时,会不会丢事务?旧主重新加入时,数据能不能对齐?应用端能不能在不改连接串的情况下把写流量导向新主?这三个问题的答案,分别由同步复制语义、pg_rewind 的前置条件和流量代理的健康检查策略决定。

同步复制:零丢失不是一个开关,而是一组取舍

Patroni 默认以异步复制运行。异步模式下,主库提交事务时不等备库确认,故障切换时未复制到新主的事务会留在旧主的分叉时间线上,实际丢失量的最坏情况上界是 maximum_lag_on_failover 字节的事务日志,加上最后 ttl 秒内写入的数据量,平均约为 loop_wait 一半时间内的写入量。对于能接受短暂数据回退、优先保可用性的业务,这是合理默认。

当业务要求“已向客户端返回成功的事务不能在切换后丢失”时,需要开启 Patroni 的 synchronous_mode。开启后,Patroni 会管理 synchronous_standby_names,仅在确认候选备库包含所有可能已向客户端返回提交成功的事务时,才允许将其提升为主库。同步复制的本质是:主库在收到同步备库的 WAL 确认之前,不向客户端返回成功。代价是写吞吐受网络往返制约,且如果同步备库不可达,主库实际上会退化为只读,因为无法满足同步确认条件。

但即使开启同步复制,也不等于在所有故障组合下都保证零丢失。Patroni 文档明确指出一个边界场景:如果主库和当前充当同步备库的节点同时故障,Patroni 会提升第三个节点,而该节点可能不包含所有已提交事务。这是因为在故障发生的瞬间,系统无法知道同步备库是否已经将最后一笔确认过的 WAL 持久化。因此,同步复制的“零丢失”有一个隐含前提:至少有一个持有完整已确认 WAL 的节点存活。

synchronous_mode 之外还有一个更严格的选项:synchronous_mode_strict。开启 strict 后,如果没有可用的同步备库,主库会拒绝写事务,而不是退化为异步继续写。这是“宁可不写,也不冒丢数据的风险”的选择。实践中的常见分界是:金融核心类业务开启 strict,普通业务仅开启 synchronous_mode,在数据安全与写可用性之间保留弹性。

另一个容易被忽略的细节是同步备库的指派逻辑。当主库和同步备库同时不可用时,Patroni 在没有同步备库存活的情况下,会基于备库接收或重放的 LSN 值来优先提升最超前的节点,而不是简单地按故障切换优先级排序。这意味着故障切换的候选优先级在同步模式下会被数据新鲜度覆盖,这是数据安全优先于配置优先的体现。

pg_rewind:旧主回归的前提,在故障发生之前就已经确定

故障切换完成后,旧主恢复上线时需要以从库身份重新加入。如果旧主在故障前有未复制到新主的 WAL,它的数据目录与新主的时间线已经分叉。pg_rewind 的设计目的就是避免全量重建:它定位旧主时间线分叉的位置,从新主复制分叉之后被修改的数据块,使旧主的数据目录与新主对齐,而不需要拷贝整个数据库。

这个机制能生效,依赖一组在故障发生之前就必须满足的前提条件。pg_rewind 要求目标服务器,即旧主,在生成需要回退的那段 WAL 期间,已经启用了 wal_log_hints,或者在 initdb 时启用了数据校验和;此外,full_page_writes 也必须在同一时期为开启状态。关键点在于“事后开启无效”:如果分叉发生后才把 wal_log_hints 打开,无法重建历史页变更证据;同样,事后启用校验和也不能追溯修复一个已经不合格的分支。Patroni 在配置中通过 use_pg_rewind 为 true 启用该行为,但它自身也会通过 pg_controldata 做类似的前置检查。

这意味着一个容易被低估的运维事实:pg_rewind 能否在故障切换后自动工作,取决于集群初始化时或至少分叉发生之前的配置决策,而不是切换瞬间的应急操作。如果旧主在分叉期间没有满足上述条件,Patroni 在旧主重新加入时会退回到更昂贵的路径:从新主做一次全新的基础备份来重建该备库。对于数据量较大的集群,这意味着恢复时间的显著延长。因此,在集群规划阶段就确认 wal_log_hints 或数据校验和是否启用,是“故障切换后旧主能否快速回归”的前置条件,而不是一个可选的调优项。

pg_rewind 还有几项硬性兼容性要求:源,即新主,和目标,即旧主,必须具有相同的数据库系统标识符、相同的 PostgreSQL 主版本,以及兼容的架构或构建;目标必须被干净地停止才能执行回退。在 Patroni 的管理下,旧主重新加入时 Patroni 会停止其 PostgreSQL 进程并触发 rewind,但如果目标未干净关闭,pg_rewind 的非 dry-run 操作会尝试进行单用户崩溃恢复,缺少相关库或配置可能导致该步骤失败。这些细节说明,旧主回归的可靠性不仅取决于 Patroni 的调度,也取决于 PostgreSQL 层面数据目录的健康状态。

流量切换:两个独立选举,以及一个容易被忽略的连接回收问题

控制平面选出了新主,数据层确认了新主可以安全接管,但应用端感知到的仍然是“连接断了”或“写不进去了”。流量切换层的任务,是把应用对数据库拓扑的感知隔离掉。

一个被广泛采用的组合是 Patroni 感知的 HAProxy 加 Keepalived VIP。这里存在两个相互独立的选举:Patroni 通过 DCS 决定哪个 PostgreSQL 节点可以成为主库;Keepalived 通过 VRRP 决定哪个代理主机持有 VIP。两者的职责必须严格分离:Keepalived 不应该去推断谁是 PostgreSQL 主库,HAProxy 也不应该去提升 PostgreSQL。应用连接的是一个稳定的 VIP 或 DNS 名称,HAProxy 根据 Patroni 的角色感知健康检查端点,将写连接路由到当前持有领导者锁的主库。

HAProxy 的配置中有两个关键点。健康检查不应只检查 PostgreSQL 端口的 TCP 可达性,而应直接请求 Patroni REST API 中仅在节点持有领导者锁时才返回成功的 primary 角色端点。这意味着 HAProxy 路由的不是“活着”的节点,而是“被授权为可写”的节点。另一个关键参数是 on-marked-down shutdown-sessions:当 HAProxy 检测到某个节点不再合格时,主动关闭已建立的连接会话,而不只是把新连接导向其他节点。如果没有这个参数,已有的数据库连接可能仍然挂在旧节点上,而旧节点在切换后会被 Patroni 降级为只读或停止,应用会在下一次写入时收到错误,而不是在切换瞬间被干净地迁移。

init-state fully-down 是另一个防御性细节:让 HAProxy 在启动时先将所有后端置为不可用,直到某个后端通过了连续的健康检查,即 rise 2,才纳入轮转,避免在角色检查尚未完成时把连接误发给一个从库。这些配置共同指向一个原则:流量层的健康检查必须跟随控制平面的角色判定,而不是自行发明数据库健康的标准。

在生产实践中,这套组合的效果被描述为“故障切换与主动切换对业务侧除零星闪断外几乎无感知,应用不需要修改连接串或重启”。需要如实承认的是,切换过程中仍然存在一个不可避免的窗口:从旧主失联到新主被提升、HAProxy 健康检查完成收敛,期间写请求会短暂失败。Patroni 的 RTO 取决于 ttl、loop_wait 和 retry_timeout 的组合,Pigsty 的默认异步模式目标 RTO 为 45 秒内。流量层能做到的是让这个窗口对应用表现为“短暂闪断后的重连”,而不是“连接串失效、需要人工切换”。

数据层取舍的总结

这三件事之间的关系可以这样概括:

同步复制决定了“切换时已确认的事务会不会丢”,它的代价是写延迟和同步备库不可用时的写退化,strict 模式则把这种退化进一步收紧为拒绝写入。

pg_rewind 决定了“旧主回归需要多长时间、是否需要全量重建”,但它的可用性在故障发生前就已经被 wal_log_hints 或 checksums 的配置决策锁定。

流量切换决定了“应用感知到的中断是短暂重连还是连接串失效”,HAProxy 对 primary 角色端点的健康检查和 shutdown-sessions 行为,是让切换对应用近乎无感的两个关键工程细节。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。