PG15 pg_basebackup 代码解析

背景

先前 PG 版本 pg_basebackup 的代码较为复杂,pg_basebackup 在备份过程中做了很多事情,但这部分代码逻辑没有完全解耦,导致一个文件里包含了很多功能的逻辑,影响了代码的可读性和可修改性。

因此,PG 15 针对这部分代码做了一个重构,在保证提供的功能没有发生变化的前提下,完成了模块间的解耦。

本文将针对重构这部分代码进行解析,建议先阅读下面 背景知识介绍 对应的文章。

整体架构

PG 15 的 pg_basebackup 部分采用 管道-过滤器 风格(数据流风格的一种),引入 bbstreamer 这个抽象结构,所有的过滤器都是 bbstreamer 的扩展。
举一个简单的例子,使用下述语句将 server 端的数据进行备份,并将输出重定向到标准输出。

pg_basebackup -h localhost -U postgres -p 5432  -Xf -Ft -D- 

那么,整个数据流的处理如下图所示
在这里插入图片描述
执行 pg_basebackup 后,server 端会发送若干个 tar 包给 pg_basebackup 对应的进程,对于每一个 tar 包,pg_basebackup 进程都将其视作数据流处理,并让数据流经过若干个过滤器进行处理:

  1. bbstreamer_tar_parser:负责解析 server 端发来的 tar 文件,并将处理后的数据流向下一个过滤器;
  2. bbstreamer_tar_archiver: 负责组装 tar 包操作,对数据进行进一步处理并将其流向下一个过滤器;
  3. bbstreamer_tar_file:负责最终的文件写入操作,将数据流写入指定的文件中。

代码分析

bbstreamer 结构分析

bbstreamer 结构的详细内容参考 bbstreamer.h 文件,里面解释非常全面。这里介绍两个主要结构体

struct bbstreamer
{
	const bbstreamer_ops *bbs_ops; /* 当前 bbstreamer 结构的函数指针 */
	bbstreamer *bbs_next; /* 指向下一个 bbstreamer 结构 */
	StringInfoData bbs_buffer; /* 临时 buffer */
};

struct bbstreamer_ops
{
	/* 函数指针:实现 bbstreamer 过滤器需要针对数据流的具体操作 */
	void		(*content) (bbstreamer *streamer, bbstreamer_member *member,
							const char *data, int len,
							bbstreamer_archive_context context);

	/* 函数指针:bbstreamer 清理函数 */
	void		(*finalize) (bbstreamer *streamer);
	void		(*free) (bbstreamer *streamer);
};

用面向对象的角度来看,bbstreamer 结构体可以看成基类,上文说的类似 bbstreamer_tar_parser 都可以看成它的子类,尤其扩展而来,并实现 bbstreamer_ops 里的三个函数。

处理流程

筛选出 pg_basebackup 中的关键函数后,整体的函数流程图如下所示

  1. 用户使用 pg_basebackup 后,经过初始化处理进入 BaseBackup 函数;
  2. 如果 server 端为 PG 15 版本,调用 ReceiveArchieveStream 函数接收数据流;
  3. ReceiveCopyData 函数循环接收数据,并调用回调函数 ReceiveArchieveStreamChunk 进行处理;
  4. ReceiveArchieveStreamChunk 函数负责一系列 bbstreamer 结构的初始化,以及使用 bbstreamer 结构体进行后续处理;
  5. 处理完毕后,针对各 streamer 进行清理操作;

对于第 4 步,我们还是使用之前的例子来做具体分析

pg_basebackup -h localhost -U postgres -p 5432  -Xf -Ft -D- 

其入口函数就是下面这个 bbstreamer_content 函数,该函数会调用当前 bbstreamer 对应实现的 content 函数。

/* Send some content to a bbstreamer. */
static inline void
bbstreamer_content(bbstreamer *streamer, bbstreamer_member *member,
				   const char *data, int len,
				   bbstreamer_archive_context context)
{
	Assert(streamer != NULL);
	streamer->bbs_ops->content(streamer, member, data, len, context);
}

对于上面的 pg_basebackup 命令,当到了最后一个过滤器 bbstreamer_plain_writer 时,会将内容写到 base.tar 中,此时的函数堆栈如下

(gdb) bt
#0  bbstreamer_plain_writer_content ()
    at bbstreamer_file.c:110
#1  0x0000000000409c94 in bbstreamer_content ()
    at bbstreamer.h:131
#2  0x000000000040a774 in bbstreamer_tar_archiver_content ()
    at bbstreamer_tar.c:437
#3  0x0000000000409c94 in bbstreamer_content ()
    at bbstreamer.h:131
#4  0x000000000040a47d in bbstreamer_tar_header ()
    at bbstreamer_tar.c:310
#5  0x0000000000409f5f in bbstreamer_tar_parser_content ()
    at bbstreamer_tar.c:142
#6  0x0000000000403607 in bbstreamer_content ()
    at bbstreamer.h:131
#7  0x0000000000405de8 in ReceiveArchiveStreamChunk ()
    at pg_basebackup.c:1449
    
下面省略若干行上层函数堆栈

可以看到,对于三个过滤器,每个过滤器执行完当前 bbstreamer 对应的函数后,都会继续调用下一个过滤器对应的 bbstreamer 函数,直到 bbstreamer_plain_writer_content 完成写 tar 文件,不再递归调用。

总结

PG 15 针对 pg_basebackup 这部分的重构,较好的完成了模块间的解耦。如果需要针对数据流做额外的修改操作,只需要按照其设计模式新增一个过滤器即可。例如不想直接写 tar 文件,想使用 gzip 额外进行压缩,只需要将最后的 bbstreamer_plain_writer 换成 bbstreamer_gzip_writer 即可。开发者在熟悉了这一架构后,可以非常快的进行开发。

参考资料

[1] http://mysql.taobao.org/monthly/2018/08/06/
[2] https://www.postgresql.org/docs/15/app-pgbasebackup.html
[3] https://github.com/postgres/postgres/commit/23a1c6578c87fca0e361c4f5f9a07df5ae1f9858

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

总想玩世不恭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值