PostgreSQL 18 流复制(Streaming Replication)集群部署指南
在 3 台 Ubuntu 22.04 服务器上基于 PostgreSQL 官方 PGDG 源从 0 到 1 搭建流复制一主两从集群,覆盖 WAL 物理复制与归档参数、pg_basebackup 建从库、读写分离与只读约束、同步异步切换及主库故障从库提升重定向实测。目标:掌握 PostgreSQL 流复制完整搭建与高可用容灾方法。
PostgreSQL 18 流复制(Streaming Replication)集群部署指南
目标:在 3 台 Ubuntu 22.04 服务器上基于 PostgreSQL 18 官方 PGDG 源从 0 到 1 搭建流复制集群(一主两从),覆盖 WAL 物理复制参数配置、pg_basebackup 建从库、读写分离与只读约束验证、同步/异步切换以及主库故障后的从库提升与重定向实测。
环境信息
- 操作系统:Ubuntu 22.04.5 LTS(3 台服务器)
- 系统架构:x86_64
- PostgreSQL 版本:18.6-1.pgdg22.04+2(PostgreSQL Community Server / PGDG 源)
- 集群拓扑:一主两从
pg-node110.0.0.11 —— 主库 Primarypg-node210.0.0.12 —— 从库 Standbypg-node310.0.0.13 —— 从库 Standby
- 数据目录:
/var/lib/postgresql/18/main - 主配置文件:
/etc/postgresql/18/main/postgresql.conf - 认证配置:
/etc/postgresql/18/main/pg_hba.conf
💡 说明:三台服务器处于同一内网(10.0.0.0/24 网段示意),已通过
/etc/hosts配置主机名映射:10.0.0.11 pg-node1、10.0.0.12 pg-node2、10.0.0.13 pg-node3,后续文档统一用主机名互访。
一、流复制原理简介
流复制(Streaming Replication)是 PostgreSQL 官方内置的物理 WAL 复制方案:
- 主库在提交事务时生成 WAL(Write-Ahead Log)日志,
walsender进程把 WAL 变更推送给从库。 - 从库通过
walreceiver接收 WAL 并调用startup进程持续重放,使从库与主库保持同一份物理数据。 - 默认异步(
synchronous_standby_names为空),主库不等从库确认即返回;可配置为同步(至少一个从库确认后才提交)。 - 由于是"物理复制",从库与主库的整库完全一致,适合基础高可用、读写分离、数据备份,但不支持跨大版本/跨架构,也不支持只同步部分表(那是逻辑复制的强项)。
适用与局限:
| 能力 | 流复制 |
|---|---|
| 复制内容 | 整库(物理块级 WAL) |
| 一致性 | 异步(可选同步) |
| 跨版本升级 | 不支持(主从必须同大版本) |
| 部分表同步 | 不支持 |
| 读写分离 / 只读扩展 | 支持(从库只读) |
| 高可用 / 故障切换 | 支持(可提升从库) |
二、三台服务器安装 PostgreSQL 18
三台服务器安装方式完全相同,以下以 pg-node1 为例逐步执行,pg-node2/pg-node3 执行一致步骤。
步骤 1. 更新软件源并安装前置工具
在 pg-node1 上执行:
sudo apt-get update
sudo apt-get install -y curl ca-certificates gnupg
执行结果:
curl is already the newest version.
ca-certificates is already the newest version.
gnupg is already the newest version.
0 upgraded, 0 newly installed, 0 to remove.
💡
curl用于下载官方 GPG 密钥;ca-certificates提供 HTTPS 证书校验;gnupg提供签名校验。
步骤 2. 导入 PGDG 官方密钥并添加软件源
在 pg-node1 上执行:
sudo install -d /usr/share/postgresql-common/pgdg
sudo curl -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc --fail https://www.postgresql.org/media/keys/ACCC4CF8.asc
sudo sh -c 'echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
sudo apt-get update
执行结果:
Get:1 https://apt.postgresql.org/pub/repos/apt jammy-pgdg InRelease [158 kB]
Get:2 https://apt.postgresql.org/pub/repos/apt jammy-pgdg/main amd64 Packages [302 kB]
Fetched 460 kB in 1s (312 kB/s)
Reading package lists... Done
步骤 3. 确认可用版本
在 pg-node1 上执行:
apt-cache policy postgresql-18
执行结果(关键行):
postgresql-18:
Installed: (none)
Candidate: 18.6-1.pgdg22.04+2
步骤 4. 安装 PostgreSQL 服务端与客户端
在 pg-node1 上执行:
sudo apt-get install -y postgresql-18 postgresql-client-18
执行结果(关键行):
Creating new PostgreSQL cluster 18/main ...
/usr/lib/postgresql/18/bin/initdb -D /var/lib/postgresql/18/main --auth-local peer --auth-host scram-sha-256 --no-instructions
The database cluster will be initialized with locale "C.UTF-8".
Data page checksums are enabled.
Setting up postgresql-18-jit (18.6-1.pgdg22.04+2) ...
Processing triggers for libc-bin (2.35-0ubuntu3.14) ...
💡 PGDG 包安装时会自动用
initdb初始化名为18/main的集群,--auth-host scram-sha-256表示远程 TCP 采用 SCRAM 密码认证。
步骤 5. 启动集群并确认三台都在线
分别在 pg-node1 / pg-node2 / pg-node3 上执行:
sudo pg_ctlcluster 18 main start
pg_lsclusters
执行结果(三台一致):
Ver Cluster Port Status Owner Data directory Log file
18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
步骤 6. 验证版本
在 pg-node1 上执行:
sudo -u postgres psql --version
sudo -u postgres psql -c "SELECT version();"
执行结果:
psql (PostgreSQL) 18.6 (Ubuntu 18.6-1.pgdg22.04+2)
PostgreSQL 18.6 (Ubuntu 18.6-1.pgdg22.04+2) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0, 64-bit
⚠️ 排障说明:三台服务器全部安装完成后,才在 pg-node1 上继续做主库配置;pg-node2/pg-node3 的本地集群先保持初始状态,后面会用备份方式覆盖成从库。若安装过程中 apt-get 因时区交互提示卡住(如出现 Geographic area: 选择界面),可预设 DEBIAN_FRONTEND=noninteractive TZ=Asia/Shanghai 后重跑安装,避免交互阻塞。
三、配置主库 pg-node1
步骤 7. 创建复制账号 repuser
在 pg-node1 上执行:
sudo -u postgres psql -c "CREATE ROLE repuser WITH REPLICATION LOGIN PASSWORD 'Rep@2024';"
执行结果:
CREATE ROLE
💡
REPLICATION是复制专用属性,授予后该账号才允许执行pg_basebackup、建立 WAL 复制连接;LOGIN表示允许登录。
步骤 8. 修改 postgresql.conf(主库流复制参数)
编辑 /etc/postgresql/18/main/postgresql.conf,在 pg-node1 上执行以下修改:
sudo sed -i "s/^#listen_addresses.*/listen_addresses = '*'"'*/' /etc/postgresql/18/main/postgresql.conf
sudo sed -i "s/^#wal_level.*/wal_level = replica/" /etc/postgresql/18/main/postgresql.conf
sudo sed -i "s/^#max_wal_senders.*/max_wal_senders = 10/" /etc/postgresql/18/main/postgresql.conf
sudo sed -i "s/^#max_replication_slots.*/max_replication_slots = 10/" /etc/postgresql/18/main/postgresql.conf
echo "wal_keep_size = 1GB" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
执行归档目录准备(新增,补充 WAL 归档能力):
sudo mkdir -p /var/lib/postgresql/18/archive
sudo chown postgres:postgres /var/lib/postgresql/18/archive
sudo sed -i "s|^#archive_mode.*|archive_mode = on|" /etc/postgresql/18/main/postgresql.conf
echo "archive_command = 'cp %p /var/lib/postgresql/18/archive/%f'" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
参数说明:
| 参数 | 值 | 作用 |
|---|---|---|
listen_addresses | '*' | 监听所有网卡,允许从库/客户端远程连接 |
wal_level | replica | 启用物理复制所需的 WAL(含归档/恢复信息) |
max_wal_senders | 10 | 允许的最大 WAL 发送进程数(≥从库数) |
max_replication_slots | 10 | 复制度/内部复制槽上限 |
wal_keep_size | 1GB | 保留 WAL 段容量,避免从库短暂离线丢段 |
archive_mode | on | 开启 WAL 归档,配合 archive_command 把完整 WAL 段复制到归档目录 |
archive_command | cp %p … | WAL 切换后执行的归档命令,%p=当前 WAL 文件路径,%f=文件名 |
💡 归档(archive)与流复制(walsender 实时推送)是两套独立的 WAL 处理链路:流复制让从库实时追平,归档则把已回满的 WAL 段落盘留存,用于长时间故障后的增量回放/备份。归档依赖
wal_level=replica(replica/minimal 之下归档不生效),生产环境建议与流复制同时开启。归档目录必须由 postgres 属主,且需预留足够磁盘空间。
验证配置(在 pg-node1 上执行):
sudo -u postgres psql -c "SHOW wal_level; SHOW max_wal_senders; SHOW listen_addresses;"
执行结果:
wal_level
-----------
replica
max_wal_senders
-----------------
10
listen_addresses
-----------------
*
步骤 9. 修改 pg_hba.conf(放行复制连接)
在 pg-node1 编辑 /etc/postgresql/18/main/pg_hba.conf,追加:
sudo tee -a /etc/postgresql/18/main/pg_hba.conf <<'EOF'
# ---- streaming replication: app + repuser from standby nodes ----
host all all 10.0.0.0/24 scram-sha-256
host replication repuser 10.0.0.12/32 scram-sha-256
host replication repuser 10.0.0.13/32 scram-sha-256
EOF
💡 第一行放行整个内网做业务读写(后续从库只读查询也可连);后两行仅放行
repuser从两个从库 IP 建立复制连接。
步骤 10. 重启主库使配置生效
在 pg-node1 上执行:
sudo pg_ctlcluster 18 main restart
pg_lsclusters
执行结果:
Ver Cluster Port Status Owner Data directory Log file
18 main 5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
四、用 pg_basebackup 构建从库
步骤 11. 在 pg-node2 上用备份方式生成从库数据
思路:
pg_basebackup从主库物理备份整个数据目录,配合-R自动写入standby.signal(标记恢复态)和primary_conninfo(回连主库信息)。pg-node2/pg-node3 各自独立执行,禁止脚本循环批量,逐台执行。
在 pg-node2 上逐步执行:
# 1. 停止本机初始集群(其数据目录是空的初始库,需清空后用备份覆盖)
sudo pg_ctlcluster 18 main stop
# 2. 清空数据目录(文件属主为 postgres,用 root 清理后再由 basebackup 重建)
sudo rm -rf /var/lib/postgresql/18/main/*
# 3. 从主库 pg-node1 拉取整库备份,-R 生成 standby.signal 与 primary_conninfo
sudo -u postgres PGPASSWORD=Rep@2024 pg_basebackup -h pg-node1 -U repuser \
-D /var/lib/postgresql/18/main -X stream -P -R
执行结果(pg-basebackup 进度,末行为完成):
waiting for checkpoint
23536/23536 kB (100%), 0/1 tablespace
23536/23536 kB (100%), 1/1 tablespace
验证生成的恢复标记文件(pg-node2 上执行):
ls -l /var/lib/postgresql/18/main/standby.signal /var/lib/postgresql/18/main/postgresql.auto.conf
cat /var/lib/postgresql/18/main/postgresql.auto.conf
执行结果:
-rw------- 1 postgres postgres 417 ... /var/lib/postgresql/18/main/postgresql.auto.conf
-rw------- 1 postgres postgres 0 ... /var/lib/postgresql/18/main/standby.signal
# Do not edit this file manually!
# It will be overwritten by the ALTER SYSTEM command.
primary_conninfo = 'user=repuser password=''Rep@2024'' host=''pg-node1'' port=5432 ...'
💡
standby.signal存在即表示该节点以只读热备模式启动;primary_conninfo告诉它回连哪个主库拉取 WAL。需再开启监听与远程认证(否则无法被外部读取)。
步骤 12. 配置并启动 pg-node2 从库
在 pg-node2 上执行:
sudo sed -i "s/^#listen_addresses.*/listen_addresses = '*'"'*/' /etc/postgresql/18/main/postgresql.conf
sudo tee -a /etc/postgresql/18/main/pg_hba.conf <<'EOF'
host all all 10.0.0.0/24 scram-sha-256
host replication repuser 10.0.0.0/24 scram-sha-256
EOF
echo "hot_standby_feedback = on" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
echo "max_standby_streaming_delay = 30s" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
echo "wal_receiver_status_interval = 10s" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
echo "recovery_target_timeline = latest" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
sudo pg_ctlcluster 18 main start
从库进阶复制参数(补充):
| 参数 | 值 | 作用 |
|---|---|---|
hot_standby_feedback | on | 从库把正在执行的查询反馈给主库,避免主库清理掉从库仍需要的行版本,防止从库查询中断 |
max_standby_streaming_delay | 30s | 从库应用 WAL 与本地查询冲突时的最大等待时间 |
wal_receiver_status_interval | 10s | 从库每隔多久向主库上报一次复制进度/状态 |
recovery_target_timeline | latest | 从库始终恢复到最新时间线,故障切换并存时间线时也能正确跟随 |
💡 关于 max_connections:流复制场景建议把从库的
max_connections调到大于主库(如主库 100、从库 200)。因为主库只需服务自身连接,而从库需同时服务「本地业务查询 + 恢复重放进程」的返回集,资源需求更高。本环境暂未调整该值,正式环境请按"从库 > 主库"原则单独设置并重启。
验证恢复态(pg-node2 上执行):
sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();"
执行结果:
t
💡
t(true)表示该节点正处于恢复/从库状态(只读)。
步骤 13. 相同方式构建 pg-node3 从库
在 pg-node3 上逐一执行(命令与 pg-node2 完全一致,只是该服务器本身):
# 1. 停止本机初始集群并清空数据目录
sudo pg_ctlcluster 18 main stop
sudo rm -rf /var/lib/postgresql/18/main/*
# 2. 从主库拉取整库备份
sudo -u postgres PGPASSWORD=Rep@2024 pg_basebackup -h pg-node1 -U repuser \
-D /var/lib/postgresql/18/main -X stream -P -R
# 3. 开启监听 + 放行远程认证 + 从库进阶复制参数
sudo sed -i "s/^#listen_addresses.*/listen_addresses = '*'"'*/' /etc/postgresql/18/main/postgresql.conf
sudo tee -a /etc/postgresql/18/main/pg_hba.conf <<'EOF'
host all all 10.0.0.0/24 scram-sha-256
host replication repuser 10.0.0.0/24 scram-sha-256
EOF
echo "hot_standby_feedback = on" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
echo "max_standby_streaming_delay = 30s" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
echo "wal_receiver_status_interval = 10s" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
echo "recovery_target_timeline = latest" | sudo tee -a /etc/postgresql/18/main/postgresql.conf
# 4. 启动并确认处于恢复态
sudo pg_ctlcluster 18 main start
sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();"
执行结果:
... pg_basebackup progress 23608/23608 kB (100%), 1/1 tablespace
standby.signal present on node3
t
五、功能验证:读写分离与只读约束
步骤 14. 主库写入测试数据
在 pg-node1 主库上执行:
sudo -u postgres psql -c "CREATE DATABASE appdb;"
sudo -u postgres psql -d appdb -c "CREATE TABLE users(id serial primary key, name text, created timestamptz default now());"
sudo -u postgres psql -d appdb -c "INSERT INTO users(name) VALUES ('alice'),('bob'),('carol');"
执行结果:
CREATE DATABASE
CREATE TABLE
INSERT 0 3
步骤 15. 查看主库复制状态(async 异步推送)
在 pg-node1 上执行:
sudo -u postgres psql -x -d appdb -c "SELECT application_name, client_addr, state, sync_state, write_lag, flush_lag, replay_lag FROM pg_stat_replication;"
执行结果:
-[ RECORD 1 ]----+----------------
application_name | 18/main
client_addr | 10.0.0.12 -- pg-node2
state | streaming
sync_state | async
write_lag | 00:00:00.000657
flush_lag | 00:00:00.000793
replay_lag | 00:00:00.001133
-[ RECORD 2 ]----+----------------
application_name | 18/main
client_addr | 10.0.0.13 -- pg-node3
state | streaming
sync_state | async
write_lag | 00:00:00.000302
flush_lag | 00:00:00.000708
replay_lag | 00:00:00.000986
💡
state=streaming表示两个从库正在实时接收 WAL;write_lag/flush_lag/replay_lag均低于 1ms,接近实时。默认sync_state=async(异步)。
步骤 16. 从库读取验证(读写分离)
在 pg-node2 与 pg-node3 上分别执行:
sudo -u postgres psql -d appdb -c "SELECT * FROM users;"
执行结果(两从库一致):
id | name | created
----+-------+-------------------------------
1 | alice | 2026-09-30 00:34:26.831547+08
2 | bob | 2026-09-30 00:34:26.831547+08
3 | carol | 2026-09-30 00:34:26.831547+08
(3 rows)
步骤 17. 验证从库只读约束
从库上是不能写的,在 pg-node2 上尝试写入验证:
sudo -u postgres psql -d appdb -c "INSERT INTO users(name) VALUES ('dave');"
执行结果:
ERROR: cannot execute INSERT in a read-only transaction
💡 从库
hot_standby开启、允许只读查询,但任何写事务都会被拒绝,从而保证读写分离的安全。
六、同步/异步模式切换
步骤 18. 开启同步复制(至少一个从库确认)
在 pg-node1 上执行(把主库要等待确认的从库名单写入参数,18/main 是主库集群名,也即两个从库上报的 application_name):
sudo -u postgres psql -c "ALTER SYSTEM SET synchronous_standby_names = '\"18/main\"';"
sudo -u postgres psql -c "SELECT pg_reload_conf();"
验证当前参数:
sudo -u postgres psql -tAc "SHOW synchronous_standby_names;"
执行结果:
"18/main"
再次查看复制状态(pg-node1 上执行):
sudo -u postgres psql -x -d appdb -c "SELECT application_name, client_addr, sync_state FROM pg_stat_replication ORDER BY sync_state;"
执行结果:
-[ RECORD 1 ]---+-----------
application_name | 18/main
client_addr | 10.0.0.13
sync_state | potential
-[ RECORD 2 ]---+-----------
application_name | 18/main
client_addr | 10.0.0.12
sync_state | sync
💡
sync_state变为sync的从库(此处为 pg-node2)成为"同步从库"——主库只有收到它的提交确认才返回成功,保证该从库零丢失;另一个从库标记为potential,作为备选同步从库。把synchronous_standby_names置回空串即可恢复异步模式。
💡 补充
synchronous_commit:同步复制的可靠性还取决于synchronous_commit参数(默认on,建议保持)。on表示主库在返回提交成功前先把 WAL 落盘,并(在配置了同步从库时)等待同步从库确认;若误改为off,主库可能先返回成功、WAL 尚未干净落盘,一旦主库随后宕机仍有可能丢失最近的提交,破坏"同步零丢失"的语义。只有当业务对写入时延极敏感、允许丢最后几条提交时才考虑关掉。
七、故障切换实测(模拟主库宕机,提升从库)
用于演示流复制的高可用能力:主库故障 → 提升一个从库为新主库 → 其余从库重定向到新主库。
步骤 19. 模拟主库 pg-node1 宕机
在 pg-node1 上执行(真实环境可能是断网/宕机/停机维护):
sudo pg_ctlcluster 18 main stop
pg_lsclusters
执行结果:
Ver Cluster Port Status Owner Data directory Log file
18 main 5432 down postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log
步骤 20. 提升 pg-node2 为新主库
在 pg-node2 上执行提升(结束恢复态,成为可写主库):
sudo pg_ctlcluster 18 main promote
验证不再是恢复态、且数据完整(pg-node2 上执行):
sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();"
sudo -u postgres psql -d appdb -c "INSERT INTO users(name) VALUES ('dave');"
sudo -u postgres psql -d appdb -c "SELECT * FROM users ORDER BY id;"
执行结果:
f
INSERT 0 1
id | name | created
----+-------+-------------------------------
1 | alice | 2026-09-30 00:34:26.831547+08
2 | bob | 2026-09-30 00:34:26.831547+08
3 | carol | 2026-09-30 00:34:26.831547+08
34 | dave | 2026-09-30 00:35:24.143086+08
(4 rows)
💡
pg_is_in_recovery()返回f,pg-node2 已成为新主库,且数据完整、可正常写入(新增了 dave)。
步骤 21. 把 pg-node3 重定向到新主库 pg-node2
pg-node3 原先回连的是 pg-node1(已宕机),需把 primary_conninfo 里的主库改为主 pg-node2,重启后它就会续上复制。
在 pg-node3 上执行:
sudo sed -i "s/pg-node1/pg-node2/" /var/lib/postgresql/18/main/postgresql.auto.conf
sudo pg_ctlcluster 18 main restart
验证 pg-node3 已追平新主库数据(含 dave)并处于恢复态:
sudo -u postgres psql -tAc "SELECT pg_is_in_recovery();"
sudo -u postgres psql -d appdb -c "SELECT * FROM users ORDER BY id;"
执行结果:
t
id | name | created
----+-------+-------------------------------
1 | alice | 2026-09-30 00:34:26.831547+08
2 | bob | 2026-09-30 00:34:26.831547+08
3 | carol | 2026-09-30 00:34:26.831547+08
34 | dave | 2026-09-30 00:35:24.143086+08
(4 rows)
新主库 pg-node2 侧确认 pg-node3 已成为自己的从库:
sudo -u postgres psql -x -d appdb -c "SELECT application_name, client_addr, state, sync_state FROM pg_stat_replication;"
执行结果:
-[ RECORD 1 ]----+-----------
application_name | 18/main
client_addr | 10.0.0.13 -- pg-node3
state | streaming
sync_state | async
故障切换完成:pg-node2 接替为主库并继续对外提供读写在 pg-node3,pg-node1 修复后可重新以从库身份 pg_basebackup 回归集群。
八、常见问题
Q1:pg_basebackup 报 "target directory exists but is not empty"
原因:目标数据目录非空(初始集群未清空)。解决:先 pg_ctlcluster 18 main stop,再 rm -rf /var/lib/postgresql/18/main/* 后重跑备份。
Q2:从库启动后一直处于 activating 或连接不上主库
原因通常在 pg_hba.conf 未放行复制连接,或主库 wall_level 非 replica。排查:查看从库日志 /var/log/postgresql/postgresql-18-main.log 的 primary_conninfo 连接错误,确认主库 listen_addresses='*' 且复制账号口令正确。
Q3:从库为什么不能写
从库是物理恢复态(pg_is_in_recovery()=t),数据目录受保护,只读。这是读写分离的设计保证。
Q4:同步复制下主库写入变慢或卡住
同步复制要求至少一个同步从库确认,若从库故障/网络延迟会阻塞主库提交。业务对写入时延敏感时可改回异步(置空 synchronous_standby_names),或只对关键从库开启同步。
九、参考链接
- PostgreSQL 官方流复制文档:https://www.postgresql.org/docs/current/warm-standby.html
- pg_basebackup 手册:https://www.postgresql.org/docs/current/app-pgbasebackup.html
- PGDG apt 源:https://www.postgresql.org/download/linux/ubuntu/