🟢 第一阶段:握手与特权建立 (进度:0%)

目标:让主库知道“我不是来查数据的,我是来拷全库的”。

  • 💻 客户端 (pg_basebackup)
    • 发起数据库连接。但在底层连接报文里,悄悄塞入一个特权参数:replication=true
  • 🖥️ 服务端 (PostgreSQL 主库)
    • 主进程 (Postmaster) 收到连接,看到 replication=true
    • 拒绝分配普通的 SQL 工作进程,而是专属 fork() 出一个具有底层物理读取权限的特种进程:walsender(日志发送进程),由它全权接待客户端。

🟡 第二阶段:双线并发与打下锚点 (进度: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()原理,  这个工具的运行机制逻辑推演:等您坐沙发呢!

发表评论