简介:面向 PostgreSQL 数据库运维与架构人员的实战型技术文档,围绕 WAL 日志流复制搭建主备库,并结合 pgpool-II 实现连接池、负载均衡、读写分离与故障自动切换,回应 Linux 生产环境中数据库高可用的实际需求。整包仅含 1 个 docx 文件,约 773KB,篇幅紧凑但结构完整,按方案介绍、实践环境、客户环境准备、资源调整等章节依次展开。文档从同步复制与异步复制的取舍讲起,给出软件版本清单与环境拓扑示意,再细述创建 postgres 用户、配置目录权限、关闭防火墙与 selinux、网络与 hosts 配置、时钟同步,以及 sysctl 与 limit 资源调优等细节。目前已有 471 人学习,读者可据此复现主库参数配置、备库初始化、pgpool-II 策略设置与故障模拟切换的完整流程,并顺带了解 pg_rman 的备份思路。
1. 版本先对齐:PostgreSQL 12.2 与 pgpool-II 4.1.1 为什么不能照抄 10.3 的老文档
有个坑我印象太深:拿着 PostgreSQL 10.3 时代的安装笔记,在一台 CentOS 7.5 上装 PostgreSQL 12.2 做 PostgreSQL 主从,再从库上把 recovery.conf 写得一字不差,备库启动直接报错退出,日志里那句recovery.conf is not used让人半天回不过神。PostgreSQL 主从流复制 + pgpool 高可用这套组合在中小业务里太常见了,主库扛写、备库扛读、pgpool 挡在前面做连接池和读写分离,出故障时 VIP 漂过去继续服务。可它偏偏是个对版本极其敏感的组合:12 版本废掉了 recovery.conf,pgpool 3.6 和 4.1 的配置项差出一大截,pg_rman 又必须跟数据库大版本对齐,任何一环抄错,装出来的就是一个看着像能用、实际上切换即翻车的环境。下面按我实际落地的顺序走一遍,RHEL/CentOS 通用,新手照做能跑通,熟手能看清版本之间的取舍。
2. 把系统铺平:CentOS 7.5 上关防火墙、配 hosts、调 sysctl 的落地顺序
装数据库之前先花二十分钟把操作系统收拾干净,这部分工作看起来琐碎,实际上是后面所有玄学问题的源头。我见过太多案例:流复制连不上,查半天是 firewalld 拦了 5432;主备时间差三分钟,WAL 回放的时间线判断直接乱掉;shared_buffers调到 8GB,实例起不来只因为kernel.shmmax还是默认值。所以第二章不讲数据库,只讲系统层该铺的四件事:目录与软件包、内核参数与 limits、时间同步、SSH 免密。
2.1 目录规划与安装包落位
我习惯把所有安装介质统一放到/opt/soft,源码解压到/opt/src,数据库数据目录单独挂一块盘放/data。这么做的好处是后期做备份、扩盘、排查磁盘满的时候,路径一眼能对上,不会出现「数据到底在哪个目录」的扯皮。三套软件按下面的结构落位,主备两台机器保持一致。
# 统一目录,主备两台都执行,路径一致后期脚本才好复用 mkdir -p /opt/soft /opt/src /data chown -R postgres:postgres /data # 安装包提前放到 /opt/soft,离线环境尤其要提前准备 # postgresql-12.2.tar.gz pgpool-II-4.1.1.tar.gz pg_rman-1.3.x.tar.gz ls -lh /opt/soft这里强调一点:客户环境不能联网是常态。常见做法是先在一台能联网的同版本机器上用 yum 把依赖包下载成 rpm 缓存,打包拷进内网,再用yum localinstall或直接rpm -ivh装。输入的方案里也专门给了「安装环境获取脚本」和「安装脚本」两个附件,思路就是先在能联网的环境把 rpm 缓存收集出来,再拿到离线机器上解压安装。这个流程比现场一个个找依赖靠谱得多,尤其是 gcc、readline-devel、zlib-devel 这类编译期依赖,缺一个 configure 就过不去。
创建 postgres 用户是所有机器的第一步,UID 我一般固定下来,主备一致,后面挂 NFS 或者做目录权限同步时不会因为 UID 不同导致属主错乱。
# 固定 UID,主备一致,避免后期共享目录属主错乱 groupadd -g 1200 postgres useradd -u 1200 -g postgres -m -s /bin/bash postgres echo "Postgres@2024" | passwd --stdin postgres2.2 内核参数与 limits:共享内存不够的锅
PostgreSQL 启动时会向内核申请一大块共享内存,shared_buffers设得越大,对kernel.shmmax和kernel.shmall的要求越高。默认值在 CentOS 7 上通常是 4GB 左右,你要是把shared_buffers拉到 16GB,实例十有八九起不来,报错信息还特别不直观。我一般直接按物理内存的一半给上限,省得以后调参再回来改。
# /etc/sysctl.d/99-pgsql.conf # shmmax 给到物理内存一半以上,shmall 按页数算(4KB 一页) kernel.shmmax = 68719476736 kernel.shmall = 16777216 kernel.sem = 5010 641280 5010 128 net.ipv4.tcp_keepalive_time = 600 net.ipv4.tcp_keepalive_intvl = 30 net.ipv4.tcp_keepalive_probes = 3 vm.swappiness = 10 vm.overcommit_memory = 2# 立即生效并核对,改完不生效是最常见的低级错误 sysctl -p /etc/sysctl.d/99-pgsql.conf sysctl kernel.shmmax kernel.shmall参数含义上,kernel.shmmax是单个共享内存段的最大字节数,kernel.shmall是系统范围内可分配的共享内存总页数,两者必须同时够用。vm.swappiness = 10是让数据库尽量别被换出到 swap,vm.overcommit_memory = 2配合overcommit_ratio使用时要小心,如果机器上还跑着别的吃内存服务,建议保持默认的 0,我这边是专用数据库机器才这么设。改完一定要sysctl -p再sysctl xxx回读一次,配置文件写了但没加载的情况太常见了。
limits 同样要动,PostgreSQL 每个连接对应一个进程,max_connections上到 300 的时候,默认的 1024 文件句柄数就不够看了。
# /etc/security/limits.d/99-pgsql.conf postgres soft nofile 65536 postgres hard nofile 65536 postgres soft nproc 16384 postgres hard nproc 16384 postgres soft memlock unlimited postgres hard memlock unlimited注意limits.d下的文件名排序会影响最终生效值,/etc/security/limits.conf里如果有同名项,后面的文件会覆盖前面的。改完用su - postgres -c "ulimit -n"验证,注意必须带-走登录 shell,直接su postgres是不读 profile 的,这个细节坑过不少人。
2.3 时钟同步与 SSH 免密:流复制的隐形地基
两台机器的系统时间必须同步,这不是洁癖。流复制的 WAL 回放、pg_rman 的备份时间点、pgpool 的健康检查日志,全都依赖时间戳判断先后。主备差个几分钟,排查问题时日志对不上,等于摸黑找路。内网环境我一般直接配 chrony 指向内网时间源,没有内网源就指向网关。
# 主备都装 chrony,指向同一个内网时间源 yum install -y chrony sed -i 's/^server .*/server 192.168.10.1 iburst/' /etc/chrony.conf systemctl enable --now chronyd chronyc sources -vSSH 免密是 pg_basebackup 做备库、以及后面写切换脚本的前提。虽然 pg_basebackup 本身可以走密码,但故障切换脚本里再让你交互输密码,那就没法自动化了。
# postgres 用户下生成密钥,主备互信 su - postgres -c "ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa" su - postgres -c "ssh-copy-id -i ~/.ssh/id_rsa.pub postgres@192.168.10.12" su - postgres -c "ssh 192.168.10.12 'hostname && date'"hosts 文件两台都要写全,pgpool 的 watchdog 靠主机名互相识别,hostname和/etc/hosts对不上,后面 VIP 漂移会莫名失败。
# /etc/hosts 主备保持一致 192.168.10.11 node1 192.168.10.12 node2 192.168.10.100 vip-pg最后把 firewalld 和 SELinux 关掉。生产上更稳妥的做法是开端口而不是关服务,但数据库这套组件的端口比较杂(5432、9999、9000、9898),内网隔离环境下直接关掉更省事,边界防火墙那边做好策略就行。
systemctl stop firewalld && systemctl disable firewalld setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config getenforce3. PostgreSQL 12.2 主从流复制:从 initdb 到备库跟上主库
系统层铺完,进入正题。流复制的本质是主库的 walsender 把 WAL 日志推给备库的 walreceiver,备库的 startup 进程不断回放。所谓「实时」,靠的是这条 TCP 通道常连不断,而不是像 WAL 归档那样等一个日志文件写满再传。理论上不需要额外软件,装两份同样的 PostgreSQL,参数配好就能跑。但 12 版本在备库标识方式上做了一次断代式改动,这一节的重点就是把新旧差异讲透。
3.1 编译安装与环境变量
依赖包一次装齐,我列的是编译 PostgreSQL 最常用的那组,缺 readline-devel 会导致 psql 没有历史命令,缺 zlib-devel 会在 configure 阶段直接报错。
yum install -y gcc gcc-c++ make readline-devel zlib-devel openssl-devel \ pam-devel libxml2-devel libxslt-devel perl-ExtUtils-Embed# 解压编译,--prefix 决定后面环境变量怎么写,别中途改 tar -zxvf /opt/soft/postgresql-12.2.tar.gz -C /opt/src cd /opt/src/postgresql-12.2 ./configure --prefix=/usr/local/pgsql12 --with-pgport=5432 --with-openssl make -j4 && make install--prefix我习惯带上大版本号,/usr/local/pgsql12、/usr/local/pgsql10,同一台机器上并存两套的时候不会打架,升级验证也方便。make -j4的并行数按 CPU 核数来,编译 PostgreSQL 本身不慢,十几分钟的事。
环境变量写到 postgres 用户的.bash_profile,注意LD_LIBRARY_PATH一定要有,否则某些场景下 psql 会去找系统自带的旧 libpq。
# postgres 用户 ~/.bash_profile export PGHOME=/usr/local/pgsql12 export PGDATA=/data/pgdata export PATH=$PGHOME/bin:$PATH export LD_LIBRARY_PATH=$PGHOME/lib:$LD_LIBRARY_PATH export MANPATH=$PGHOME/share/man:$MANPATH初始化数据目录,字符集和 locale 要跟应用侧对齐,这个在建库之后基本改不了,只能重新 initdb。
su - postgres -c "/usr/local/pgsql12/bin/initdb -D /data/pgdata -E UTF8 --locale=C"3.2 主库 postgresql.conf 与 pg_hba.conf 的参数取舍
流复制相关参数集中在postgresql.conf里,这一段是整个安装里最需要理解而不是照抄的部分。
# /data/pgdata/postgresql.conf 主库 node1 listen_addresses = '*' port = 5432 max_connections = 300 shared_buffers = 4GB wal_level = replica max_wal_senders = 10 max_replication_slots = 10 wal_keep_segments = 512 hot_standby = on archive_mode = on archive_command = 'test ! -f /data/pg_archive/%f && cp %p /data/pg_archive/%f' synchronous_commit = on synchronous_standby_names = 'node2'参数逐个说清楚。wal_level = replica是流复制的最低要求,它会额外记录足够的信息供备库重放;如果要做逻辑订阅才需要logical,代价是 WAL 体积更大。max_wal_senders要大于备库数量,我一般留 10 个余量,pg_basebackup 做基础备份时也占用这个名额。wal_keep_segments表示主库至少保留多少个 WAL 段,512 个段按默认 16MB 算就是 8GB,作用是备库短暂断连时主库别把日志清掉,给重连留窗口。
这里有个版本差异必须点出来:wal_keep_segments在 PostgreSQL 12 里仍然可用但已标记为弃用,单位固定是段;到 PostgreSQL 13 才正式换成wal_keep_size = 8GB这种带单位的写法。照着 13 的文档往 12 上写wal_keep_size,实例起不来。这种版本错位在新老环境并行的时候最容易出现。
synchronous_standby_names = 'node2'走的是同步复制,主库事务提交前要等备库确认收到 WAL。这是性能与数据安全之间的取舍:同步复制几乎不丢数据,但事务响应时间会被备库的写入速度拖住;异步复制(把这一行注释掉,同时synchronous_commit保持 on)响应快,主库突然崩溃时可能丢最后不到一秒的数据。我的默认选择是同步,业务能接受轻微写入延迟就选同步,压测对写入延迟敏感的报表类场景再退成异步。
pg_hba.conf负责访问控制,复制连接和普通连接要分别放行。
# /data/pgdata/pg_hba.conf host replication repl 192.168.10.0/24 md5 host all all 192.168.10.0/24 md5 local all all peer注意认证方式这里写的是md5。PostgreSQL 10 开始支持scram-sha-256,但 12 版本的password_encryption默认值还是md5,所以直接写scram-sha-256会认证失败。要么统一用 md5,要么先改password_encryption = 'scram-sha-256'再重建用户并重设密码。这个坑后面第 5 章还会展开。
改完配置重启主库,然后建复制用户。
su - postgres -c "/usr/local/pgsql12/bin/pg_ctl -D /data/pgdata -l /data/pgdata/pglog.log start" # 建复制专用账号,replication 属性必需 psql -Upostgres -c "CREATE ROLE repl WITH REPLICATION LOGIN PASSWORD 'Repl@2024';" psql -Upostgres -c "SELECT * FROM pg_create_physical_replication_slot('node2_slot');"3.3 备库 pg_basebackup:12 版本不再有 recovery.conf
这是整套流程里版本差异最大的一处。PostgreSQL 12 之前,备库靠数据目录下的recovery.conf标识自己是备库,里面写standby_mode = 'on'和primary_conninfo;从 12 版本开始,这个文件被彻底废弃,标识改成一个空文件standby.signal,连接信息则合并进了postgresql.conf。所以从 10.3 升级到 12.2 的环境,备库怎么起都起不来,看日志只会看到一句recovery.conf is not used,没有更多提示。
基础备份我直接用pg_basebackup的-R参数,它会自动生成standby.signal并把primary_conninfo写进postgresql.conf,手工配置的机会都不给你,也就不会写错。
# 备库 node2:先停库、清空数据目录,再从主库拉基础备份 su - postgres -c "/usr/local/pgsql12/bin/pg_ctl -D /data/pgdata stop -m fast" rm -rf /data/pgdata/* su - postgres -c "/usr/local/pgsql12/bin/pg_basebackup \ -h 192.168.10.11 -p 5432 -U repl \ -D /data/pgdata -Fp -Xs -P -R"参数含义:-Fp表示 plain 格式,直接铺成数据文件;-Xs表示流式传输 WAL,备份过程中产生的日志一起带走,避免备份完还要单独补;-P显示进度;-R就是自动生成 standby 标识和连接串。执行完看一眼目录,应该能看到standby.signal和postgresql.auto.conf里的primary_conninfo。
cat /data/pgdata/standby.signal # 空文件,存在即代表备库 grep primary_conninfo /data/pgdata/postgresql.auto.conf备库的postgresql.conf里hot_standby = on要打开,这样备库才能接受只读查询,pgpool 的读写分离才有意义。另外max_connections建议不小于主库,pgpool 的连接池会同时往两边分发。
su - postgres -c "/usr/local/pgsql12/bin/pg_ctl -D /data/pgdata -l /data/pgdata/pglog.log start"3.4 同步复制与异步复制怎么选
到这一步流复制应该已经跑起来了,验证方式是在主库查pg_stat_replication。
-- 主库执行,state 为 streaming、sync_state 为 sync 说明同步复制生效 SELECT client_addr, state, sync_state, sent_lsn, replay_lsn FROM pg_stat_replication;sync_state有三种取值:sync表示同步备库,主库提交要等它;async表示异步;potential表示在synchronous_standby_names的候选列表里但当前不是同步的。如果这一行根本查不出来,说明备库没连上,先去看备库日志里的连接报错,八成是pg_hba.conf或者复制用户密码的问题。
同步复制还有个容易被忽略的副作用:备库一旦挂掉,主库的写操作会卡住不提交,业务侧表现就是写入接口全部超时。所以生产上要么配两个备库做 quorum(synchronous_standby_names = 'ANY 1 (node2, node3)'),要么接受异步复制的少量数据风险。我在只有一主一备的环境里更倾向异步,理由很直接:备库维护重启是常态,为了那一秒的数据窗口让整个写入链路停摆,不划算。
4. pgpool-II 4.1.1 安装与配置:连接池、读写分离、watchdog 与 VIP
流复制解决了数据同步,但客户端仍然需要知道主库在哪、备库在哪,主库挂了还得手工改连接串,这就不叫高可用了。pgpool-II 站在数据库和客户端中间,对外只暴露一个 9999 端口,内部负责连接复用、把 SELECT 分发到备库、主库故障时自动切换并把虚拟 IP 漂到活着的节点上。4.x 版本相比 3.6 最大的变化是引入了backend_clustering_mode,必须显式声明集群模式,不写这一项启动就报错。
4.1 编译安装与 pgpool_regclass 等函数注册
pgpool 的编译依赖 PostgreSQL 的开发头文件,所以要在--with-pgsql里指向刚装好的那套 PostgreSQL。
tar -zxvf /opt/soft/pgpool-II-4.1.1.tar.gz -C /opt/src cd /opt/src/pgpool-II-4.1.1 ./configure --prefix=/usr/local/pgpool \ --with-pgsql=/usr/local/pgsql12 \ --with-openssl make -j4 && make install装完之后要在主库上注册 pgpool 需要的几个函数,主要是pgpool_regclass和pgpool_recovery,前者用于处理带 schema 的表名,后者在在线恢复时被调用。不注册也能跑,但一旦涉及带 schema 的查询或者在线恢复就会报函数不存在。
# 在主库 node1 上执行,把 pgpool 自带 SQL 里的函数装进 template1 psql -Upostgres -f /usr/local/pgpool/share/pgpool_configs/pgpool-regclass.sql psql -Upostgres -d template1 -f /usr/local/pgpool/share/pgpool_configs/pgpool-regclass.sql # pgpool_recovery 等函数用于在线恢复,一并装上 psql -Upostgres -f /usr/local/pgpool/share/pgpool_configs/pgpool-recovery.sql psql -Upostgres -d template1 -f /usr/local/pgpool/share/pgpool_configs/pgpool-recovery.sql环境变量同样写进 postgres 用户的 profile,注意 pgpool 的bin要放在PGHOME/bin前面,否则会误调 PostgreSQL 自带的同名工具。
export PGHOME=/usr/local/pgsql12 export PGDATA=/data/pgdata export PGPORT=5432 export PATH=/usr/local/pgpool/bin:$PGHOME/bin:$PATH export LD_LIBRARY_PATH=/usr/local/pgpool/lib:$PGHOME/lib:$LD_LIBRARY_PATHpgpool 运行需要一个工作目录存 pid 和日志,提前建好并授权。
mkdir -p /var/run/pgpool /var/log/pgpool chown -R postgres:postgres /var/run/pgpool /var/log/pgpool4.2 pgpool.conf 的核心参数
配置文件在/usr/local/pgpool/etc/pgpool.conf,主备两台都要配,除了backend_hostname的顺序和 watchdog 的主机名不同,其余基本一致。下面是我实际用的核心片段。
# 对外监听,客户端连的就是这个 9999 listen_addresses = '*' port = 9999 # 4.x 必须显式声明集群模式,流复制场景就是 streaming_replication backend_clustering_mode = 'streaming_replication' # 后端节点 0 是主库,节点 1 是备库 backend_hostname0 = '192.168.10.11' backend_port0 = 5432 backend_weight0 = 1 backend_flag0 = 'ALLOW_TO_FAILOVER' backend_hostname1 = '192.168.10.12' backend_port1 = 5432 backend_weight1 = 1 backend_flag1 = 'ALLOW_TO_FAILOVER' # 连接池 connection_cache = on num_init_children = 32 max_pool = 4 child_life_time = 300 client_idle_limit = 0 # 复制状态检查账号,必须是真实存在的数据库用户 sr_check_user = 'pgpool_checker' sr_check_password = 'Check@2024' sr_check_period = 10 # 健康检查,备库挂了不影响主库写,主库挂了下游才会切换 health_check_period = 10 health_check_timeout = 20 health_check_user = 'pgpool_checker' health_check_password = 'Check@2024' health_check_database = 'postgres' # 故障切换脚本 failover_command = '/usr/local/pgpool/bin/failover.sh %d %H %P %D %m %M %U %R' # 读写分离:备库权重给 1 表示读请求全部走备库 database_redirect_preference_list = '' app_name_redirect_preference_list = ''参数说明得展开几点。num_init_children乘以max_pool就是 pgpool 能同时维持的最大数据库连接数,32 乘 4 等于 128,要小于主库的max_connections减去预留连接数。这个值不是越大越好,pgpool 是多进程模型,每个 child 是一个独立进程,开太大内存扛不住。backend_weight在流复制模式下只影响读请求的分发比例,两个都是 1 表示读请求平均分到主备;如果想让主库不接读,把backend_weight0设为 0 即可。
backend_flag的设置要谨慎。默认ALLOW_TO_FAILOVER表示这个节点挂了 pgpool 会做故障切换;DISALLOW_TO_FAILOVER用于那些不允许被踢掉的节点。我一般两台都保持允许,让 pgpool 统一管理。只有在明确的维护窗口里,才会临时把主库标成DISALLOW_TO_FAILOVER防止误切换。
再把连接池需要的账号建出来,这个账号在 pgpool 的健康检查和复制状态检查里会用到。
-- 主库执行,账号会随流复制同步到备库 CREATE ROLE pgpool_checker WITH LOGIN PASSWORD 'Check@2024'; GRANT pg_monitor TO pgpool_checker;pg_monitor是 PostgreSQL 10 引入的预定义角色,把监控相关的视图权限打包给了它,比过去一个个GRANT SELECT干净得多。12 版本上直接这么用就行。
4.3 pool_hba、pcp.conf 与 pool_passwd 三个文件别混
pgpool 有一组配置文件长得像但用途完全不同,安装时最容易装错。
pool_hba.conf控制客户端连到 pgpool 时的认证,格式和 PostgreSQL 的pg_hba.conf几乎一样,要在主配置里打开enable_pool_hba = on才生效。
# /usr/local/pgpool/etc/pool_hba.conf host all all 192.168.10.0/24 md5 local all all md5pool_passwd是 pgpool 自己做认证时用的密码文件,客户端连 9999 时的密码校验就发生在这里,不生成的话连接会报认证失败。
# 交互式输入数据库用户的密码,-m 表示生成 md5 格式 /usr/local/pgpool/bin/pg_md5 -m -f /usr/local/pgpool/etc/pool_passwd -u postgres /usr/local/pgpool/bin/pg_md5 -m -f /usr/local/pgpool/etc/pool_passwd -u pgpool_checker grep -c . /usr/local/pgpool/etc/pool_passwdpcp.conf是管理命令pcp_*系列工具的认证文件,看节点状态、踢节点、做在线恢复都靠它。4.x 下用pg_md5 -p生成,会提示输入两次密码。
/usr/local/pgpool/bin/pg_md5 -f /usr/local/pgpool/etc/pcp.conf -p cat /usr/local/pgpool/etc/pcp.conf三个文件的区别我一般这么记:pool_hba.conf管「谁能连进来」,pool_passwd管「密码对不对」,pcp.conf管「管理命令能不能执行」。搞混了就会出现能连库但pcp_node_info报认证失败,或者反过来管理命令正常但业务连不上。
4.4 watchdog 与 delegate_IP 漂移
要做到主库整机宕机仍然可用,光有 pgpool 不够,还要两台 pgpool 之间互相监控,这就是 watchdog。两台 pgpool 通过 9000 端口互发心跳,谁拿到多数票谁持有虚拟 IP,主库挂了由持有 VIP 的那台执行failover_command把备库提升为主库。
# 主备两台 pgpool.conf 的 watchdog 段,hostname 各自的要改成本机 use_watchdog = on # 虚拟 IP,客户端连这个地址,不连具体节点 delegate_IP = '192.168.10.100' # 本机信息 wd_hostname = 'node1' wd_port = 9000 pgpool_port = 9999 # 对端信息 other_pgpool_hostname0 = '192.168.10.12' other_pgpool_port0 = 9999 other_wd_port0 = 9000 other_pgpool_hostname1 = '' other_pgpool_port1 = 9999 other_wd_port1 = 9000 # VIP 上下线命令,需要 sudo 免密 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'这几个命令里的$_IP_$是 pgpool 的占位符,会被替换成delegate_IP的值,不要手工改掉。sudo免密必须配,否则 VIP 上下线会静默失败,日志里只有一行权限不足。
# 给 postgres 用户放行这几条命令,不需要全量 sudo cat >> /etc/sudoers.d/pgpool <<'EOF' postgres ALL=(root) NOPASSWD: /sbin/ip addr add *, /sbin/ip addr del *, /usr/sbin/arping * EOF chmod 440 /etc/sudoers.d/pgpoolfailover 脚本我一般放在/usr/local/pgpool/bin/failover.sh,内容就是根据 pgpool 传进来的参数判断哪台是新的主库,然后去那台机器上执行pg_ctl promote,并处理旧主库的standby.signal。
#!/bin/bash # %d 触发切换的节点号 %H 新主库主机名 %m 新主库节点号 %M 旧主库节点号 FAILED_NODE=$1 NEW_MASTER_HOST=$2 NEW_MASTER_ID=$4 OLD_MASTER_ID=$6 echo "$(date '+%F %T') failover: node${FAILED_NODE} down, promote node${NEW_MASTER_ID}" \ >> /var/log/pgpool/failover.log # 提升备库为主库,12 版本就是 pg_ctl promote,不需要再建 trigger 文件 /usr/bin/ssh -T postgres@${NEW_MASTER_HOST} \ "/usr/local/pgsql12/bin/pg_ctl -D /data/pgdata promote" exit 0脚本里注意pg_ctl promote是 PostgreSQL 12 的标准做法,老版本用的pg_ctl trigger已经废弃。另外脚本必须有执行权限,日志目录要提前建好,不然切换时脚本静默失败,看起来就像 pgpool 没触发。
启动 pgpool 用-n前台跑一次看日志,确认没问题再交给后台。
/usr/local/pgpool/bin/pgpool -n -D -f /usr/local/pgpool/etc/pgpool.conf5. 排查与避坑:启动报错、后端全 down、VIP 不动、备份目录写满
这一章是我这些年踩出来的记录,按现象、原因、解决三段写。这几类问题在第一次搭这套环境时几乎必遇,提前知道能省掉一整晚。
5.1 no pg_hba.conf entry for host:三层认证都要放行
现象:pgpool 启动正常,psql连 9999 端口时报no pg_hba.conf entry for host "192.168.10.11",但直连 5432 是通的。原因:这条链路上有三层认证,客户端的pool_hba.conf、pgpool 到后端的pg_hba.conf、以及pool_passwd里的密码,任何一层没放行都会报类似的错,而报错文本长得几乎一样,很容易找错地方。解决:先看 pgpool 日志确认是哪一段拒绝的,enable_pool_hba = on时客户端侧校验在 pgpool,报错里带的是 pgpool 自己的日志格式;如果是 pgpool 去连后端被拒,要去数据库的pg_hba.conf里加host all all 192.168.10.0/24 md5,然后pg_ctl reload。改完别忘了pool_passwd里对应的用户也要有记录。
5.2 all backend nodes are down:先看 status 再看 health_check
现象:psql连 9999 报all backend nodes are down,但两台数据库明明都在跑。原因:这通常不是数据库真的挂了,而是 pgpool 的健康检查判定失败,被踢出了可用后端池。常见触发点是health_check_user不存在、密码不对、health_check_database指向了一个不存在的库,或者health_check_timeout设得太短而数据库正好在做 checkpoint。解决:先用pcp_node_info看每个节点的status和role,再直接手工用health_check_user去 psql 连一次,能连上说明账号没问题。改完参数后执行pcp_detach_node再pcp_attach_node把节点拉回来,或者干脆重启 pgpool。我一般把health_check_period设成 10 秒、health_check_timeout设成 20 秒,宁可慢一点也别误判。
5.3 切换之后主备标识没改,重启就成两个主库
现象:failover 演练做完,业务连上新主库一切正常,但过两天重启旧主库,它又变回主库,两台同时接受写,数据直接分叉。原因:PostgreSQL 12 的备库标识是standby.signal文件,切换之后旧主库这个文件不存在,如果不手工创建,它启动时就是主库身份。老版本是改recovery.conf,新版本对应的是补standby.signal并确认postgresql.auto.conf里的primary_conninfo指向新主库。解决:在 failover 脚本里补上反向处理,旧主库恢复后走pg_rewind追平数据,然后创建standby.signal、修好primary_conninfo再启动,最后pcp_attach_node把它挂回 pgpool。这一步不能省,否则就是给自己埋了个双主的定时炸弹。
5.4 pg_rman 备份失败:归档目录和版本两个坑
现象:pg_rman backup执行到一半报错退出,日志里提示找不到 WAL 归档文件。原因有两个层面。一是archive_command没有把 WAL 写到 pg_rman 期望的$BACKUP_PATH/arclog目录下,pg_rman 做完整备份前会先做一次 WAL 切换,等不到归档文件就超时。二是 pg_rman 的版本和 PostgreSQL 大版本不匹配,装了个只支持到 10 的 pg_rman 去备 12 的库,备份能跑但恢复阶段直接报错。解决:把archive_command指到备份目录下面,并确保目录存在、属主是 postgres。
# postgresql.conf 里配合 pg_rman 的归档设置 archive_mode = on archive_command = 'test ! -f /data/pg_rman/arclog/%f && cp %p /data/pg_rman/arclog/%f'# 备份目录初始化与一次完整备份 mkdir -p /data/pg_rman chown -R postgres:postgres /data/pg_rman su - postgres -c "pg_rman init -B /data/pg_rman -D /data/pgdata" su - postgres -c "pg_rman backup --backup-mode=full -B /data/pg_rman -D /data/pgdata" su - postgres -c "pg_rman validate -B /data/pg_rman" su - postgres -c "pg_rman show -B /data/pg_rman"版本这块的判断标准很直接:pg_rman 的版本号要对得上 PostgreSQL 的大版本,装之前先看它的 README 支持矩阵,别凭感觉。备份脚本我习惯分两个,一个做每日完整备份、一个做归档清理,用 crontab 跑,日志单独落文件,出问题翻日志比翻终端输出靠谱。
6. 装完怎么验证:三条命令看流复制,一次 failover 演练收尾
环境装完不等于能用,我见过太多「装完就交差」的现场,真出事的时候才发现流的根本不是同一个库。验证我分三层做,从下往上,每层都有明确的判据。
第一层看流复制本身。在主库执行下面这条,state必须是streaming,sync_state要和配置的同步模式对得上,sent_lsn和replay_lsn的差值应该很小。
-- 主库:备库有没有真的在收日志 SELECT client_addr, state, sync_state, pg_wal_lsn_diff(sent_lsn, replay_lsn) AS lag_bytes FROM pg_stat_replication;-- 备库:确认只读,且正在回放 SELECT pg_is_in_recovery(), pg_last_wal_receive_lsn(), pg_last_wal_replay_lsn();pg_is_in_recovery()返回t才是备库。这条命令还能反过来验证 5.3 那个坑:切换之后旧主库如果还返回f,说明它还是主库身份,standby.signal没补上。
第二层看 pgpool 的视角。通过 VIP 连 9999,把节点状态和实际数据对一遍。
# 通过虚拟 IP 连 pgpool,看后端节点状态 psql -h 192.168.10.100 -p 9999 -U postgres -d postgres -c "show pool_nodes;" ip addr show eth0 | grep 192.168.10.100show pool_nodes的输出里重点看三列:status为up、role主库是primary备库是standby、replication_state为streaming。三列都对了才算 pgpool 真正认出了这套主从关系,只看status是不够的。
第三层做一次真实的 failover 演练,这一步不能省。挑一个业务低峰,直接在备库上pg_ctl stop -m fast停掉主库,观察 VIP 是否漂到备库节点,show pool_nodes里备库是否变成primary,业务连接是否在几十秒内恢复。
| 验证项 | 命令 | 期望结果 |
|---|---|---|
| 流复制状态 | 主库查 pg_stat_replication | state = streaming |
| 备库身份 | 备库查 pg_is_in_recovery | 返回 t |
| 节点识别 | show pool_nodes | 两行均 up,角色正确 |
| VIP 归属 | ip addr show eth0 | 只在存活节点上出现 |
| 切换后写入 | 新主库建表插入 | 成功且能同步到旧主库 |
演练完记得把旧主库用pg_rewind追平、补上standby.signal、pcp_attach_node挂回去,再跑一遍第一层验证确认它重新变成streaming。这套动作我从第一次切换翻车之后就再没省过,因为「装完能连」和「切换后能用」之间,隔着的正是这一遍演练。希望帮到你。
本文还有配套的精品资源,点击获取