ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

PostgreSQL主从复制:不开归档也能同步?流复制原理与代价详解

2026/9/9 22:36:47 拓冰建站 浏览量
PostgreSQL主从复制:不开归档也能同步?流复制原理与代价详解 这个问题我印象很深重庆这边不少做数据库运维的朋友都问过我PostgreSQL 做主从复制主库不开归档日志到底能不能同步直接说结论能而且完全不影响流复制本身。很多人把“归档”和“复制”当成一回事其实它们是两个完全独立的环节。你开不开归档都不影响 WAL 日志通过流复制协议发给备库。但反过来讲如果你只做流复制、完全关掉归档你就要接受一个后果你的灾难恢复能力和备库重建能力会被削弱。所以这个问题的完整答案不是简单的“能”或“不能”而是“能不能”后面还跟着一串“但是”。这篇就把它彻底讲透。先说个背景我这边平时主要做重庆及周边企业的 PostgreSQL、Oracle 数据库运维和架构咨询主从复制是几乎每个生产环境都要碰的东西。每次有客户拿着这个问题来问我基本能猜到他们脑子里想的是 MySQL 那一套逻辑——因为 MySQL 的主从复制确实和 binlog 归档日志强绑定不开 binlog 就做不了复制。但 PG 不是这么设计的PG 里复制依赖的是 WAL预写日志的实时传输归档只是 WAL 的一个额外备份出口。把这个机制搞清楚了后面所有参数配置、排查思路就都顺了。1. 主从复制和归档到底是什么关系1.1 两套机制两个出口PostgreSQL 的 WAL 日志从生成到最后销毁中间有两条路可以走第一条路是流复制通道。主库的 WAL sender 进程把日志段直接通过网络发送给备库的 walreceiver 进程备库拿到日志后不断重放到自己的数据目录里。这个过程是实时的和归档没有半点关系。第二条路是归档通道。主库的 archive 进程如果设置了archive_mode on把已经写满的 WAL 段文件复制一份丢到归档目录里这个目录可以本地的也可以是 NFS、对象存储之类的。归档的作用是留底目的是为了将来做 PITR时间点恢复或者用归档日志把某个备库补到最新状态。所以你看流复制用的是“传输中”的日志归档用的是“已经写完并切走”的日志。你关掉归档只是切断了第二条路第一条路照常跑。为了把这个讲得更直白我打个比方。WAL 日志就像店里每天记流水账的账本主库每做一笔交易就先往账本上写一笔。归档是什么是你每天打烊之后把写完的账本复印一份存到保险柜里万一哪天店里着火了你还能靠保险柜里的复印件把账目恢复出来。流复制是什么是你旁边开了一家分店备库你每记一笔就立刻把这一笔抄送一份给分店分店照着誊写一份。所以你复印不复印归档开不开都不影响你往分店抄送流复制。这个比喻能帮你理解一个核心观点归档是给“过去”留后路流复制是让“未来”有实时副本。两个东西服务的场景不同不是同一个功能。1.2 为什么总有人把两者混在一起这个问题出现频率高我分析下来主要有两个原因。原因一MySQL 的思维惯性。MySQL 主从复制里的 binlog 是“一专多能”——它既是归档/备份的依据也是复制传输的数据源。你让一个 MySQL DBA 来配 PG他潜意识里会觉得“不开日志归档怎么做复制”但其实 PG 早在 9.0 版本就把复制独立出来了流复制不需要等 WAL 归档到某个目录再拉取而是直接内存级、实时地推送。原因二PG 的某些高可用方案比如 Patroni 搭配 WAL 归档会同时要求开归档。在实际生产里很多架构师为了稳妥会同时开启archive_mode和archive_command这就让新手误以为归档是复制的“前置条件”。但实际上那只是高可用方案里的“增强配置”不是“必要条件”。所以你现在再遇到别人说“PG 主从必须开归档”你可以这样回答他流复制本身不需要归档但你要做完善的备份策略、要能在主库数据目录损坏时做时间点恢复归档就几乎是必须的。开不开归档取决于你的业务对 RPO/RTO 的要求不取决于你想不想做主从。2. PG 流复制的核心流程与关键角色2.1 WAL 日志的产生、发送与重放要理解为什么不归档也能同步就需要看看一条 WAL 日志从生成到应用完整走过了哪些步骤。事务提交时主库把变更记录写入 WAL buffer然后刷到 pg_wal 目录下的 WAL segment 文件里。每个 segment 默认 16MB写满之后会切换到下一个编号的 segment。这时候主库上已经配置好的max_wal_senders会允许若干个 WAL sender 进程存在。每个备库连接上来之后会有一个专门的 WAL sender 进程负责按照备库请求的 LSN日志序列号位置把 WAL 数据从 pg_wal 目录或者内存缓存里读出来通过网络发给备库。备库这边walreceiver 进程负责接收这些数据并把它们写进备库自己的 pg_wal 目录。然后备库的 startup 进程会按照 WAL 记录逐个重放把变更应用到数据文件里。这一切都是异步的、流水线式的备库永远在追主库的尾巴但正常情况下差距只有几百毫秒甚至更小。这里面有个关键点WAL sender 在发送日志时读的是主库 pg_wal 目录里的文件不是归档目录。哪怕主库完全没开归档只要 WAL 文件还在 pg_wal 目录里sender 就有数据可发。所以“能不能同步”这个问题真正应该问的是主库的 pg_wal 目录里备库需要的那个 WAL 段还在不在如果备库因为网络原因落后太多需要读一个已经被主库回收掉的旧 WAL 段而你又没开归档那备库就追不上了。这就是“不开归档能同步”这句话的前提限制后面我会详细讲。2.2 复制槽防止 WAL 被过早回收的保险既然主库 pg_wal 里的 WAL 文件会被循环复用或回收那怎么保证备库需要的老日志不被删掉答案有两个一是调大wal_keep_size在 pg_wal 目录里多保留一段时间二是用复制槽replication slot。我一般建议生产环境都开复制槽。复制槽的作用是主库会记住每个备库当前消费到的 WAL 位置只要备库还没消费完主库就不会删除对应的 WAL 日志。这相当于给每个备库发了一个“请勿删除”的标签。但复制槽也有一个副作用——如果你长期忘记清理备库或者备库下线了复制槽会一直把 WAL 日志留在主库磁盘上直到把磁盘撑爆。所以用复制槽必须配合监控备库下线时要及时pg_drop_replication_slot。这里再多提一句PG 12 之前备库的复制槽是靠standby.signal创建之前手工指定的PG 12 以后可以直接用pg_create_physical_replication_slot配合primary_slot_name来管理物理复制槽方便不少。2.3 归档在恢复路径里到底起什么作用那归档到底有什么用最核心的用处是 PITR。假设你做了全量备份比如 pg_basebackup 的基础备份然后开启了归档。当你需要把数据库恢复到某个历史时间点时流程是用全量备份把数据文件恢复到那一刻然后把归档日志从那个时间点开始用restore_command一步步重放到目标时间点。归档日志在这里就充当了“时间线拼接器”。如果不开归档你只能把数据库恢复到“全量备份的时刻”之后所有的事务变更都无法重演。哪怕你有流复制备库备库也只能反映主库的“当前状态”不能把主库“回放”到任意历史时间点。所以你看归档和流复制服务的恢复场景是不同的流复制对应的是“主库宕机备库顶上”归档对应的是“数据误操作、逻辑损坏、需要时间点回退”。生产系统要想真正睡得着觉两个都要有。3. 实操过程主库不开归档部署一套流复制3.1 实验环境准备为了演示我建议你在本地或者测试机上用两个实例做实验。下面是我常用的环境主库192.168.1.10端口 5432数据目录 /data/pgdata备库192.168.1.11端口 5432数据目录 /data/pgdataPG 版本我用的是 PostgreSQL 16但流程在 PG 12 都是通用的你可以直接用 YUM/APT 装好 PG或者用编译安装这里不再赘述。关键是主库安装时不要设置archive_mode我演示的正是“主库不开归档”的场景。3.2 主库参数修改哪些必须改哪些不用动首先编辑主库的 postgresql.conflisten_addresses * port 5432 wal_level replica max_wal_senders 10 wal_keep_size 512MB max_replication_slots 10逐项解释一下listen_addresses允许远程连接。wal_level replica这是流复制必须的PG 15 之前叫hot_standbyPG 15 之后统一叫replica。如果设成minimalPG 15 前是archive或minimal备库连不上因为物理复制需要 WAL 里带完整的数据页信息。max_wal_senders允许同时存在多少个 WAL sender 进程。一个备库至少占一个建议设 10 左右留点余量。wal_keep_size告诉主库在 pg_wal 目录里至少保留多少 MB 的 WAL 文件。我设 512MB防止备库短时间断网时的日志被清理。PG 13 之前这个参数叫wal_keep_segments单位是 WAL 段数量。换算的话1 个段 16MB所以 512MB 相当于 32 个段。max_replication_slots物理复制槽的数量上限不建槽的话也可以不设但生产我建议建。不需要改的archive_mode保持offarchive_command留空。这正是本次实验的核心。改完之后重启主库pg_ctl restart -D /data/pgdata3.3 创建复制用户和 pg_hba.conf 授权流复制需要一个具备REPLICATION权限的数据库用户CREATE ROLE repl LOGIN REPLICATION PASSWORD repl_password;然后编辑 pg_hba.conf增加如下行# 允许 repl 用户从备库 IP 进行复制连接 host replication repl 192.168.1.11/32 md5这里要特别提醒replication 连接走的是复制协议不是普通数据库连接所以一定要写在replication那一类里。有的新手把它写在all后面或者只写了host all all ...结果备库就是连不上报FATAL: no pg_hba.conf entry for replication connection。所以记住必须是host replication。改完 pg_hba.conf 之后 reload 一下pg_ctl reload -D /data/pgdata3.4 用 pg_basebackup 做基础备份并启动备库在主库上执行备份把数据文件完整拉到备库pg_basebackup -h 192.168.1.10 -p 5432 -U repl -D /data/pgdata -Fp -Xs -R -P参数解释-Fp按文件格式备份不是 tar 包。-Xs备份过程中同时把 WAL 日志流式传过来确保备库得到一个一致性的快照点。-R自动生成备库的standby.signal文件和postgresql.auto.conf里的primary_conninfo这一步非常省事。-P显示进度。执行完后检查备库的 /data/pgdata 下应该有两个关键文件standby.signal告诉 PG 启动时以备库standby模式启动。postgresql.auto.conf包含primary_conninfo和primary_slot_name如果你指定了。然后启动备库pg_ctl start -D /data/pgdata如果你用的是 PG 11 及以前的版本备库需要在 recovery.conf 里写standby_mode on和primary_conninfoPG 12 之后统一改成了standby.signal机制。你只要记住PG 12 的备库身份靠 standby.signal 标记不是靠某个参数开关。3.5 验证同步状态在主库上执行SELECT client_addr, state, sync_state, sent_lsn, replay_lsn FROM pg_stat_replication;正常情况下你会看到一行client_addr | state | sync_state | sent_lsn | replay_lsn ----------------------------------------------------------- 192.168.1.11 | streaming | async | 0/3002E10 | 0/3002E10state streaming说明流复制已经通了sync_state async说明是异步复制主库不会等备库确认才提交事务这也是默认情况。到这里你已经成功在主库完全没有开启归档的前提下部署了一套可以实时同步的 PG 主从架构。从这个实验可以看出主库开不开归档对“流复制通道是否建立”没有影响。4. 同步复制与异步复制的差异及监控要点4.1 异步是默认但你要知道怎么切同步上面实验里看到sync_state async这是 PG 默认的异步复制。意思是主库提交一个事务时只要本地 WAL 刷盘成功就返回给客户端不等备库收到并重放。这个模式的优点是主库性能几乎不受影响缺点是备库可能会有延迟主库突然宕机时可能会丢一部分已在主库提交但尚未传到备库的事务。如果你业务要求更高的数据安全等级可以把复制模式改成同步复制。做法是在主库的 postgresql.conf 里设置synchronous_standby_names FIRST 1 (standby1) synchronous_commit on这里standby1是你备库的application_name。所谓 FIRST 1意思是排在最前面的 1 个备库确认后主库的提交才算完成。改成同步之后你再看pg_stat_replicationsync_state会变成sync并且主库的提交延迟会因为要等备库确认而变高特别是两台机器物理距离远的时候延迟会比较明显。所以同步复制不是银弹要结合实际网络条件来选。我个人的建议同机房、同城双机场景可以开同步复制换取更高的数据安全跨地域、网络不稳定的场景老老实实用异步否则 CPU 和负载都会因为等确认而变得很难看。4.2 常见监控 SQL与延迟排查做 DBA 的不能光把复制搭起来就不管了。我一般会在监控系统里放几条 SQL定时跑。-- 查看备库 replay 延迟字节差 SELECT pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn) AS replay_delay_bytes, pg_wal_lsn_diff(pg_current_wal_lsn(), flush_lsn) AS flush_delay_bytes, client_addr, state, sync_state FROM pg_stat_replication;-- 查看备库是否长时间没有心跳 SELECT client_addr, state, now() - backend_start AS session_age, now() - state_change AS state_change_age FROM pg_stat_replication;如果 replay_delay_bytes 持续增大说明备库重放速度跟不上可能是备库磁盘 IO 慢或者备库上有大的查询在抢资源。如果state长时间不是 streaming而是 catchup说明备库在追赶日志你要关注网络和主库的 WAL 保留策略。另外要提醒的是pg_stat_replication里查到的write_lsn、flush_lsn、replay_lsn分别代表备库收到、刷盘、重放的 WAL 位置。异步模式下这三个位置和主库的pg_current_wal_lsn()的差距就是最直观的同步延迟指标。4.3 备库只读别手滑在上面写数据备库在 standby 模式下是只读的但很多初学者不理解这个“只读”是物理层面的不是权限层面的。你在备库上执行CREATE TABLE、INSERT等写操作会直接报错ERROR: cannot execute INSERT in a read-only transaction但有几种例外要注意临时表在备库上是允许建的因为它只对当前会话可见不会影响物理数据一致性pg_stat_statements之类的扩展也可能在备库上写入统计信息这些不影响复制。建议生产环境在备库上把default_transaction_read_only设为on从会话级别就杜绝误操作。5. 主库不开归档的代价哪些场景会踩坑5.1 备库落后太多WAL 被回收就追不上了这是不开归档最典型的坑。举个例子你的主库今天凌晨 2 点因为大批量导入WAL 写得特别多而备库因为某种原因停掉了半天。等你下午发现备库不可用重启备库想接着同步时备库会要求主库提供它最后一次收到的 WAL 位置之后的日志。但主库的 pg_wal 目录里早就把那些日志回收了假设你没开足够大的wal_keep_size也没建复制槽归档又是关闭状态怎么办备库会一直卡在那里报错核心日志是FATAL: requested WAL segment 00000001000000000000002A has already been removed这就是为什么我在 3.2 节强调wal_keep_size或者复制槽至少要配一个的原因。但如果你两者都没配又不开归档那你只能重新做一次pg_basebackup来重建备库而不能“续传”。所以如果你明确知道自己的环境里备库可能长期离线或者 WAL 产生速度很快不开归档的风险就非常大。开归档相当于给备库加了一道保险网即使主库的 WAL 被清了备库还能从归档目录里拉取历史日志补上。5.2 无法做 PITR误操作只能认栽这个前面已经说过了再给个实际例子。某天业务人员执行了DELETE FROM orders WHERE status obsolete忘了加AND create_time 2024-01-01结果把所有历史订单全删了。这时候你的第一反应是什么用 PITR 把数据库恢复到一个小时前让业务先恢复再导出数据。但如果你没开归档你手里只有一个昨天的全量备份恢复出来也是昨天的数据这一个小时内新增的订单就全丢了。很多没开归档的 PG 环境出事之后唯一的补救手段是拿备库在误操作发生前的一个时间点快照通过物理手段或工具而这个手段其实是非常有限的。所以每次客户跟我说“我们就是不想开归档觉得占磁盘”我都会问他一句如果你误删了数据你能接受丢多久如果答案是“一天都不能丢”那就老老实实开归档做备份策略。5.3 想用 pg_rewind 恢复旧主库时更麻烦级联故障、主备切换后旧主库如果要重新加入集群通常用pg_rewind快速增量同步数据避免重新全量复制。pg_rewind需要从新主库拉取旧主库落后期间产生的 WAL 日志而这些日志从哪来如果新主库的 pg_wal 里还留着那没问题如果已经被回收了而你开过归档新主库可以去归档目录取没开归档的话pg_rewind很可能失败。这也是为什么不少高可用方案的文档里会强制要求开归档——因为它依赖归档来度过日志缺失的空窗期。5.4 什么时候你可以放心不开归档说完坏处也得说说什么场景可以不开归档。一是纯测试环境你只是要验证流复制功能不在乎数据丢失或者能随时重建数据。二是短生命周期集群比如你临时搭一套环境做数据分析用完就删不需要灾备和历史恢复。三是你的备份方案根本不以事务日志为基础比如你每天夜里定时用pg_dump逻辑备份接受最多丢一天数据而且业务数据没有严格的一致性要求。这种情况下不开归档也可以接受因为逻辑备份本身不依赖 WAL。但只要是核心业务生产环境我的态度一直很明确开归档不是可选项是必选项。它和主从复制不冲突而且互相配合最好的姿势是流复制保证“主库挂了有备用”归档保证“数据坏了能回滚”两者合在一起才是完整的数据库容灾。6. 常见问题与排查技巧实录6.1 备库一直显示 “catchup”而不是 “streaming”怎么办备库启动后状态一般会短暂经历catchup然后变成streaming。如果一直停在catchup说明备库正在追赶主库的日志且差距较大。排查步骤看主库pg_stat_replication里replay_lsn和pg_current_wal_lsn()差距有多大。看备库 IO 和 CPU如果备库是机械盘重放开 16MB 的 WAL 文件速度受限追赶慢是正常的等一会儿就好。看备库日志里有没有反复出现的 WAL 文件缺失错误如果有详见 6.2。多数情况下开着流复制不管它过一段时间备库会自然追上。如果你设置了复制槽主库会保证需要的 WAL 不被清理所以不用太慌但也要关注追赶时间是否在业务可接受范围内。6.2 备库日志报 “requested WAL segment has already been removed”这个错误上过线的人应该都不陌生。原因就是备库需要的 WAL 段在主库的 pg_wal 里已经不存在且归档关闭/归档目录也找不到。处理办法只有一个重建备库用pg_basebackup重新拉一份全量数据再启动流复制。这就是不自救的后果——你要依赖重新做一次 pg_basebackup 的时间窗口期间业务没有跨机房的备用节点。防止策略开复制槽CREATE SLOT之后主库会“钉住”备库的消费位置。开归档万一 pg_wal 里没有可以从归档拉取。调大wal_keep_size适合备库数量少、WAL 产生速度可预估的环境。我个人最推荐组合是开复制槽 开归档。两条腿走路哪一个都不能少。6.3 备库启动报 “FATAL: no pg_hba.conf entry for replication connection”百分之九十的情况是 pg_hba.conf 配置漏了replication关键字。比如host all repl 192.168.1.11/32 md5这种写法只允许普通数据库连接不允许复制连接。复制连接需要这样写host replication repl 192.168.1.11/32 md5改完记得 reload。另外还有一种情况主库上的复制用户没有REPLICATION权限。你在 pg_hba.conf 里面写了replication但用户的角色没有这个属性备库连接时可能报FATAL: permission denied for replication解决办法是给用户赋值权限ALTER ROLE repl WITH REPLICATION;这种权限类问题通常多看两眼日志就能定位备库那端报错往往不是第一现场你要去主库的日志里找根因。6.4 同步复制模式下备库挂了把主库也拖住了有些同学把synchronous_standby_names配上去之后发现备库一断主库的写入也 hang 住了客户端报错ERROR: canceling statement due to conflict with recovery或者主库直接不commit。原因很简单同步复制要求备库确认备库不可用时主库会一直等待事务提交被无限期挂起。如果你能接受“备库挂了主库也能继续写入”建议把synchronous_commit调成remote_apply之外的降级选项或者用DEFAULTsynchronous_standby_names保持默认的off/local让主库在备库不可用时自动降级成异步。更严格的做法是设置synchronous_standby_names时不要用FIRST 1的强绑定写法而是用某种带降级逻辑的HA方案来管理。生产环境里我一般建议同步复制只用于同机房、网络稳定的两台机器跨机房一定要慎用或者使用半同步方案比如用 Pgpool-II、Patroni 做管理。裸用同步复制跨机房网络抖动一次业务可能就抖一片那感觉比丢几秒数据还难受。7. 手工切换与回切流程验证你的复制真能救急光看statestreaming不算数你得真正演练过主备切换才知道你的环境在故障来临时能不能顶住。假设你要把备库提升为主库步骤如下在备库上执行SELECT pg_promote();或者旧版本用pg_ctl promote -D /data/pgdata执行完之后备库会立刻应用完已接收的日志然后把standby.signal文件删掉回放完最后一段 WAL 后开始接受读写。同时确认新主库的pg_stat_replication状态。如果旧主库还要重新加入集群可以在修复后用pg_rewind快速切换回来步骤大致是确保旧主库已停止。用新主库的pg_rewind --target-pgdata旧主库目录 --source-serverhost新主库ip port5432 userrepl dbnamepostgres把旧主库对齐到新主库的时间线。在旧主库目录里重新创建standby.signal再启动它作为新主库的备库。pg_rewind需要开启wal_log_hints或使用 checksum否则会报不支持。这个也是配置时容易漏的一个点很多新版本还默认关闭wal_log_hints所以如果你想做快速回切配置阶段就要把它打开wal_log_hints on如果没有这个参数pg_rewind会拒绝执行提示数据页缺少校验信息。这时候你只能老老实实重新pg_basebackup。实际上我见过不少团队在生产环境做主备切换演练过程比预想的坑多有的是因为关着归档备库在切换时没有完整日志导致丢数据有的是因为复制槽没建切换后新主库把旧主库需要的 WAL 直接删了旧主库回不来了。所以建议你在测试环境先把切换演练跑熟演练完再回切一轮确认整个生命周期都通才叫真的会了。8. 给正在做 PG 主从的人几点心得根据这些年的实战经验最后分享几个比较零碎但实用的心得。第一不要被“开不开归档”这个问题带偏。你真正要关心的是如果主库瞬间宕机你能不能在一个可接受的时间内恢复业务并尽可能少丢数据。不开归档的主从复制只是“看起来有容灾”本质上它的恢复能力是打折的。所以只要条件允许我都建议在生产环境把归档打开并且有意识地做定期的恢复演练别等事故来了才后悔。第二WAL 日志是 PG 的高可用命脉。无论是流复制、归档、还是 PITR都围绕 WAL 做文章。平时多花点时间弄懂 WAL 的生成、切换、回收机制比背一万个参数都管用。有时候排障排到最后就是看 WAL 有没有被清、备库能不能拿到对应的 WAL。再直白一点PG 的高可用问题十有八九就是 WAL 的问题。第三监控比配置更重要。主从复制配完之后一定要有告警盯着pg_stat_replication这个视图。延迟告警、连接断了告警、备库追上不告警这些都是最基本的。配置再完美没有监控故障来了你仍然是被动的。第四做切换演练时别在业务高峰期搞。选一个业务低峰窗口提前通知相关团队模拟主库故障演练备库接管、旧主库回切全流程。每季度甚至每月做一次你会发现流程里隐藏的坑不下三个。第五如果团队规模不大尽量用现成的开源高可用工具比如 Patroni 加上 etcd 或者 ZooKeeper把自动故障切换这件事交给成熟方案而不是自己写脚本。但用工具之前底层这些原生的原理一定要懂否则工具出了问题你都不知道怎么排查。最后再说回标题里的问题主库不开归档能进行同步吗能流复制真的不需要归档参与。但作为过来人我想提醒你的是如果你只追求“复制”这个动作那不开归档确实够用但如果你是做生产环境的请一定认真对待归档这件事。它不会破坏你原有的一主一备架构反而会在关键时刻救你一把。我见过太多“不开归档反正也没出事”的案例出事那天当事人都懊悔到不行。今天就分享到这里吧这套思路在 PG 12 到 PG 17 上都通用。有机会再单独聊聊 Patroni 的配置细节和故障切换源码层面的实现。