ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

PostgreSQL主从流复制+pgpool高可用与pg_rman备份恢复实战

PostgreSQL主从流复制+pgpool高可用与pg_rman备份恢复实战

简介:《Postgres主从流复制+pgpool高可用方案》是一份面向PostgreSQL数据库管理员、运维工程师及后端开发者的技术文档,聚焦主备流复制与pgpool-II高可用架构的落地设计,适合需要理解WAL日志同步、读写分离和故障切换的实践场景。压缩包共1个docx文件,约773KB,内容以方案说明和配置参考为主,涵盖同步与异步复制模式、pgpool-II连接池、复制、负载均衡及连接限制、pg_rman备份,以及软件环境、用户权限、主备节点和pgpool的配置要点。文档目录从方案介绍、实践环境、客户环境准备到资源调整逐层展开,并包含启动验证与故障模拟测试思路,便于读者梳理WAL传输与应用链路、理解主库故障时的切换策略,也可作为搭建PostgreSQL高可用环境的对照材料。已有471人学习,适合希望系统掌握流复制与pgpool组合方案的中级读者。

1. 单主库撑不住的那天,我把 Postgres 主从流复制和 pgpool 高可用重新排了一遍

某公司的业务库跑到第六个月,报表查询开始把连接池占满,白天一条大 SQL 就能让下单接口超时。第一次处理很粗暴,直接调大max_connections,结果内存和上下文切换一起上来,问题反而更频繁。后来我把思路拆成两半:数据层用 Postgres 主从流复制,让 WAL 实时落到第二台机器,备库接只读查询;连接层用 pgpool-II,把连接收拢、把读请求摊开,主库出问题时还能把流量切走。这份《Postgres主从流复制+pgpool高可用方案》就是按这条线写的实施手册,覆盖 RHEL/CentOS 双节点搭建、pgpool-II 集群配置、pg_rman 离线备份三块,给了 10.3 + pgpool 3.6.14 和 12.2 + pgpool 4.1.1 两套版本示例。适合需要自己动手搭一套“能切、能读扩展、能回滚”的 PostgreSQL 高可用环境,又不想引入重型中间件的运维和 DBA。它给到脚本级别,不是产品化封装,环境适配得自己过一遍。

2. 流复制落地:从 wal_level 到 pg_basebackup 的完整链路

2.1 先定同步还是异步:synchronous_standby_names 是唯一开关

PostgreSQL 的流复制默认是异步的:主库提交事务时不等备库确认,WAL 由 walsender 推给备库的 walreceiver,延迟通常在 1 秒以内。好处是写入性能不受备库影响,代价是主库突然崩溃时可能丢最后几个事务。切换成同步复制的条件只有两个:synchronous_standby_names设成非空值,synchronous_commit不能是off。少一个都还是异步。

synchronous_commit的几个档位值得记清楚,它决定了主库“等多久”:

取值主库何时返回提交成功典型场景
off本地 WAL 还没落盘就返回可承受丢失,追求吞吐
local本地 WAL 落盘单机一致性
remote_write备库写入操作系统缓存折中方案,延迟低
on备库 WAL 落盘(默认同步语义)常规同步复制
remote_apply备库重放完成,读已可见强一致读,代价最大

我一般这么选:报表、日志、埋点这类库走默认异步,反正丢了也能补;账务、订单状态机这类库用remote_write或on,别一上来就remote_apply,它要求备库把 WAL 应用完才返回,主库写入延迟直接被备库的重放速度绑住,压力大一点就雪崩。另外synchronous_standby_names从 9.6 起支持ANY 1 (standby1, standby2)和FIRST 2 (...)语法,两个备库的场景用ANY更省事,谁先应答算谁。

主库侧要改的参数集中在postgresql.conf:

# postgresql.conf --- 主库 192.168.10.11 listen_addresses = '*' port = 5432 wal_level = replica # 支撑流复制的最低档,逻辑复制才需要 logical max_wal_senders = 10 # 每个备库和每次 pg_basebackup 各占一个,别设成 3 wal_keep_segments = 512 # 12 及以前;13 起换成 wal_keep_size = '8GB' hot_standby = on # 备库允许只读查询,不开备库连不上 wal_log_hints = on # 给 pg_rewind 留后路,回切时能省一次全量重做 synchronous_commit = on synchronous_standby_names = 'standby1' # 留空即为异步 archive_mode = on archive_command = 'test ! -f /data/pg_archive/%f && cp %p /data/pg_archive/%f'

max_wal_senders是最容易被低估的参数。备库一个、pg_basebackup拉基线时一个、再加上监控和手工连上去看状态的会话,10 是比较稳妥的起点,设成 3 很可能在半夜做备份时把主库的复制连接挤掉。

2.2 主库侧两件事:复制用户和 pg_hba 里的 replication 关键字

复制用户不需要超级权限,REPLICATION属性就够了。密码认证方式取决于pg_hba.conf,注意那一行的第二列必须写replication,不是数据库名,写错了日志里就是一句no pg_hba.conf entry for replication在刷屏。

-- 主库执行 CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'Repl_2024#';
# pg_hba.conf --- 主库,加在文件末尾 # TYPE DATABASE USER ADDRESS METHOD host replication repl 192.168.10.0/24 md5 host all all 192.168.10.0/24 md5

第一行给复制链路用,第二行给应用和 pgpool 用,两条职责不同,别合并。改完pg_hba.conf执行pg_ctl reload即可生效,不用重启。用select * from pg_hba_file_rules;(10 及以上)可以直接看到系统解析出来的规则和错误行号,比拍脑袋排查快得多。

2.3 备库侧:pg_basebackup 拉基线,两代版本差一个文件

基线用pg_basebackup从主库拉,不要在备库上手工 initdb 再拷数据目录,那样很容易漏掉表空间和 WAL 配置。命令里几个参数是硬要求:-Xs用流式方式把基线过程中的 WAL 一起带走,避免主库在拷贝期间切换 WAL 段导致基线不可用;-R在 12 及以后会自动生成standby.signal并把primary_conninfo写进配置文件。

export PGHOME=/usr/local/pgsql export PGDATA=/data/pgdata # 备库 192.168.10.12 上执行,目录必须为空或不存在 $PGHOME/bin/pg_basebackup \ -h 192.168.10.11 -p 5432 -U repl \ -D $PGDATA -Fp -Xs -P -R -c fast

-Fp表示明文格式输出,方便直接看目录结构;-c fast会强制主库立刻做一次 checkpoint,省掉等待检查点的时间;-P只负责看进度条。这里有个隐蔽的坑:-R写进去的primary_conninfo只带 host、port、user,application_name是空的。而同步复制靠synchronous_standby_names里的名字去匹配备库上报的application_name,名字对不上,同步复制永远进不了sync状态,主库的写操作会卡在等待上。所以拉完基线一定要手工补一行:

# 备库 postgresql.conf(12 及以后) primary_conninfo = 'host=192.168.10.11 port=5432 user=repl password=Repl_2024# application_name=standby1' recovery_target_timeline = 'latest' hot_standby = on

12 和 10 的核心差异就在这里。10.x 及以前,备库身份靠$PGDATA/recovery.conf声明,里面写standby_mode = 'on'、primary_conninfo、recovery_target_timeline,备库启动后这个文件会被自动改名为recovery.done。12 起recovery.conf被彻底移除,改成一个空的standby.signal文件加配置项。升级过实例的环境里经常能看到两个文件同时躺着,12 的实例上只要存在recovery.conf,启动直接失败,报的还是“unrecognized configuration parameter”,第一次遇到很容易懵。

2.4 验证:别只看进程在,要看 LSN 在动

启动备库后用pg_ctl start,然后主库上看复制状态:

-- 主库 SELECT pid, usename, application_name, client_addr, state, sync_state, sent_lsn, replay_lsn, pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes FROM pg_stat_replication;

期望结果:state是streaming,sync_state在异步模式下是async、同步模式下是sync,lag_bytes稳定在几百 KB 到几 MB 之间波动。lag_bytes持续增长说明备库重放跟不上,先看备库 CPU 和磁盘 IO,再看是不是有长事务把replay_lsn卡住了。10 之前这个函数叫pg_xlog_lsn_diff,目录也叫pg_xlog,从 10 开始统一改成pg_wal,网上的老脚本直接抄会报函数不存在。

备库侧两条查询能确认它到底在不在恢复状态:

-- 备库 SELECT pg_is_in_recovery(); -- 期望 t SELECT status, sender_host, sender_port, latest_end_lsn FROM pg_stat_wal_receiver; -- 期望 streaming

进程层面ps -ef | grep -E 'walreceiver|startup'能看到 walreceiver 和 startup 两个辅助进程,前者收 WAL,后者重放。只看到 walreceiver 没有 startup,说明重放进程卡在某个锁上,查pg_stat_activity里backend_type = 'startup'的那一行。备库默认只读,除了临时表和序列的nextval,任何写操作都会报cannot execute ... in a read-only transaction,这一点在应用连错库时反而是个保护。

3. pgpool-II 接入:连接池、读写分离与 watchdog 三件事

3.1 后端节点声明:3.x 和 4.x 是两套写法

pgpool-II 站在客户端和 PostgreSQL 之间,默认监听 9999 端口,客户端连它就像连一个普通 PG。第一件事是在pgpool.conf里声明后端节点。3.6 用master_slave_mode = on加master_slave_sub_mode = 'stream'表达“这是流复制集群”,4.0 起统一收敛成一个backend_clustering_mode。两套写法不能混用,抄错版本会得到一句语焉不详的启动失败。

# pgpool.conf --- 4.x 写法 listen_addresses = '*' port = 9999 backend_clustering_mode = 'streaming_replication' # 3.6 等价写法:master_slave_mode = on / master_slave_sub_mode = 'stream' backend_hostname0 = '192.168.10.11' backend_port0 = 5432 backend_weight0 = 0 # 主库不参与读负载均衡 backend_data_directory0 = '/data/pgdata' backend_flag0 = 'ALWAYS_PRIMARY' # 4.x;3.x 用 ALLOW_TO_FAILOVER backend_hostname1 = '192.168.10.12' backend_port1 = 5432 backend_weight1 = 1 backend_data_directory1 = '/data/pgdata' backend_flag1 = 'ALLOW_TO_FAILOVER'

backend_weight是读请求的权重,主库设 0 表示不接只读流量,这是读写分离里最省心的配置——主库专心写,备库专心读,不会出现读请求打到主库又把连接占满的情况。多备库时权重按机器配置分,配置差一半的机器权重也给一半。backend_flag决定 failover 时这个节点会不会被摘掉,ALWAYS_PRIMARY用在主库上,防止健康检查抖动把主库判死。

3.2 读写分离:load_balance_mode 背后的判定逻辑

load_balance_mode = on之后,pgpool 会把符合条件的 SELECT 分发到权重非零的备库。所谓“符合条件”有明确边界:不在显式事务块里的、只读的、不是SELECT ... FOR UPDATE的、不调用nextval()这类写函数的语句。函数调用这一块靠两个列表控制,4.x 是read_only_function_list和write_function_list,写进write_function_list的函数,包含它的整条语句都会被强制送到主库。

业务上最常踩的是“刚写完立刻读”。主库 INSERT 完,同一个用户在页面上刷新,SELECT 被路由到备库,而备库的replay_lsn还差几十毫秒,用户看到的是旧数据。这不是 pgpool 的 bug,是读写分离的固有代价。处理方式两种:一是关键路径的查询在 SQL 里加/*NO LOAD BALANCE*/注释强制走主库,二是这类业务在一个事务里先写后读,事务内的读天然走主库。别指望把health_check_period调小能解决,那只会增加无谓的探测压力。

3.3 连接池与认证:先算 num_init_children × max_pool

连接池是 pgpool 最直接的价值。num_init_children是预派的子进程数,等于客户端并发连接的上限,超出的连接会排队而不是直接被拒;max_pool是每个子进程对每个后端最多缓存几条连接。两者相乘就是 pgpool 对单个后端可能建立的最大连接数:

# 连接池 num_init_children = 100 max_pool = 4 child_life_time = 300 connection_life_time = 0 client_idle_limit = 0 # 健康检查 health_check_period = 10 health_check_timeout = 20 health_check_user = 'pool_check' health_check_password = 'Check_2024#' health_check_database = 'postgres' health_check_max_retries = 3 health_check_retry_delay = 1

默认值 32 × 4 = 128,如果后端max_connections只有 100,那么 pgpool 自己就能把后端打满,日志里是一串FATAL: sorry, too many clients already。反算方式:后端max_connections先扣掉 20% 留给超级用户、备份和监控,剩下的除以max_pool,才是num_init_children的安全上限。health_check_timeout要小于health_check_period,否则健康检查会自己叠自己;health_check_database建议单独建一个空库或者用postgres,别指到业务库上,探测会话会出现在业务的连接统计里。

认证这条链路有两段,很多人只配了一半:

# 生成 pool_passwd 条目,-m 表示 MD5 方式,会交互式提示输入密码 /usr/local/pgpool/bin/pg_md5 -m -f /usr/local/pgpool/etc/pool_passwd postgres # pcp.conf 是管理命令的账号,密码存 MD5 密文 /usr/local/pgpool/bin/pg_md5 pcpuser echo "pcpuser:<上一步输出的密文>" >> /usr/local/pgpool/etc/pcp.conf

客户端先过pool_hba.conf,pgpool 用pool_passwd里的密码做认证,认证通过后再用它自己配置的后端账号连 PostgreSQL。所以pool_hba.conf和后端的pg_hba.conf是两份文件、两个阶段,改一份另一份不动,就会出现“直连能进、走 pgpool 进不去”的经典现象。

3.4 watchdog 与虚拟 IP:pgpool 自己不能成为单点

单台 pgpool 挂了,整套高可用就是个摆设。watchdog 的作用是让两台 pgpool 互相心跳,主机持有虚拟 IP 对外服务,主机失联后备机接管 IP。这里所有涉及ip addr、arping的命令都要 sudo 权限,而 pgpool 通常以普通用户运行,所以必须提前配免密:

# pgpool.conf --- watchdog 段 use_watchdog = on wd_hostname = '192.168.10.21' wd_port = 9000 delegate_IP = '192.168.10.100' if_up_cmd = '/usr/bin/sudo /sbin/ip addr add $_IP_$/24 dev eth0 label eth0:0' if_down_cmd = '/usr/bin/sudo /sbin/ip addr del $_IP_$/24 dev eth0' arping_cmd = '/usr/bin/sudo /usr/sbin/arping -U $_IP_$ -w 1 -I eth0' wd_lifecheck_method = 'heartbeat' wd_heartbeat_port = 9694 wd_heartbeat_keepalive = 2 wd_heartbeat_deadtime = 30 wd_interval = 10 wd_life_point = 3 # 节点故障时的动作 failover_command = '/usr/local/pgpool/bin/failover.sh %d %H %P %R' follow_master_command = '/usr/local/pgpool/bin/follow_master.sh %d %H %D'

$_IP_$是 pgpool 的占位符,会被替换成delegate_IP。failover_command里的%d是失效节点 ID,%H是失效节点主机名,%P是旧主节点 ID,%R是新主节点 ID;follow_master_command在主备切换完成后对每个还活着的备节点执行一次,常用来让原主库以备库身份重新加入。wd_heartbeat_deadtime设 30 秒意味着网络抖一下超过 30 秒就可能触发切换,跨机房部署时这个值要往上调,否则会出现两边都认为自己该持有 IP 的脑裂。

4. 五个把我按在地上摩擦的坑:从 hba 报错到虚拟 IP 不漂

4.1 no pg_hba.conf entry for host:两套 hba 各管一段

现象:客户端连 9999 端口报这条,但用 psql 直连 5432 完全正常,于是开始怀疑密码错了,改了三次还是这条。原因在于认证发生在 pgpool 层,客户端连的是 pgpool,它查的是pool_hba.conf,跟后端那份pg_hba.conf没关系。解决要三件事一起做:enable_pool_hba = on、pool_hba.conf里补上客户端网段的那一行、pool_passwd里存在对应用户的条目。三者缺一,报错长得都一样。

4.2 all backend nodes are down:健康检查账号是个隐形依赖

现象:pgpool 进程启动正常,ps能看到一堆子进程,但任何业务连接都被拒,日志刷all backend nodes are down,用 psql 直连后端数据库却好得很。原因是 pgpool 判断节点死活的依据是健康检查会话能不能建立,而health_check_user建的账号不存在、密码写错、或者health_check_database指定的库不存在,健康检查就全部失败,节点被标记成 down。排查用pcp_node_info -h 虚拟IP -p 9898 -U pcpuser 0看 status 和 role,解决就是建一个独立的只读监控账号,确认它能连上health_check_database。

4.3 虚拟 IP 不漂移:差一个 sudo 权限,切换就成摆设

现象:手工停掉主机 pgpool,备机进程一切正常,pcp_watchdog_info也显示备机是 leader,但业务连接还是打不通,IP 还挂在已经死掉的那台机器上。pgpool 用的普通用户没有改网卡的权限,if_up_cmd执行失败,失败信息只在 pgpool 日志里有一行,很容易被忽略。解决是在visudo里给这个用户开放具体命令的NOPASSWD,命令要写全路径,/sbin/ip和/usr/sbin/arping一个都不能省,路径写错一样静默失败。改完在主机上手工sudo /sbin/ip addr add ...验证一次,能过再重启 pgpool。

4.4 时间线冲突:主库修好重新加入,备库起不来

现象:failover 之后原主库修好重加,日志报requested timeline 2 is not a child of this server's history,或者干脆一直停在恢复中。原因是切换后新主库 promote 出了新的时间线,原主库还停在旧时间线上,数据分叉了,直接把新主库的数据目录拷回来也解决不了。解决方式是pg_rewind --target-pgdata=$PGDATA --source-server="host=新主库 port=5432 user=repl",把原主库拉回新时间线,前提是原主库开了wal_log_hints或者建库时开了 data checksums。没开的话只能pg_basebackup重拉一遍,几十分钟的停机就跑不掉。

4.5 连接数被自己的连接池打满

现象:业务高峰后端突然冒出sorry, too many clients already,但应用侧的连接数看起来并没有爆炸。pgpool 的每个子进程对每个后端最多缓存max_pool条连接,num_init_children × max_pool才是真实上限,两个参数任何一个调过,都得回头核对后端的max_connections。更隐蔽的一种是child_life_time和connection_life_time都设成 0,空闲连接永不回收,池子只会涨不会落。解决是按 4.3 节的反算公式把三个参数配成一组,改完之后用show pool_pools看实际连接分布。

5. pg_rman 离线备份:先有归档,再谈任意时刻恢复

5.1 归档模式是前提:archive_command 写错等于没备份

流复制只保证备库跟主库同步,它救不了误删表、误改数据这类逻辑错误——备库会忠实地把DELETE也重放一遍。要恢复到某个时间点,必须有归档 WAL 和基础备份。wal_level = replica时归档就能用,但archive_mode必须显式打开:

# postgresql.conf --- 主库 archive_mode = on archive_command = 'test ! -f /data/pg_archive/%f && cp %p /data/pg_archive/%f'

%p是 WAL 的完整路径,%f是文件名,test ! -f防止重试时覆盖已有归档。这条命令的返回值决定 PostgreSQL 会不会重复投递同一个 WAL:返回非 0 就一直重试,归档目录写满时主库会因为无法归档而卡住pg_wal目录膨胀。检查归档是否健康只看一个视图:

SELECT archived_count, failed_count, last_archived_wal, last_failed_wal FROM pg_stat_archiver;

failed_count在涨就说明archive_command有问题,先看last_failed_wal那个文件在不在、目录权限对不对。手工催一次归档用select pg_switch_wal();,10 之前叫pg_switch_xlog(),脚本里混用直接报错。

5.2 pg_rman 初始化与备份:validate 这一步不能省

pg_rman 的定位是把基础备份、归档日志、服务器日志打包成一套可管理的时间点集合,命令不多但每一步都有约束。初始化要在能读到$PGDATA的机器上做,-B是备份目录,-D是数据目录:

export PGHOME=/usr/local/pgsql export PGDATA=/data/pgdata export BACKUP_PATH=/data/pg_rman # 初始化备份目录 $PGHOME/bin/pg_rman init -B $BACKUP_PATH -D $PGDATA # 全量备份,带上服务器日志方便事后排查 $PGHOME/bin/pg_rman backup --backup-mode=full --with-serverlog \ -B $BACKUP_PATH -D $PGDATA # 关键一步:校验,不校验的备份在 show 里状态是不可用的 $PGHOME/bin/pg_rman validate -B $BACKUP_PATH # 后续按天做增量 $PGHOME/bin/pg_rman backup --backup-mode=incremental --with-serverlog \ -B $BACKUP_PATH -D $PGDATA $PGHOME/bin/pg_rman validate -B $BACKUP_PATH # 查看备份集合 $PGHOME/bin/pg_rman show -B $BACKUP_PATH

--backup-mode=full是全量,incremental依赖上一次备份加上中间所有归档日志,归档链断了增量就不可用,所以pg_stat_archiver的监控要一直挂着。validate的作用是把备份标记为可用,跳过这一步pg_rman show里状态会显示异常,恢复时直接拒绝。版本上 pg_rman 和 PostgreSQL 大版本要匹配,源码编译时用--with-pgsql=$PGHOME指到对应版本的安装目录,否则连库时会因为 libpq 版本对不上而失败。

5.3 恢复验证:没恢复到别的目录跑过,就不算有备份

恢复演练要放在独立目录,别在原数据目录上做,一次失误就没了。restore前目标目录必须不存在或者是空目录,pg_rman 会检查这一点:

# 恢复到独立目录,指定时间点 $PGHOME/bin/pg_rman restore -B $BACKUP_PATH -D /data/pgdata_restore \ --recovery-target-time='2024-06-18 09:30:00' # 改端口起一个验证实例,别和线上抢 5432 $PGHOME/bin/pg_ctl -D /data/pgdata_restore -o "-p 5433" start psql -p 5433 -c "select count(*) from orders;" $PGHOME/bin/pg_ctl -D /data/pgdata_restore stop

恢复完第一件事不是启动服务,是count一张核心表看行数对不对,再看一眼pg_controldata里的时间点。这一步花十分钟,能在真出事的时候省掉几小时的手忙脚乱。

5.4 备份脚本的两条血泪经验

第一个坑是 cron 环境。手工跑得好好的脚本,进了 crontab 就报pg_rman: command not found或者连不上库,原因是 cron 不加载用户的 profile,PGDATA、PATH、PGPORT全是空的。脚本第一行之后必须把这些变量显式 export 一遍,用绝对路径调二进制。

#!/bin/bash export PGHOME=/usr/local/pgsql export PGDATA=/data/pgdata export PGPORT=5432 export BACKUP_PATH=/data/pg_rman export PATH=$PGHOME/bin:$PATH LOG=/var/log/pg_rman_full.log echo "=== start $(date) ===" >> $LOG $PGHOME/bin/pg_rman backup --backup-mode=full --with-serverlog \ -B $BACKUP_PATH -D $PGDATA >> $LOG 2>&1 if [ $? -eq 0 ]; then $PGHOME/bin/pg_rman validate -B $BACKUP_PATH >> $LOG 2>&1 else echo "backup failed, skip validate" >> $LOG fi # 保留最近 7 天,更早的连同归档日志一起清掉 $PGHOME/bin/pg_rman delete 7 -B $BACKUP_PATH >> $LOG 2>&1 echo "=== end $(date) ===" >> $LOG

第二个坑是保留策略。pg_rman delete 7删除的是 7 天之前的备份,同时会连带清理不再被任何备份引用的归档日志;手工rm备份目录会让 pg_rman 的元数据和实际文件对不上,show里一堆missing。要清就统一走pg_rman delete,定期再用pg_rman show核对一遍备份集合和实际磁盘占用。

6. 切换演练:把判据写死在脚本里,别靠手感

切换这件事,配置写完只是开始,真正的信心来自反复演练。我习惯按三档做:第一档停主库的 postgres 进程,看 pgpool 是否能识别后端故障并触发 failover;第二档停主机的 pgpool 进程,看 watchdog 是否把虚拟 IP 漂到备机;第三档直接断网,看脑裂保护有没有生效。三档的观察点不一样,缺一档就会留下没被验证过的路径。

演练时的判据要固定下来,别每次凭感觉:

演练动作观察命令通过标准
停主库 postgresshow pool_nodes节点 0 状态 down,节点 1 变 primary
停主机 pgpoolpcp_watchdog_info备机 leader,ip addr看到虚拟 IP
断网 30 秒pgpool 日志不出现双 leader,不误切
恢复原主库pcp_attach_node节点 0 以 standby 身份回来

手工摘除和挂回节点用 pcp 命令,这一步在做计划内维护时比等自动切换更可控:

# 摘除节点 0(主库),触发切换 /usr/local/pgpool/bin/pcp_detach_node -h 192.168.10.100 -p 9898 -U pcpuser 0 # 原主库修好、pg_rewind 之后挂回来 /usr/local/pgpool/bin/pcp_attach_node -h 192.168.10.100 -p 9898 -U pcpuser 0 # 看节点最终状态 /usr/local/pgpool/bin/pcp_node_info -h 192.168.10.100 -p 9898 -U pcpuser 0

演练最容易忽略的是应用侧。数据库切过去了,但应用的连接池里还攥着一堆指向旧主库的死连接,表现是“库明明活着,业务一直报连接超时”。所以演练必须带上应用,确认它能在 30 秒内重连到虚拟 IP,并且重连后写的还是新主库。连接串里只写虚拟 IP,不要写死物理机地址,这一条比任何配置都重要。

从那以后我每次上生产前,都强制把这三档演练完整跑一遍,跑完把pg_stat_replication的 lag、pg_stat_archiver的 failed_count、show pool_nodes的输出各截一份存档,作为基线。出事的时候有基线可对比,比现场猜快得多。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进