流复制与只读副本
High Contrast
Dark Mode
Light Mode
Sepia
Forest
1 min read218 words

流复制与只读副本

流复制(Streaming Replication)是 PostgreSQL 高可用的基础:主库(Primary)将 WAL 日志实时传输给副本(Standby),副本持续回放,保持与主库的数据同步。副本可以处理只读查询,分担主库压力。


流复制的工作原理

主库 (Primary)
├── 写入 WAL(预写式日志)
└── WAL Sender 进程 ────→ 网络传输 WAL ────→ 副本 (Standby)
├── WAL Receiver 接收
└── 回放 WAL,保持数据同步
应用读写分离:
写操作 → 主库
读操作 → 副本(只读,延迟通常 < 100ms)

配置流复制

第一步:主库配置

# postgresql.conf(主库)
wal_level = replica              # 必须:至少 replica 级别
max_wal_senders = 10             # 允许的 WAL sender 进程数
max_replication_slots = 10       # 复制槽数量(防止 WAL 被提前清理)
wal_keep_size = 1GB              # 保留的 WAL 大小(副本断连时的缓冲)
synchronous_commit = on          # 同步提交(默认,保证不丢数据)
# synchronous_commit = remote_write  # 确保副本已收到(稍慢但更安全)
# pg_hba.conf(主库):允许副本连接
# TYPE  DATABASE    USER          ADDRESS          METHOD
host    replication replicator    副本IP/32        scram-sha-256
-- 在主库创建复制用户
CREATE ROLE replicator WITH REPLICATION LOGIN PASSWORD 'strong_password';

第二步:创建副本

# 在副本服务器上,用 pg_basebackup 初始化副本
pg_basebackup \
--host=主库IP \
--username=replicator \
--pgdata=/var/lib/postgresql/16/main \
--format=plain \
--wal-method=stream \
--checkpoint=fast \
--progress \
--write-recovery-conf  # 自动写入 standby.signal 和连接信息

第三步:副本配置

# postgresql.conf(副本)
# 以下参数可以与主库不同,副本可以有自己的优化配置
hot_standby = on                 # 允许副本处理只读查询(必须)
max_standby_streaming_delay = 30s  # 副本落后超过此值时拒绝只读查询
# primary_conninfo 连接信息(由 --write-recovery-conf 自动生成)
# 或手动写入:
primary_conninfo = 'host=主库IP port=5432 user=replicator password=xxx application_name=standby1'
# 副本数据目录中会有 standby.signal 文件(PG 12+)
# 这个文件的存在标志着这是一个备用服务器
ls /var/lib/postgresql/16/main/standby.signal
# 启动副本
sudo systemctl start postgresql

监控复制状态

-- 在主库查看复制状态
SELECT
application_name,
client_addr,
state,                    -- streaming / catchup
sent_lsn,                 -- 已发送到的 WAL 位置
write_lsn,                -- 副本已写入的位置
flush_lsn,                -- 副本已刷盘的位置
replay_lsn,               -- 副本已回放的位置
write_lag,                -- 写入延迟
flush_lag,                -- 刷盘延迟
replay_lag,               -- 回放延迟(最关键)
sync_state                -- async / sync / quorum
FROM pg_stat_replication;
-- 在副本查看延迟
SELECT
now() - pg_last_xact_replay_timestamp() AS replication_lag;
-- 返回 0ms 或几十毫秒 = 正常
-- 超过 1 分钟 = 副本可能落后
-- 副本是否在回放中
SELECT pg_is_in_recovery();  -- 副本返回 true,主库返回 false

复制槽(Replication Slots)

-- 复制槽防止主库清理副本还未接收的 WAL
-- 适合:副本必须接收所有 WAL(如 CDC、逻辑复制)
-- 创建物理复制槽
SELECT pg_create_physical_replication_slot('standby1_slot');
-- 副本使用时在 primary_conninfo 中指定:
-- primary_slot_name = 'standby1_slot'
-- 查看复制槽状态
SELECT
slot_name,
active,
restart_lsn,
pg_size_pretty(
pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)
) AS lag_size
FROM pg_replication_slots;
-- ⚠️ 警告:如果副本长时间断连,复制槽会导致主库 WAL 无限积累
-- 建议设置监控:复制槽积压超过 10GB 时告警
-- 或设置 max_slot_wal_keep_size 限制最大积压

读写分离:应用层配置

# Python(使用 psycopg2),应用层读写分离
import psycopg2
from psycopg2 import pool
# 主库连接池(读写)
primary_pool = pool.ThreadedConnectionPool(
minconn=5, maxconn=20,
host="primary.db.internal",
database="myapp",
user="app_user",
password="password"
)
# 副本连接池(只读)
replica_pool = pool.ThreadedConnectionPool(
minconn=5, maxconn=40,  # 副本可以有更多连接(只读压力大)
host="replica.db.internal",
database="myapp",
user="app_user",
password="password",
options="-c default_transaction_read_only=on"  # 强制只读
)
def get_db(read_only=False):
if read_only:
return replica_pool.getconn()
return primary_pool.getconn()
# 使用示例
# 写操作
with get_db(read_only=False) as conn:
conn.execute("INSERT INTO orders ...")
# 读操作(走副本)
with get_db(read_only=True) as conn:
result = conn.execute("SELECT * FROM products ...")
# 或者使用 PgBouncer 的 read/write 分离配置(见第09章)
# 或者使用云数据库的 reader endpoint(RDS、Aurora 等)

同步复制 vs 异步复制

-- 异步复制(默认):
-- 主库提交后立即返回,不等待副本确认
-- 风险:主库故障时,副本可能丢失最新的几个事务
-- 优点:写入性能最高
-- 同步复制:
-- 主库提交后等待至少一个副本确认写入,才返回成功
-- 风险:副本故障会导致主库挂起!
-- 优点:零数据丢失
-- 开启同步复制(谨慎使用)
-- postgresql.conf(主库):
-- synchronous_standby_names = 'standby1'  -- 指定同步副本名
-- synchronous_standby_names = 'ANY 1 (standby1, standby2)'  -- 任意一个确认即可
-- 如果同步副本断连,主库写入会一直等待!
-- 解决:设置超时后降级为异步
-- synchronous_commit = remote_write  -- 副本写入内存即可(比 on 更快)

故障转移(手动提升副本为主库)

# 当主库故障时,手动提升副本为新主库
# 方法一(PG 12+):
pg_ctl promote -D /var/lib/postgresql/16/main
# 或:
touch /var/lib/postgresql/16/main/promote
# 方法二:
# 删除 standby.signal 文件,然后重启
rm /var/lib/postgresql/16/main/standby.signal
pg_ctl restart -D /var/lib/postgresql/16/main
# 验证提升成功
psql -c "SELECT pg_is_in_recovery();"
# 应返回 false(已升为主库)
# 更新应用配置,将写操作指向新主库
# ⚠️ 如果使用 Patroni,故障转移是自动的(见下一节)

下一节Patroni 自动故障转移——手动故障转移可能需要几分钟,Patroni 能在 30 秒内自动完成主从切换,是生产级 PostgreSQL 高可用的标准方案。