流复制自保护
概述
在同步复制场景下,主库事务提交需要等待同步从库返回写入确认。当从库发生宕机、网络中断或进程异常时,主库将因等待不到确认而使事务长时间挂起,影响业务正常进行,严重时造成连接积压。
流复制自保护(又称哨兵)用于解决该问题:主库上预先建立虚拟通道,当同步从库故障时,哨兵自动接替故障从库的角色、隔离故障的流复制通道,主库事务继续执行,不受从库故障影响。故障从库修复并重新接入后,哨兵退出接替,集群恢复原有的同步复制状态。
哨兵机制遵循以下行为原则:
- 从库正常时,哨兵不介入同步流程,仅在虚拟通道上维护 LSN 状态;
- 从库仅存在复制延迟而链路正常时,不视为故障,哨兵不介入;
- 介入遵循"刚好"原则:N 个同步从库中有 M 个故障时,哨兵仅以 M 个从库的角色介入;
- 从库故障时自动介入,无需人工干预,介入无延迟。
注意:哨兵机制用于隔离故障通道、释放主库等待会话,不承担主从切换、从库修复等职责,与业务连续性高可用方案互补;从库故障被隔离后仍应尽快恢复从库。
哨兵的启用
启用哨兵前,须已完成同步复制配置(参见复制概述 · 同步复制及物理复制示例),即主库已设置 synchronous_standby_names,且同步从库处于正常流复制状态。
enable_synchronous_guardian(布尔型)
是否启用哨兵机制。默认值 off。该参数通过 pg_ctl reload 重新加载即可生效,无需重启数据库。
_max_sentinel_workers(整数型)
哨兵工作进程的最大数量,取值范围 0~5,默认值 5;设置为 0 时完全禁用哨兵机制。该参数为隐含参数,通过 SHOW 命令查看,修改后需重启数据库生效。
说明:系统中另有 sentinel_mode 等哨兵相关内部参数用于高级调优,保持默认值即可满足常规使用需求。
启用的步骤如下。
1. 确认同步复制状态正常
halo0root=# show synchronous_standby_names;
synchronous_standby_names
---------------------------
node1
(1 行记录)
halo0root=# select application_name, state, sync_state from pg_stat_replication;
application_name | state | sync_state
------------------+-----------+------------
node2 | streaming | async
node1 | streaming | sync
(2 行记录)
2. 修改主库参数配置文件,启用哨兵
echo "enable_synchronous_guardian = on" >> $PGDATA/postgresql.conf
pg_ctl reload
3. 验证参数已生效
halo0root=# show enable_synchronous_guardian;
enable_synchronous_guardian
-----------------------------
on
(1 行记录)
4. 确认哨兵进程与虚拟通道已就位
ps -ef | grep -E "sentinel|walsender halo ::1"
halo 1930 1880 0 11:47 ? 00:00:00 postgres: halo-cluster: sentinel launcher
halo 3183 1880 0 12:22 ? 00:00:00 postgres: halo-cluster: sentinel w000
halo 3184 1880 0 12:22 ? 00:00:00 postgres: halo-cluster: walsender halo ::1(33974) START_REPLICATION
说明:哨兵守护进程(sentinel launcher)随数据库启动常驻,未启用哨兵时亦存在;判断哨兵是否启用,以参数取值及是否出现哨兵工作进程(sentinel w000 等)与虚拟通道(walsender halo ::1)为准。虚拟通道不会出现在 pg_stat_replication 视图中。
哨兵的验证
以下在集群环境中演示故障隔离效果:node3 为同步主库,node1 为同步从库,node2 为异步从库。
1. 创建测试表并确认正常写入
halo0root=# create table sentinel_demo(a int, ts timestamptz default clock_timestamp());
CREATE TABLE
halo0root=# insert into sentinel_demo(a) values (100);
INSERT 0 1
2. 停止同步从库 node1,模拟从库故障
pg_ctl stop -D $PGDATA -m fast
等待服务器进程关闭 .... 完成
服务器进程已经关闭
3. 主库执行写入,事务立即完成
halo0root=# insert into sentinel_demo(a) values (200);
INSERT 0 1
从库故障期间,主库写入立即返回,无等待;写入的数据在主库立即可见:
halo0root=# select a from sentinel_demo order by a;
a
-----
100
200
(2 行记录)
4. 恢复从库,验证数据追平
pg_ctl start -D $PGDATA
从库恢复后重新接入流复制,并追平故障期间的 WAL。主库查看当前 WAL 位置:
halo0root=# select pg_current_wal_insert_lsn();
pg_current_wal_insert_lsn
---------------------------
0/2B038A58
(1 行记录)
从库查看回放位置,与主库一致:
halo0root=# select pg_last_wal_replay_lsn();
pg_last_wal_replay_lsn
------------------------
0/2B038A58
(1 行记录)
从库追平后,哨兵介入期间主库提交的事务在从库同样可见:
halo0root=# select a from sentinel_demo order by a;
a
-----
100
200
(2 行记录)
5. 关闭哨兵
将 postgresql.conf 中的 enable_synchronous_guardian 改回 off(或删除该配置行),执行 pg_ctl reload 即可;哨兵工作进程与虚拟通道随即退出,哨兵守护进程保持常驻。
验证完毕后,可删除测试表:
halo0root=# drop table sentinel_demo;
DROP TABLE
使用注意事项
启用与未启用哨兵时,同步从库故障期间主库写入行为的对照如下:
| 行为 | 未启用哨兵 | 启用哨兵 |
|---|---|---|
| 从库故障期间主库写入 | 事务持续等待,直至从库恢复 | 立即完成 |
| 写入数据在主库的可见性 | 从库恢复、事务完成后可见 | 立即可见 |
| 从库恢复后 | 追赶期间积压的 WAL,事务陆续完成 | 正常追平故障期间的 WAL |
| 运维要求 | 恢复从库前主库写入持续受阻 | 恢复从库前集群处于同步降级状态,仍应尽快恢复 |
注意:同步复制的等待发生在事务已写入主库本地 WAL 之后。未启用哨兵时,等待中的事务即使客户端取消或断开连接也不会回滚,待从库恢复后确认自动完成;语句超时(statement_timeout)不能打断该等待。启用哨兵可避免上述等待。
注意:从库故障、哨兵介入期间,集群的同步保护处于降级状态(主库写入不再等待真实从库确认),应尽快恢复故障从库;若介入期间主库发生不可恢复故障,介入期间提交的事务存在丢失风险。
注意:在业务连续性(Halo Shield)管理的集群中演练从库故障时,需先暂停集群管理(如 clumgrctl pause),避免故障从库被自动拉起,演练完成后恢复(clumgrctl resume)。