🟢 第一阶段:握手与特权建立 (进度:0%)
目标:让主库知道“我不是来查数据的,我是来拷全库的”。
- 💻 客户端 (
pg_basebackup):- 发起数据库连接。但在底层连接报文里,悄悄塞入一个特权参数:
replication=true。
- 发起数据库连接。但在底层连接报文里,悄悄塞入一个特权参数:
- 🖥️ 服务端 (PostgreSQL 主库):
- 主进程 (Postmaster) 收到连接,看到
replication=true。 - 它拒绝分配普通的 SQL 工作进程,而是专属
fork()出一个具有底层物理读取权限的特种进程:walsender(日志发送进程),由它全权接待客户端。
- 主进程 (Postmaster) 收到连接,看到
🟡 第二阶段:双线并发与打下锚点 (进度:1%)
目标:建立双通道防日志覆盖,并在主库打下时空起跑线。
- 💻 客户端 (
pg_basebackup):- 因为你加了
-X stream参数,客户端立刻在本地裂变出第二个后台线程。 - 主线程:负责发号施令,发送
BASE_BACKUP备份指令。 - 副线程:立刻建立第二条独立的网络连接,专门死死盯住主库的 WAL 日志流。
- 因为你加了
- 🖥️ 服务端 (
walsender收到指令后):- 强刷脏页:向后台的 Checkpointer 发送紧急信号,强制将内存所有脏页刷入磁盘。
- 记录起点:在 WAL 日志中刻下当前的精确坐标(
START WAL LOCATION)。 - 开启护盾:修改全局内存变量,强制开启 FPI(全页镜像)。从这一秒起,前台业务的第一次修改必须把完整的 8KB 数据页记入日志,防止后续拷贝时发生页撕裂。
🔵 第三阶段:流水线搬运与伴飞 (进度:1% -> 99%)
目标:物理文件的极速传输,与时空录像带的同步录制。
在这个漫长的(可能是几个小时)阶段,两条网络通道同时在疯狂运转:
- 🖥️ 服务端 (
walsender– 数据通道):- 智能过滤:开始遍历主库的数据目录。遇到无用的垃圾目录(如
pg_stat_tmp内存统计)直接跳过。 - 内存打包:读到有效的数据文件块,不落盘,直接在内存中将其封装成
.tar格式的数据包。 - 网络倾泻:将打包好的
.tar流通过第一条 Socket 疯狂发送给客户端主线程。
- 智能过滤:开始遍历主库的数据目录。遇到无用的垃圾目录(如
- 💻 客户端 (双线程协同):
- 主线程:源源不断地接收
.tar数据流,解包并写入本地的base/等数据目录。 - 副线程:通过第二条 Socket,实时接收主库正在产生的、包含海量 FPI 镜像的 WAL 日志流,写入本地的
pg_wal/目录。(这就保证了长达数小时的备份期间,日志绝对不会因为主库空间不足而被覆盖删除)。
- 主线程:源源不断地接收
🟠 第四阶段:凌空注入与强行收口 (进度:99% -> 100%)
目标:植入复活基因,关闭时空敞口,确保日志绝对安全。
- 🖥️ 服务端 (
walsender– 数据发完后):- 生成基因锁:数据文件全发完了,它会在内存中动态生成
backup_label文件(里面写着第二阶段打下的起跑线坐标)。 - 尾部注入:将这个
backup_label作为数据流的最后一部分,直接发送给客户端。(注意:这个文件全程不接触主库的磁盘)。 - 刻下终点:在 WAL 日志中写入
BACKUP_END记录,宣告时空拓印结束。关闭 FPI 全页镜像状态。 - 死亡等待(最易卡死的一步):
walsender此时不返回成功。它死死盯着主库的归档进程 (archiver),必须确认从起点到终点产生的所有 WAL 日志,都已经被安全归档到远端,它才肯放行。
- 生成基因锁:数据文件全发完了,它会在内存中动态生成
- 💻 客户端:
- 收到
backup_label并落盘。此时这具克隆体拥有了复活所需的“基因指导手册”。
- 收到
🔴 第五阶段:防篡改与绝对落地 (备份结束)
目标:生成数字签名,逼迫操作系统将数据真正写死在硬盘上。
- 🖥️ 服务端 (
walsender):- 发送
backup_manifest文件(包含所有刚才发送的文件的 SHA-256 哈希校验码)。 - 断开连接,
walsender进程销毁。
- 发送
- 💻 客户端 (
pg_basebackup):- 接收完所有文件后,它绝对不信任操作系统的 Page Cache(页缓存)。
- 狂暴刷盘:它会对刚才下载的每一个数据文件,甚至每一个目录的描述符,依次调用
fsync()系统指令。逼迫操作系统把这几 TB 的数据从内存缓存里死死地钉进物理硬盘! - 大功告成:所有
fsync成功后,在终端打印base backup completed。进程完美退出。
一句话总结这条清爽的时间线:
客户端亮明身份获取特权 (T1) -> 服务端打下起点并开启全页镜像防撕裂 (T2) -> 服务端内存打包发数据,客户端双线程一边收数据一边收日志 (T3) -> 服务端凌空注入标签并死等归档结束 (T4) -> 客户端收尾,执行全盘 fsync 强制落盘 (T5)。
💣 暗器一:防网卡熔断的“物理限流器”(--max-rate)
- 纸上谈兵的灾难:在刚才的第三阶段,主库的
walsender在内存里打包好tar之后,会向网络疯狂倾泻数据。如果你不加限制,它会瞬间跑满千兆/万兆网卡!主库的出口带宽一旦被榨干,前台正常的业务 SQL 回包就会被严重堵塞,整个应用层会出现大面积超时(Timeout),业务直接瘫痪! - 架构师的保命微操:原厂 DBA 在敲下命令时,绝对、必须要加上
--max-rate(例如-r 100M)参数。 - 底层逻辑:这会逼迫服务端的
walsender进程在发包时进行自旋限速。宁可备份拷上 10 个小时,也绝不能让备份任务抢占前台高并发交易的一丝网络带宽!
🪢 暗器二:对抗网络闪断的“终极锁喉”(Replication Slot)
- 致命盲区:我们刚才说,客户端用了一个副线程(
-X stream)去实时拉取 WAL 日志,防止日志被主库循环覆盖。但是!如果拷到第 2 个小时,这根拉取日志的网线突然“闪断”了 1 分钟呢? - 连锁崩溃:就在这断网的 1 分钟里,主库的
checkpointer恰好执行了清理,把你急需的 WAL 给删了!等你网线连上,备份已经彻底报废。 - 架构师的保命微操:不仅要流式拉取,还要加上
-S slot_name(使用复制槽)! - 底层逻辑:当网络闪断时,这个“复制槽”会像一只铁手,死死卡住主库的脖子,绝对禁止主库删除任何还未发给备份机的 WAL 日志! 哪怕主库自己的磁盘被憋爆,它也不敢删。这为网络恢复争取了绝对的容错空间。
🗺️ 暗器三:表空间软链接的“异地迷宫”(Tablespace Mapping)
- 恐怖的异地复活失败:很多大厂的主库,为了性能,会把某些核心表通过
Tablespace放到一块专门的极速 NVMe 磁盘上(比如挂载在/mnt/fast_disk)。在 PG 内部,这表现为一个指向/mnt/fast_disk的软链接(Symlink)。 - 灾难现场:你拷出来的备份集里,原封不动地保留了这个软链接。当你把备份拿到灾备机房去恢复时,灾备机器上根本没有
/mnt/fast_disk这个目录!数据库启动瞬间直接报错崩溃。 - 架构师的保命微操:在执行备份时,必须使用
-T /old_path=/new_path(表空间映射)。 - 底层逻辑:客户端在解包落地时,会在底层拦截所有的软链接创建系统调用,动态将其重定向到灾备机房的真实磁盘路径下,确保克隆体能够在新环境中完美扎根!
🛡️ 底牌一:主库的绝对物理隔离(级联备份机制)
- 残酷现实:即使你加了
--max-rate限速,5TB 的数据读取依然会消耗主库极其宝贵的磁盘 IOPS 和 CPU 算力。在双十一这种核心大促期间,原厂架构师绝对不允许任何人去碰主库的一根汗毛! - 架构师的终极微操(从备库抽血):
pg_basebackup极其逆天的一点在于,你完全可以通过连接字符串,连到一个“只读备库(Standby)”上去做全量备份! - 底层逻辑:只要备库开启了
hot_standby = on,它就能像主库一样,响应walsender的底层协议,把自己的物理文件和正在接收的 WAL 日志打包发给你。这叫“备份算力 100% 物理卸载”! 主库在前面岁月静好地跑着 5 万 TPS,备库在后面默默地把血抽给备份机,主库连感知都感知不到!
🗜️ 底牌二:网络与 CPU 的生死博弈(PG 15+ 的 ZSTD 降维压缩)
- 物理瓶颈:5TB 的数据,就算你有万兆网卡,纯裸传也需要极长的时间。老旧的
-z(gzip) 压缩虽然省带宽,但会把服务器的 CPU 单核瞬间打到 100%,导致备份一样极其缓慢。 - 现代工业级利器(
-Z zstd):在 PostgreSQL 15 之后,内核全面引入了工业界神级的 Zstandard (ZSTD) 压缩算法! - 双向战术选择:
--compress=server-zstd:如果备份机网络带宽极差,但主库/备库 CPU 富余,让服务端用 zstd 极速压缩后再发,网络传输量瞬间降维到 1TB。--compress=client-zstd:如果主库 CPU 极其紧张,那就让它发裸流,让客户端(备份机)的 CPU 去承担高强度的压缩计算!- 定性:不懂压缩算法的代际演进,就无法在“CPU 算力”和“网络带宽”之间找到那条完美的黄金平衡线。
⏱️ 底牌三:暴力落地时的“时间陷阱”(--no-sync 的双刃剑)
- 恐怖的收尾:我们上一节推演过,备份最后一步是执行暴力的
fsync狂飙。但如果你备份机挂载的是一块非常慢的机械硬盘(SATA HDD),这最后一下的全盘fsync可能会卡上整整 2 个小时! 让你以为程序死机了。 - 高工的权衡:如果你做这个备份,仅仅是为了快速搭建一个测试环境,或者挂载一个临时只读节点(就算断电坏了也无所谓,大不了重做)。
- 极限微操:你可以直接加上
--no-sync参数!pg_basebackup下载完文件后,直接甩手走人,绝不等慢吞吞的磁盘落盘,把一切交给操作系统的异步刷脏机制。备份时间瞬间缩短!(警告:如果是做核心灾备,打死也不能加这个参数!)
🚀 终极革命:跨越时代的“块级增量备份”(PG 17 降维打击)
- 工业界延续了十几年的绝对痛点:我们刚才推演的
pg_basebackup有一个极其致命的物理缺陷——它只能做全量备份!哪怕你的 5TB 数据库今天只修改了 1MB 的数据,你也必须老老实实地把这 5TB 重新拷一遍!为了解决这个问题,大厂过去只能依赖极其复杂的第三方外部工具(如 pgBackRest 或 Barman)。 - 社区的终极杀招:在最新的 PostgreSQL 17 中,官方内核终于引入了史诗级的更新:原生支持基于
pg_basebackup的块级增量备份(Incremental Backup)! - 底层微操推演(WAL Summary):
- 过去:要知道哪些 8KB 的数据块被改了,必须去扫描庞大的数据文件或完整解析海量的 WAL 日志,极其耗时。
- PG 17 革命:内核引入了一个全新的后台机制叫
WAL Summarizer(WAL 摘要器)。它会在后台默默提取 WAL 日志中的精华,生成极其轻量级的“摘要文件”。 - 物理动作:当你执行
pg_basebackup --incremental时,主库的walsender不再傻傻地扫描那 5TB 的全量文件。它会直接去读取那些轻量级的 WAL 摘要,瞬间在内存中定位出“自上次备份以来,到底有哪几个 8KB 的物理块发生了改变”。然后,它像手术刀一样,极其精准地只把这几个改变的 8KB 碎块打包通过网络发给你!
- 终极定性:原本需要 3 个小时、跑满万兆网卡的 5TB 备份,在增量模式下,可能只需要 5 秒钟、传输几 MB 就瞬间完成了!这彻底颠覆了 PostgreSQL 原生灾备架构的物理极限!
了解 www.876873.xyz 的更多信息
订阅后即可通过电子邮件收到最新文章。
pg_basebackup()原理, 这个工具的运行机制逻辑推演:等您坐沙发呢!