故障转移:副本提升
当当前主实例失败时,将副本提升为新的主实例。pgcli 提供 3 步手动故障转移工作流——每个步骤在其各自的主机上运行,不自动检测同主机与跨主机拓扑。
概述
故障转移后(提升 ro1 → 新主实例):
3 步故障转移
每个步骤都是独立的命令。按顺序在各自的主机上运行它们。
步骤 1:pg replica promote <name>
在被提升为主实例的副本主机上运行。
发生了什么:
- 验证实例是副本(
ReplicaOf已设置)且容器正在运行 - 调用
pg_promote()(PostgreSQL 12+ 原生提升——无需容器重启) - 等待恢复结束(通常亚秒级)
- 通过
ALTER SYSTEM RESET从postgresql.auto.conf清理primary_conninfo - 更新配置:清除
ReplicaOf和PrimaryDSN,启用PITR - 自动初始化 PITR:
- pgBackRest stanza 创建
archive_mode/archive_command配置- PostgreSQL 重启以应用 postmaster 级别参数
- 打印下一步指令
幂等: 如果副本已被提升(例如通过手动 pg_ctl promote),命令会跳到配置更新。
步骤 2:pg replica drop <name> -i <old-primary>
在旧主实例主机上运行以清理复制槽。此步骤仅在特定场景中需要。
这会在旧主实例上删除物理复制槽 pgcli_r_ro1。没有清理,槽会无限期保留 WAL,直到主实例磁盘空间耗尽。
何时运行:
| 场景 | 操作 | 原因 |
|---|---|---|
| 旧主实例永久丢失 | 跳过 | 槽随服务器一起消失 |
| 计划将旧主实例降级为副本 | 跳过 | repoint 销毁数据目录(包括 pg_replslot/),所有槽被隐式移除 |
| 旧主实例已恢复,继续作为独立主实例运行 | 必须运行 | 槽无限期保留 WAL;没有清理磁盘最终会填满 |
| 旧主实例已恢复但将被关闭 | 可选 | 如果实例不再运行,跳过无害 |
保持旧主实例不变? 如果你想保留旧主实例及其原始数据(例如用于取证分析或作为只读归档),你可以简单地不理会它——不要在其上运行
drop或repoint。旧主实例继续作为具有陈旧数据的独立实例运行。只是要注意被提升副本的复制槽仍然存在并会累积 WAL;你可能想要仅删除那个特定的槽(pg replica drop ro1 -i pg01)同时保持其他一切不变。
步骤 3:pg replica repoint <name> --primary-dsn <dsn> --primary-name <name>
在每个剩余副本主机上运行以将其重新指向新主实例。
发生了什么:
- 通过 DSN 查询新主实例的扩展(
pg_extension目录) - 如果存在非内置扩展(例如 pg_cron、timescaledb),构建本地
-ext镜像并匹配包 - 停止旧副本容器并销毁其数据目录
- 通过 DSN 在新主实例上创建复制槽
- 更新配置:
ReplicaOf、PrimaryDSN、ImageTag、Extensions,禁用PITR - 通过
pg_basebackup -R从新主实例重新初始化 - 以备用模式启动副本容器
为什么销毁 + 重建而不是 ALTER SYSTEM SET?
提升后,新主实例进入新时间线。旧时间线上的其他副本不能简单地更改 primary_conninfo——PostgreSQL 会拒绝连接:
唯一安全的方法是从新主实例进行完整的 pg_basebackup。
获取主实例 DSN
从被提升副本主机上的 pg status 获取新主实例的连接字符串:
将 127.0.0.1 替换为从副本主机可达的新主实例主机 IP(例如 10.241.21.97)。
降级旧主实例
当旧主实例恢复时,你可以使用相同的 repoint 命令将其作为新主实例的副本重新加入:
即使 pg01 曾是主实例(未设置 ReplicaOf)这也有效。该命令:
- 停止 pg01 并销毁其数据(包括旧的 PITR stanza)
- 在新主实例上为 pg01 创建复制槽
- 设置
ReplicaOf = "ro1",PITR.Enabled = false - 通过
pg_basebackup从新主实例重新初始化
重新指向后,pg01 作为只读副本从新主实例流式传输 WAL——没有 WAL 归档,没有备份。
扩展同步
当副本被重新指向新主实例时,pgcli 会自动同步扩展:
- 查询 — 通过 DSN 连接到新主实例并查询
pg_extension获取已安装的扩展 - 过滤 — 识别非内置扩展(需要外部包的扩展,例如 pg_cron、pgmq、timescaledb)
- 构建 — 如果存在非内置扩展,构建本地
-ext镜像:- 如果本地
-ext镜像已存在,在其上安装缺失的包(重用 Pigsty 仓库——快速) - 如果没有
-ext镜像,从基础镜像构建并设置 Pigsty 仓库 apt-get install是幂等的——安装已存在的包是无操作
- 如果本地
- 应用 — 副本启动时,
ApplyExtensions将shared_preload_libraries写入postgresql.conf - 跳过 CREATE EXTENSION — 副本是只读的;扩展通过
pg_basebackup+ WAL 流式传输从主实例复制
这确保副本容器具有 postgresql.auto.conf 中引用的所需共享库(例如 pg_cron)。
同主机 vs 跨主机
pgcli 不自动检测拓扑。你选择在每个命令运行的位置:
| 场景 | 步骤 1 | 步骤 2 | 步骤 3 |
|---|---|---|---|
| 全部在同一主机 | pg replica promote ro1 |
pg replica drop ro1 -i pg01 |
pg replica repoint ro2 --primary-dsn "postgres://...@127.0.0.1:..." --primary-name ro1 |
| 主实例 + 副本分散在多个主机 | 在副本主机 | 在旧主实例主机 | 在每个副本主机上使用新主实例的网络 IP |
| 混合 | 在各自的主机 | 在旧主实例主机 | 在每个副本主机 |
--primary-dsn 必须使用从 repoint 运行所在主机可达的 IP/主机名。
完整示例
跨主机示例
级联复制
副本本身可以作为下游副本的主实例,形成级联链。这减少主实例的负载并启用分层拓扑。
工作原理
-
创建副本的副本:使用副本作为
-i目标 -
WAL 传播:
- ra2 从 ra3 流式传输 WAL
- ra2_ro1 从 ra2 流式传输 WAL
- 数据流:ra3 → ra2 → ra2_ro1
-
复制槽:每个链接维护自己的槽
- ra3 有槽
pgcli_r_ra2 - ra2 有槽
pgcli_r_ra2_ro1
- ra3 有槽
优势
- 减少主实例负载:只有直接副本连接到主实例
- 地理分布:主实例 → 区域副本 → 本地副本
- 网络效率:本地副本可以共享区域上游
限制
- 增加延迟:每一跳增加复制延迟
- 级联故障:如果 ra2 失败,ra2_ro1 失去其上游
- 提升复杂性:提升 ra2_ro1 需要将其重新指向新主实例
验证级联
级联故障转移
如果 ra2(中间节点)失败:
如果 ra3(主实例)失败且 ra2 被提升:
注释
- pg_promote() — PostgreSQL 12+ 原生函数,无需容器重启。实例就地退出恢复并立即变为可读写
- 时间线分歧 — 提升后,新主实例在新时间线上。其他副本不能用
ALTER SYSTEM SET primary_conninfo重新指向——它们必须通过pg_basebackup重建 - 被提升副本上的 PITR — 提升后,运行
pg start创建 pgBackRest stanza 并启用 WAL 归档。被提升的副本没有先前的备份历史 - 复制槽 — 旧主实例为被提升副本的槽在提升后变得陈旧。
pg replica drop清理它。如果旧主实例被降级为副本,repoint销毁旧数据且陈旧的槽不再被引用 - 扩展 — 副本容器通过
postgresql.auto.conf从主实例继承shared_preload_libraries。repoint 命令确保本地镜像在重建副本之前具有所需的扩展包 - 跳过 CREATE EXTENSION — 副本是只读的;
pg_basebackup从主实例复制扩展元数据,因此不需要CREATE EXTENSION(且会失败 “cannot execute CREATE EXTENSION in a read-only transaction”)