关于我们

质量为本、客户为根、勇于拼搏、务实创新

< 返回新闻公共列表

PostgreSQL 高可用怎么才算真可用:同步复制、法定票数和脑裂边界的资源账

发布时间:2026-10-15

PostgreSQL 高可用怎么才算真可用:同步复制、法定票数和脑裂边界的资源账

主库下线后备库提主:老主库回来的那几分钟写入算谁的

凌晨两点十七分,主库所在的那台机器失联。Patroni 在二十几秒后把备库提为主库,应用重连成功,写入恢复,值班群里报了一声"已切换"。四分钟后,老主库那台机器的网络回来了,PostgreSQL 进程还在,它在被摘掉之前又接了一批写入——负载均衡的健康检查有间隔,连接池里还留着几十条建好的 TCP 连接,这些连接不会因为你换了个主库就自己消失。于是两边的 WAL 已经在两条不同的时间线上各自往前走了。

这时候该合并还是丢弃?答案是:合并不是选项。PostgreSQL 的流复制是物理复制,复制的是数据页的字节变化,不是逻辑操作。两条时间线上对同一个数据页的修改没法在物理层面做合并,因为后一次修改依赖前一次留下的页状态。能走的只有三条路,而且选择权不在工具手里。

第一条路:如果老主库上多出来的那批写入可以丢,用 pg_rewind 把老主库回退到分叉点,对齐到新主库的时间线,重新挂成备库。pg_rewind 只搬运发生变化的块,比重新做一次基础备份快得多,但它有前置条件——目标端需要开启 full_page_writes 或数据页校验和,具体以所用版本官方文档为准。第二条路:如果那批写入必须保留,比如订单已经落库并且已经给上游返回了成功,那就只能人工把那段时间的数据导出来,在应用侧做对账和补写,这一步没有任何工具能替你做。第三条路:如果你能证明老主库上的那批写入没有产生任何外部可见的效果(事务最终回滚、或下游全部拒绝),按第一条处理。

所以这个场景真正的教训是:问题不出在"切没切成功",而出在"老主库的写入通道是什么时候被切断的"。Patroni 能做的是让老主库自己退位,它管不了应用连接池里那些已经建立的连接。这就是为什么 fencing 才是高可用设计的下半场,而很多人把上半场做完就以为完事了。

把这件事翻译成一个可以算的量:双写窗口等于从主库实际失去续租能力,到它的写入通道被彻底切断,中间这段时间里可能落进去多少条事务。这个数字乘上你的写入 TPS,就是最坏情况下需要人工对账的条目数。后面所有内容基本都在回答一个问题——怎么把这个窗口从"不可控"变成"可量化"。

同步复制的写路径代价:一次提交要等几次落盘

要说清同步复制的代价,得先把一次 COMMIT 的等待链拆开。开启同步复制后,客户端发出 COMMIT 到收到成功返回,中间经过:主库把 WAL 写入本地缓冲区并发起 fsync;WAL 通过网络发给备库;备库接收并写自己的 WAL 段,同样发起 fsync;备库回一个确认;主库收到确认后才向客户端返回提交成功。相比异步复制,多出来的部分是一个网络往返时间加上备库那次 WAL fsync 的时间。

synchronous_commit 这个参数决定了等在哪一步(取值与语义以所用版本官方文档为准)。默认值 on 是等备库把 WAL 刷到持久存储;remote_apply 是等备库把 WAL 回放完成,代价最高,但换来备库上的读操作立即可见刚写入的数据;remote_write 只等备库把 WAL 交给操作系统,也就是进到页缓存就返回,不保证落盘,只有主备同时掉电才会丢数据,通常拿来换一点延迟;local 是不等备库,等于就地降级为异步;off 连本地 fsync 都不等,最多丢一个提交延迟窗口的数据。这几个档位不是"性能高低的连续调节",而是"把持久化的承诺放在哪一层"的离散选择,选错档位会造成"以为没丢实际丢了"的误会。

延迟量级可以给个直观参照:本地 NVMe 上一条小事务的 fsync 大致在零点几毫秒到一毫秒的量级;同机房往返通常小于一毫秒;同城不同机房在一到三毫秒;跨城在十到三十毫秒量级。于是同一条事务的提交延迟,从本地的一毫秒左右,变成同城三毫秒左右、跨城可能二十五毫秒以上。这不是百分比的问题,是把写入延迟的地板整个抬起来了。

但吞吐并不按同样比例下降。PostgreSQL 有组提交机制,多个并发事务的 WAL 刷盘可以合并成一次 fsync,所以高并发场景下每秒事务数的损失通常小于单条延迟的涨幅,相关的调节参数(commit_delay、commit_siblings 一类)也以所用版本官方文档为准。真正被精准打击的是低并发、串行依赖的写入路径:一个请求里连续多次往返数据库的 ORM 逻辑,或者每笔都要等提交返回才继续的支付类流程。这种情况下同步复制带来的延迟会乘以往返次数,一毫秒变三毫秒的差在十次往返后就是二十毫秒。

判断规则可以直接写成一条取舍:如果业务写入是"每秒几千条、由少数几个连接串起来的小事务",跨机房同步复制基本不该考虑;如果是"每分钟几百条、每条都必须不丢"的账务流水,同城同步复制的两三毫秒代价通常值得付。介于两者之间的场景,先去压测,别靠估算拍板。

两节点加一个见证节点,为什么和三节点不是一回事

先把两节点的问题说死。网络分区发生后,A 和 B 各自看到自己活着、对方失联。A 想提主,它需要证明"B 已经不可能再接受写入"。但 A 只有自己的一票,两节点的多数派是二,A 拿不到;B 同理。双方都无法自证,这是机制上的死结,不是靠调参数能绕过去的。任何声称"两节点也能保证不脑裂"的方案,实际都引入了第三方裁决者,只是你可能没注意到它在哪。

第三方裁决者通常有三种形态:外部见证节点,不跑数据库,只参与集群成员判定;共享存储上的租约,谁能拿到盘上的独占锁谁就是主;带外的电源或网络级隔离,直接把疑似死掉的那台断电或断网。第一种最便宜,第二种依赖存储能力,第三种最彻底但在云上基本拿不到。

三节点(一主两备)和两节点加见证,在数据层面完全不是一回事。两节点加见证只有一份副本,任何时刻只要挂一台,你就只剩一份数据;而见证节点上没有数据,如果主库的数据盘本身损坏,它帮不上任何忙。三节点有两份副本,可以配成法定票数同步——把 synchronous_standby_names 写成 ANY 1 (备库一, 备库二) 的形式(语法以所用版本官方文档为准),任意一个备库掉线或变慢,写入都不受影响,RPO 仍然是零,要两台备库同时出问题才会降级。

这就是为什么"两个数据库节点加一台只跑 DCS 的小机器"是常见的低成本解法:它买到的不是数据冗余,而是法定票数。那台小机器的配置要求极低,不需要多少内存也不需要多少核,但它必须落在独立的故障域——不能和主库在同一台宿主机、同一台交换机下,否则它制造的是一个三节点集群的假象,实际故障域还是一。见证节点的具体配置项在不同 Patroni 版本之间有变化,早年是一个布尔开关,较新版本走标签方式,以所用版本官方文档为准。

这里有一条明确的取舍:能上三节点就别上两节点加见证。见证节点解决的是"谁当主",它不解决"数据还剩几份"。更要紧的是,两节点加见证恰恰是最容易强化"有了高可用就不需要备份"这个误解的架构,因为它看起来像是三份。实际上它不是。

复制与切换方案的取舍清单

把前面几节的选择摆到一张表里,横向对比的是同一个问题:切换由什么触发、切换时会丢多少、平时要多付多少延迟。表里没有"最好的方案",只有"你的 RPO 落在哪一档"。

方案 切换触发条件 数据丢失风险 写入延迟代价 适用场景
异步复制加单个备库 主库进程退出,或 DCS 上的 leader 租约过期 可能丢失尚未传出的 WAL,量级等于故障时刻的复制延迟 接近单机,主库不等任何网络确认 内部系统、写入可重放、RPO 容忍分钟级
同步复制加单个备库 同上,提主前确认目标备库已收到全部 WAL 备库在线时 RPO 为零;备库掉线后自动降级为异步即让渡 RPO 每条写事务叠加一个网络往返加备库 fsync 账务、订单一类不能丢的主数据
法定票数同步(一主两备) 同上,且按最快确认的备库判定 任一备库在线即 RPO 为零,可容忍一台备库故障 叠加一个往返,但不受单台备库变慢拖累 同城三节点,既要不丢数据又要抗单点
remote_apply 强一致 同上,且备库回放完成后才确认 RPO 为零,且备库读取无延迟窗口 叠加往返再加备库回放时间,几档里最高 读写分离且读必须立刻看到刚写入的数据
异步复制加落后阈值闸门 同上,但落后超过阈值的备库不允许被提为主 丢失量被阈值封顶;极端情况下无人可接导致停写 接近单机 跨机房容灾,接受有界丢失换取不拖慢写入

法定票数到底由谁在数:DCS 层的租约而不是数据库层

很多人把 Patroni 的防脑裂理解成"数据库节点之间投票选主",这个理解是错的。投票不发生在数据库层,发生在 DCS 层。

实际机制是这样:集群里有一把 leader key 存在 DCS 中,DCS 可以是 etcd、Consul、ZooKeeper,也可以是 Kubernetes 里的配置对象,具体支持范围以 Patroni 官方文档为准。谁持有这把 key 谁就是主库,key 带一个存活时间。主库必须周期性续租,续不上就失去主库身份。DCS 自身用 Raft 一类的共识协议保证这把 key 的写入有全局顺序、并且只有多数派确认后才生效。

所以真正决定"会不会脑裂"的是两件事。第一件是 DCS 集群自身有没有多数派:三个成员的 etcd 能容忍一台故障,五个成员能容忍两台;而两个成员的 etcd 没有意义,它的多数派是二,等于任何一台挂掉整个 DCS 就不可写。第二件是那把 key 的存活时间有多长:它决定了老主库在失去续租能力之后,最坏还能自认为是主库多久。第二个数字就是双写窗口的上界。

Patroni 里 ttl、loop_wait、retry_timeout 三个参数之间有硬性关系,官方文档要求 ttl 不小于 loop_wait 加上两倍的 retry_timeout,否则会出现主库还没来得及续租就被别人抢走的情况,具体约束与默认值以所用版本官方文档为准。常见的一组取值是 ttl 三十秒、loop_wait 十秒、retry_timeout 十秒。

有了这三个数,RTO 就能粗算:最坏切换时间约等于 ttl,加上备库追赶剩余 WAL 的时间,加上提主后的启动检查时间,再加上应用重新建立连接的时间。把 ttl 从三十秒降到十五秒,RTO 能省十几秒,代价是一次 etcd 的慢响应、一次网络抖动就可能触发误切换,而误切换本身也是一次停写。反过来把 ttl 抬到六十秒,稳定性变好,但真出事时停写时间和双写窗口都翻倍。

这个账没有标准答案,但有明确的算法:先问业务"停写三十秒能不能接受",再问"最坏情况下出现多少条需要人工对账的双写记录还能接受",两个答案一起才能定 ttl。只问第一个问题的团队,通常会在半年后遇到一次解释不清的数据不一致。

网络分区来的时候,集群该停写还是该继续写

这个问题由 DCS 成员的部署位置决定,不是 Patroni 的参数能决定的。假设主库在机房 A、备库在机房 B,三个 DCS 成员怎么放,直接决定了分区时的行为。

三个都放 A:A 整体失联时,B 那边一个成员都拿不到,永远凑不出多数派,B 上的备库不会自动提主,集群选择停写。这是正确行为,因为此时没人知道 A 是不是还在写。三个都放 B 是对称的同一个问题。A 放两个、B 放一个:A 失联时 B 只剩一票,还是拿不到多数派,仍然停写。只有 A 放一个、B 放一个、第三个放 C(第三个机房,或者云上一个网络路径独立的节点),A 失联时 B 和 C 才能构成多数派,B 才有机会提主。

结论很直接:如果你只有两个机房,又希望机房级故障能自动切换,DCS 必须有第三个故障域。这个第三故障域可以很小、很便宜,但它必须是独立的网络路径。这正好和前面说的"两个数据库节点加一台只跑 DCS 的小机器"对上——那台小机器的价值在 DCS 层,不在数据层。

反过来看:如果 DCS 全在主库所在的机房,那这套高可用实际上只能防单机故障,防不了机房故障。这没有错,很多业务只需要这个级别,但要在设计文档里写清楚,别让运维以为跨机房能自动切。真实事故里,"以为能切结果没切"造成的停写时间,往往比"一开始就知道不能切"多好几倍,因为前者还需要花时间排查为什么没切。

第三故障域还有一层隐藏成本:DCS 跨机房部署会让每一次续租都要等多数派跨机房确认。三个成员分散在往返二十毫秒的三个机房时,续租本身就在二十毫秒量级。ttl 设三十秒时这完全无所谓;ttl 设五秒时,续租开销占比上升,对抖动的敏感度也跟着上升。所以想压低 ttl 换 RTO,就得先确认 DCS 的跨机房往返能不能压下去,否则两个目标是互相打架的。

同步复制该在什么时候降级为异步

同步复制最危险的失败模式不是慢,是永久阻塞。如果 synchronous_standby_names 写成 FIRST 1 (备库一, 备库二) 的形式,备库一挂了会等备库二,两个都挂,主库上所有写事务会卡在等确认这一步,永不返回。这不是性能问题,是整站写不可用,而且从监控上看只是"延迟变高",很容易被误判成慢查询。

所以正确的设计不是"要不要开同步",而是"同步备库失效时怎么自动降级"。Patroni 提供的是动态摘除:同步备库不可用时把它从 synchronous_standby_names 里拿掉,集群降级为异步继续服务,等它回来再挂回去。这个动作本身就是一次 RPO 让渡——降级那一刻起,你的 RPO 从零变成"未复制出去的 WAL 量"。

三类该主动降级的时机值得提前写进运维手册。计划内的备库维护:先手动把同步降级为异步再做维护,别让 Patroni 在维护中途自动摘除,前者的行为更可预期,回滚路径也更清楚。备库持续落后:如果备库因为大查询、磁盘变慢或者批量导入而跟不上,同步复制会把主库拖到和备库一样慢,这时候与其让主库等,不如降级,判定阈值用复制延迟的字节数或时间,配合下面那个闸门一起看。跨机房往返超过写入延迟预算:这个要提前算,不要等线上抖动了才发现。

maximum_lag_on_failover 是另一个方向上的闸门:备库落后主库超过这个 WAL 字节数,就不允许它被提为主。设得太小会出现"主库挂了但没人能接",集群宁可停着也不切,这在业务侧看起来就是"高可用失效了";设得太大就接受更大的数据丢失。它和 ttl 一样是 RPO 与 RTO 的旋钮,只是方向相反,而且它经常被设完就再也没人回头看过。

这里有一条必须说破的取舍:如果你不能接受任何数据丢失,那么"同步复制加自动降级"这个组合本身就是自相矛盾的。你要的是同步复制加停写,也就是接受在备库全挂时主库停止服务。Patroni 允许你这样配,但很多团队在配的时候根本没意识到自己选的是哪一边,直到出事那天才发现自己选的是降级那一边。

Patroni 依赖的 DCS 要不要单独配磁盘

要。而且要按控制面的标准配,不是按"一个小服务"的标准配。

etcd 的每一次写入都要先落 WAL 再 fsync,成员之间的心跳、选举超时全都建立在这个 fsync 能按时完成的前提上。etcd 的默认心跳间隔与选举超时在百毫秒到秒级这个量级,具体默认值以所用版本官方文档为准。如果 WAL 盘的 fsync p99 冲到几百毫秒甚至秒级,etcd 成员会频繁判定 leader 失联、反复发起选举。etcd 抖动期间,leader key 的写入和续租会失败或超时,Patroni 拿不到租约,就可能把一个完全健康的数据库主库降级。一次磁盘 IO 抖动就这样演变成一次数据库切换。

etcd 官方文档对存储有明确的性能建议,常见口径是 WAL 盘 fsync 的 p99 应控制在十毫秒量级以内,具体数值与官方提供的检测方式以所用版本官方文档为准。这条建议不是给"高性能场景"准备的,是给所有场景准备的,包括看起来没什么负载的小集群。

三条部署约束值得写进规范。DCS 不能与应用混部:应用的日志刷盘、备份压缩、批处理导入会抢走 IOPS 和 fsync 带宽,而 DCS 需要的恰恰是稳定的低尾延迟而不是大吞吐,两者挤在一块盘上,应用跑得越忙,数据库集群抖得越厉害。DCS 的盘最好是独立的物理盘或独立的云盘,只做目录隔离挡不住 IO 争抢。DCS 节点对 CPU 和内存的需求很低,钱应该花在磁盘和网络上,而不是核数上——这一点上 DCS 节点的选型逻辑和数据库节点正好相反。

明确的取舍是:把 DCS 塞在数据库节点上和 PostgreSQL 共用一块盘,是很多团队的第一版做法,小规模跑起来也没事。真正出问题的时机通常是业务量上来之后,而那时候的故障表现是"莫名其妙的主库降级",排查方向极容易跑偏到数据库本身,很少有人第一反应去查 etcd 那块盘的尾延迟。

数据库节点的内存、WAL 盘与连接池该怎么分档

数据库节点的选型逻辑和大多数应用服务器不一样:内存优先级高于 CPU。原因是热数据集要能放进 shared_buffers 加操作系统页缓存,放不进去的话随机读会直接落到磁盘上,而这时候加多少核都没用。shared_buffers 的常规取值是物理内存的四分之一左右,剩下的留给页缓存、连接进程和工作内存,具体建议值以所用版本官方文档为准。

工作内存的坑在于它是按连接、按排序操作分别计算的。max_connections 开到几百,再配上较大的排序内存,一条并发的复杂查询就可能把内存打穿。正确做法是控制连接数,用连接池把应用侧的大量连接收敛成数据库侧的少量连接,而不是靠堆 max_connections 硬扛。连接池的事务级复用模式对预编译语句有使用限制,改造前要逐项验证。

磁盘要分两类看。WAL 所在的盘是顺序小写加频繁 fsync,对尾延迟极度敏感,它决定了提交延迟的地板;数据盘看的是随机读写能力和容量。把 WAL 单独放到一块低延迟盘上是常见做法,两块盘共用时批量导入一类的大 IO 会直接污染 WAL 的 fsync 尾延迟。容量规划上,WAL 的保留量要能覆盖一次备库长时间离线后重新追上的时间,否则备库需要重建。

核数的判断标准反而简单:看并发活跃连接数和并行查询的开启情况。多数 OLTP 场景在十六核以内就能跑得很舒服,超过之后瓶颈往往先出现在锁竞争和 WAL 写入上,而不是 CPU。把预算从核数挪到内存和磁盘上,通常收益更大。

这类按角色分档的需求,在选服务器的时候最好一开始就说清楚,而不是按统一规格整机下单。一万网络的服务器租用方案里,数据库节点和轻量控制面节点是可以按不同档位分开选的,数据库节点把内存和磁盘档位拉上去,DCS 这类控制面节点反而可以降到很低的档位、把预算留给独立的低延迟盘——这种分法比整机统一配置更贴合 Patroni 集群的实际资源分布,具体规格与价格需询价。

同步复制下的网络质量要看到 p99 而不只是平均延迟

同步复制把网络延迟直接搬进了事务延迟,所以评估网络质量的口径也得跟着变。

平均往返时间没有意义。假设平均一毫秒,但每千个包里有五个跑到了三十毫秒,那么在每秒两千条写事务的情况下,每秒就有十条事务的延迟从三毫秒跳到三十毫秒以上。对应用来说这是毛刺,对超时设置来说这是雪崩的起点——一旦上游开始超时重试,写入量翻倍,毛刺密度跟着翻倍。要看的是 p99 和 p99.9,不是平均值。

测量方式也有讲究。ICMP 的 ping 只反映网络层的可达性和大致往返,而同步复制走的是 TCP 上的 WAL 流,中间还夹着备库的接收和 fsync。更接近真实的做法是在主库上直接观测提交延迟的分布,或者用一个持续的 TCP 往返探针记录分位数,ping 只作为辅助参照。只看 ping 的团队,通常会低估同步复制的真实延迟。

带宽的账相对简单:复制流量约等于 WAL 生成速率乘副本数。WAL 生成速率可以从数据库自带的统计视图里取,不同版本的视图名有差异,以所用版本官方文档为准。批量导入、大表建索引、vacuum full 一类操作会让 WAL 突增,规划带宽时按峰值算而不是按平均算,否则备库会在每次批量操作后持续落后,而同步复制又会把这份落后原样反馈回主库延迟。

丢包对同步复制的伤害比带宽不足大得多。TCP 重传的代价是至少多一个重传超时,而这个超时直接落在事务的关键路径上。链路上千分之一的丢包率在异步复制下几乎看不出来,在同步复制下可能就是可感知的延迟抬升。所以链路选型时,"稳定"比"大"更重要。

取舍可以直接说:如果机房间链路质量不可控,比如跨运营商、跨境、经过多跳,异步复制几乎是唯一合理的选择。以中国香港与内地之间的链路为例,其延迟和抖动量级与同城机房间完全不是一个尺度,在那里做同步复制通常只适合作为读扩展或灾备手段,不适合当作 RPO 为零的方案来承诺。

复制链路上的权限边界与备份独立性

复制账号要专用。创建一个只带 REPLICATION 属性的角色,不要用超级用户去做流复制——复制用户能读到整个集群的 WAL,也就等于能读到所有数据,权限范围远大于它实际需要的。

pg_hba.conf 里要按源地址限制复制连接,只允许备库所在的地址段,不要图省事开成全通。这条规则看起来基础,但在备库扩容、迁移 IP 的时候最容易被临时放开然后忘了收回。

复制链路应该开 TLS,而且要用会校验主机名和证书链的模式,而不是只加密不校验——后者挡不住中间人。相关参数名与取值以所用版本官方文档为准。复制流量里包含全部数据变更,它和客户端连接一样值得加密。

DCS 这一层的权限最常被忽略。etcd 的客户端证书、成员间通信证书、以及基于角色的访问控制要一起配齐。能写 DCS 的人,理论上可以删掉 leader key 制造一次切换,这不是理论威胁,而是运维误操作和凭证泄露的常见路径。Patroni 自身的 REST 接口也要注意,它可能默认不带认证,而它既暴露节点状态也接受切换指令,必须加认证并限制监听地址。

备份与高可用是两件事,不能互相替代。同步复制解决的是"主机挂了数据还在",不解决"误删了一张表"——误删会同步到所有副本,RPO 为零在此刻意味着"错误也被完美复制了"。WAL 归档加定期基础备份是另一套独立的机制,保留周期要独立于集群配置来定,而且恢复演练要真的跑过一次,不能只验证"归档文件在增长"。

如果预算只够做一件事,多数业务应该先做备份再做高可用。没有备份的高可用集群会给团队一种虚假的安全感,而这种安全感在真正需要回滚的那一刻会直接变成事故。

从单库迁到 Patroni 集群:顺序、连接改造与回滚口

迁移顺序比迁移本身重要。先把 DCS 建起来并单独验证:三个成员、独立盘,跑一段时间,看 etcd 的 fsync 尾延迟和 leader 变更次数。这一步没做稳就不要往下走,否则后面所有故障排查都会同时在两个层面上打转。

第二步用 pg_basebackup 从现有主库拉出第一个备库,先以异步方式挂上,观察复制延迟一周以上。这段时间顺便把 WAL 生成速率、峰值出现在什么时候、网络占用多少摸清楚,这些数字后面配置同步模式时都要用。第三步是应用侧的连接改造,必须在上 HA 之前完成——连接方式不改就上 Patroni,切换后应用还连着老地址,等于白做。最后才开同步模式,把 synchronous_standby_names 与 Patroni 的同步配置对齐,开完压一次测,确认延迟涨幅在预算内。

连接改造有三条路,各有代价。虚拟 IP 加 keepalived 对应用改动最小,但虚拟 IP 漂移本身又是一个需要防脑裂的组件,而且跨网段漂移未必被网络环境支持。客户端多主机连接串是另一条路,驱动支持在连接串里写多个主机并指定只连可写节点,具体参数名与可用版本以所用版本官方文档为准,好处是不引入新的中间组件,坏处是每个语言的驱动支持程度不一,需要逐个验证。代理层最灵活,能做健康检查和读写分离,代价是代理本身成了新的故障点,必须成对部署且各自独立故障域。

回滚口要在开始切换之前就准备好:保留一份迁移前的基础备份加归档 WAL,明确一个"到此为止可以整库回退"的时间点。Patroni 集群一旦开始接受写入并且产生时间线分叉,回退就不再是整库恢复的问题,而是数据对齐的问题,工作量差一个数量级。

演练至少做四项:计划内 switchover;杀掉主库进程;用 iptables 或网络策略把主库与 DCS 隔离;下线一个 etcd 成员看集群反应。第四项最容易被跳过,而它恰恰是验证法定票数设计对不对的唯一方法——不下线一次成员,你永远不知道自己那个"三节点 DCS"是不是真的能容忍一台故障。

日常该盯的指标与切换演练怎么做

监控项按重要性排,第一项是同步备库的当前数量。这是 RPO 的即时读数:它从一变成零,意味着你的 RPO 已经不是零了,应该立刻告警,而不是等复制延迟超过阈值再报。很多集群的告警配置里根本没有这一项,于是团队在"看起来一切正常"的状态下已经失去零丢失保护很久了。

第二项是复制状态与落后量,包括备库处于同步还是异步、落后多少字节、落后多少时间。落后字节数持续增长说明备库跟不上了,要进一步查是磁盘慢、有大查询挡着回放,还是 WAL 突增。第三项是 DCS 的 leader 变更次数与 fsync 尾延迟,leader 频繁变更是控制面不稳的直接信号,而且它往往先于数据库故障出现,是很好的早期指标。

第四项是 Patroni 的切换事件次数,非计划切换永远值得复盘,哪怕业务侧完全没感知——没感知可能只是运气好。第五项是 WAL 归档是否落后,归档卡住会让备份链的目标恢复点失效,而且它和复制延迟是两个独立的问题,不能互相推断。第六项是连接数与连接池排队情况,切换后应用重连风暴很容易把最大连接数打满,导致"主库已经恢复了但业务还是报错"。

告警阈值不要拍脑袋定。复制延迟的阈值应该由 RPO 反推:如果你的 RPO 是"最多丢五秒的写入",那就按峰值 TPS 算出五秒对应的 WAL 字节数,用这个数当阈值。这样告警响起来的时候,值班的人知道该按什么级别处理,而不是看着一个抽象的数字猜。

演练节奏上,计划内 switchover 建议每季度至少一次,故障注入演练至少半年一次。演练要记录三个数字:从故障注入到新主可写的秒数、实际丢失的事务数(如果有的话)、应用从报错到恢复的秒数。这三个数字就是你的真实 RTO 和 RPO,它们和设计文档里写的往往不一样,而只有演练能把这个差值暴露出来。

服务器侧的选型也建议标准化,避免每次扩容都重新比一遍。数据库节点按内存和磁盘档位优先,控制面节点按独立故障域和低延迟盘优先,两类节点的规格差异很大,混在一起谈很容易在 DCS 那一层省错地方。一万网络的服务器租用方案覆盖了从轻量控制面节点到大内存数据库节点的不同档位,这类按角色分档的组合可以直接按集群规划一次配齐,部署时也能减少重复比选的工作量,具体价格公开渠道差异较大,以实时报价为准。

PostgreSQL 高可用先定 RPO 再定架构:同步副本数与写入延迟的对价

把前面的账收拢起来,实际是一个固定的决策顺序。

第一步定 RPO。能接受丢多少?答案是"一条都不能丢"的话,同步复制就不是选项之一而是唯一选项,同时必须接受"同步备库全挂时主库停写"这个后果,别再配自动降级。答案是"可以丢几秒"的话,异步复制加上按 RPO 反推出来的延迟告警就够了,不必付同步复制的延迟税。最怕的答案是没想过——没想过的结果通常是默认配了个中间状态,既付了延迟又说不清丢多少。

第二步定副本数与法定票数。同步副本数每加一个,多出来的不是平均值而是那个副本的响应时间;但在法定票数语义下,等待的是最快确认的那一个,而不是全部,这正是 ANY 与 FIRST 两种写法的差别所在(以所用版本官方文档为准)。这也是三节点比两节点更适合做同步复制的原因:两台备库里只要有一台快速确认即可,另一台暂时慢一点不会拖慢主库。而两节点只有一台备库,它慢就等于主库慢,没有缓冲。

第三步定 ttl。它同时决定了最坏 RTO 和最大双写窗口,两头都是硬约束,不存在一个两头都好的取值,只能按业务选边。选完之后要写进文档,并且让所有值班的人知道这个数字意味着什么。

第四步定 fencing 手段,并且明确承认它的边界。物理机有带外电源可用,云上只有网络隔离、存储租约和业务层令牌这几种替代,每一种都有失效场景,而且失效场景往往正是你最需要它的那种场景。设计文档里应该写的是"我们用什么隔离、它什么时候会失效、失效时靠什么兜底",而不是一句"已实现防脑裂"。写下这句话的人,通常既没做过隔离演练,也没算过双写窗口。

第五步才是买机器。走到这一步你会发现,真正决定这套集群能不能用的不是服务器的核数,而是 DCS 那块盘的 fsync 尾延迟、两个机房间链路的 p99、以及第三个故障域到底存不存在。这三样东西里没有一样体现在常规的配置单上。

一万网络深耕 19 年(成立于 2007 年),在服务器租用这块积累下来的经验是:客户来问高可用配置时,先问清楚 RPO 和故障域怎么划分,再谈机器规格,最后落下来的配置往往和最初设想的完全不同——通常是数据库节点的内存和磁盘档位往上走,控制面节点反而降到很低的档位,但要求独立故障域和独立的低延迟盘。这类按角色分档的组合在标准化租用方案里都可以直接配,具体价格公开渠道差异较大,以实时报价为准。

数据来源与核对方式:PostgreSQL 高可用部署口径

同步复制相关参数的名称与语义,包括 synchronous_commit 的各个取值、synchronous_standby_names 的 FIRST 与 ANY 两种写法、full_page_writes 与 pg_rewind 的前置条件、以及各统计视图的字段名,均以所用 PostgreSQL 版本的官方文档为准。本文只描述机制与取舍,不锁定具体取值。

Patroni 的 ttl、loop_wait、retry_timeout、maximum_lag_on_failover 等参数名与相互约束关系,以及 witness 节点的配置项,以 Patroni 官方文档为准。witness 的配置方式在版本间有过变化,请以所用版本官方说明为准。

etcd 的默认心跳与选举超时、以及对 WAL 盘 fsync 延迟的建议阈值,以 etcd 官方文档为准。文中引用的延迟量级属于工程经验口径,不构成承诺,实际部署前应在目标机型与目标链路上实测。

服务器规格与价格信息不作为确定报价,公开渠道差异较大,以实时报价为准;涉及具体配置选型时以咨询结果为准。本文不提供任何攻击、绕过或违规采集相关内容。


上一篇:印尼节点两个档位同价不同配:199 元的迅捷型和旗舰型,该拿内存还是拿硬盘

下一篇:印度云服务器三个页面九种档位怎么读:1 核 512M 卖 320 元这一档到底给谁用