pg_rman源码学习(2) ーー 备份函数

一、 备份流程

pg_rman 的主备份流程由 do_backup() 函数协调,该函数位于 backup.c 文件中 backup.c:821-1041 。

1. 流程图

2. 详细步骤

  • 参数验证:验证必需参数如 PGDATA、备份模式等 backup.c:842-862

  • 获取备份目录锁 :使用 catalog_lock() 防止并发备份 backup.c:912-922

  • 初始化备份状态:设置备份状态为 RUNNING,初始化时间戳和大小统计 backup.c:924-938

  • 备份数据库文件 :调用 do_backup_database() 执行主要备份 backup.c:969

  • 备份归档WAL :调用 do_backup_arclog() 备份已归档的WAL文件 backup.c:972

  • 备份服务器日志 :如果配置了 with_serverlog,调用 do_backup_srvlog() backup.c:975

  • 更新状态:将备份状态更新为 DONE backup.c:979-983

  • 清理旧文件:根据保留策略删除旧备份和日志文件 backup.c:1004-1012

  • 释放锁 :调用 catalog_unlock() 释放目录锁 backup.c:1037-1038

3. 分支流程

全量备份 vs 增量备份分支

do_backup_database() 函数中,根据备份模式选择不同的处理路径 backup.c:193-232 :

后面我们会继续学习

备库服务器备份分支

如果检测到是从备库备份,会执行特殊处理 backup.c:868-878 :

快照脚本分支

如果存在快照脚本,会使用快照方式进行备份 backup.c:241-343 :

错误处理分支

使用 pgut_atexit_push() 注册清理函数,确保在错误时正确清理 backup.c:960-966 :

复制代码
说明
  • 所有分支最终都会汇聚到主流程的状态更新和清理步骤 backup.c:979-1041
  • 增量备份需要同一时间线上的有效全量备份作为基准 backup.c:200-216
  • 从备库备份不支持快照脚本方式 backup.c:289-301
  • 备份过程中会生成 backup_labeltablespace_map 文件用于恢复 backup.c:269-272

二、 主备份函数 do_backup()

1. 函数签名和参数处理

do_backup() 函数接收 pgBackupOption 结构体参数,包含所有备份配置选项 backup.c:821-837 :

cpp 复制代码
int
do_backup(pgBackupOption bkupopt)
{
	parray *backup_list;
	parray *files_database;
	parray *files_arclog;
	parray *files_srvlog;
	int    ret;
	char   path[MAXPGPATH];

	/* repack the necessary options */
	int	keep_arclog_files = bkupopt.keep_arclog_files;
	int	keep_arclog_days  = bkupopt.keep_arclog_days;
	int	keep_srvlog_files = bkupopt.keep_srvlog_files;
	int	keep_srvlog_days  = bkupopt.keep_srvlog_days;
	int	keep_data_generations = bkupopt.keep_data_generations;
	int	keep_data_days        = bkupopt.keep_data_days;
参数 类型 默认值 作用对象
keep_arclog_files int KEEP_INFINITE 归档WAL文件数量
keep_arclog_days int KEEP_INFINITE 归档WAL保留天数
keep_srvlog_files int KEEP_INFINITE 服务器日志文件数量
keep_srvlog_days int KEEP_INFINITE 服务器日志保留天数
keep_data_generations int KEEP_INFINITE 数据备份保留代数
keep_data_days int KEEP_INFINITE 数据备份保留天数

2. 初始化和验证阶段

必需参数验证
  • PGDATA 路径必须指定 backup.c:842-845
  • BACKUP_MODE 必须有效 backup.c:847-850
  • 归档WAL备份需要 ARCLOG_PATH backup.c:853-856
  • 服务器日志备份需要 SRVLOG_PATH backup.c:859-862
cpp 复制代码
	/* PGDATA and BACKUP_MODE are always required */
	if (pgdata == NULL)
		ereport(ERROR,
			(errcode(ERROR_ARGS),
			 errmsg("required parameter not specified: PGDATA (-D, --pgdata)")));
 
	if (current.backup_mode == BACKUP_MODE_INVALID)
		ereport(ERROR,
			(errcode(ERROR_ARGS),
			 errmsg("required parameter not specified: BACKUP_MODE (-b, --backup-mode)")));

	/* ARCLOG_PATH is required only when backup archive WAL */
	if (HAVE_ARCLOG(&current) && arclog_path == NULL)
		ereport(ERROR,
			(errcode(ERROR_ARGS),
			 errmsg("required parameter not specified: ARCLOG_PATH (-A, --arclog-path)")));

	/* SRVLOG_PATH is required only when backup serverlog */
	if (current.with_serverlog && srvlog_path == NULL)
		ereport(ERROR,
			(errcode(ERROR_ARGS),
			 errmsg("required parameter not specified: SRVLOG_PATH (-S, --srvlog-path)")));
备库检测

通过检查 recovery.confstandby.signal 文件判断是否从备用服务器备份 backup.c:868-878 。如果是备用服务器,必须提供主机和端口信息。

cpp 复制代码
	if (get_standby_signal_filepath(path, sizeof(path)))
	{
		if (!bkupopt.standby_host || !bkupopt.standby_port)
			ereport(ERROR,
				(errcode(ERROR_SYSTEM),
				 errmsg("please specify both standby host and port")));

		current.is_from_standby = true;
	}
	else
		current.is_from_standby = false;
压缩支持检查

检查 zlib 支持并在不可用时发出警告 backup.c:880-888 。

cpp 复制代码
#ifndef HAVE_LIBZ
	if (current.compress_data)
	{
		ereport(WARNING,
			(errmsg("this pg_rman build does not support compression"),
			 errhint("Please build PostgreSQL with zlib to use compression.")));
		current.compress_data = false;
	}
#endif
控制文件和系统标识符验证

读取 PostgreSQL 控制文件获取 WAL 段大小,验证系统标识符匹配 backup.c:890-900 。

cpp 复制代码
	controlFile = get_controlfile(pgdata, &crc_ok);

	if (!crc_ok)
		ereport(WARNING,
				(errmsg("control file appears to be corrupt"),
				 errdetail("Calculated CRC checksum does not match value stored in file.")));
	wal_segment_size = controlFile->xlog_seg_size;
	pg_free(controlFile);

	/* Check that we're working with the correct database cluster */
	check_system_identifier();

3. 备份执行阶段

目录锁和状态初始化

获取备份目录的排他锁,防止并发备份 backup.c:912-923 。初始化备份状态为 BACKUP_STATUS_RUNNING backup.c:925-939 。

cpp 复制代码
	/* get exclusive lock of backup catalog */
	ret = catalog_lock();
	if (ret == -1)
		ereport(ERROR,
			(errcode(ERROR_SYSTEM),
			 errmsg("could not lock backup catalog")));
	else if (ret == 1)
		ereport(ERROR,
			(errcode(ERROR_ALREADY_RUNNING),
			 errmsg("could not lock backup catalog"),
			 errdetail("Another pg_rman is just running. Skip this backup.")));

	/* initialize backup result */
	current.status = BACKUP_STATUS_RUNNING;
	current.tli = 0;		/* get from result of pg_backup_start() */
	current.start_lsn = current.stop_lsn = (XLogRecPtr) 0;
	current.start_time = time(NULL);
	current.end_time = (time_t) 0;
	current.total_data_bytes = BYTES_INVALID;
	current.read_data_bytes = BYTES_INVALID;
	current.read_arclog_bytes = BYTES_INVALID;
	current.read_srvlog_bytes = BYTES_INVALID;
	current.write_bytes = 0;		/* write_bytes is valid always */
	current.block_size = BLCKSZ;
	current.wal_block_size = XLOG_BLCKSZ;
	current.recovery_xid = 0;
	current.recovery_time = (time_t) 0;
备份目录创建

创建备份目录结构并写入初始的 backup.ini 文件 backup.c:941-948 。

cpp 复制代码
	/* create backup directory and backup.ini */
	if (!check)
	{
		if (pgBackupCreateDir(&current))
			ereport(ERROR,
				(errcode(ERROR_SYSTEM),
				 errmsg("could not create backup directory")));
		pgBackupWriteIni(&current);
	}

	elog(DEBUG, "destination directories of backup are initialized");
错误处理机制注册

使用 pgut_atexit_push() 注册清理函数 backup_cleanup() backup.c:960-966 。

cpp 复制代码
	/* set the error processing function for the backup process */
	pgut_atexit_push(backup_cleanup, NULL);

	/*
	 * Signal for backup_cleanup() that there may actually be some cleanup
	 * for it to do from this point on.
	 */
	in_backup = true;
三阶段备份执行

按顺序执行三种备份:

  • 数据库文件备份:do_backup_database(backup_list, bkupopt) backup.c:969
  • 归档WAL备份:do_backup_arclog(backup_list) backup.c:972
  • 服务器日志备份:do_backup_srvlog(backup_list) backup.c:975
cpp 复制代码
	/* backup data */
	files_database = do_backup_database(backup_list, bkupopt);

	/* backup archived WAL */
	files_arclog = do_backup_arclog(backup_list);

	/* backup serverlog */
	files_srvlog = do_backup_srvlog(backup_list);

4. 清理和完成阶段

状态更新

备份完成后更新状态为 BACKUP_STATUS_DONE 并记录结束时间 backup.c:980-983 。

cpp 复制代码
	/* update backup status to DONE */
	current.end_time = time(NULL);
	current.status = BACKUP_STATUS_DONE;
	if (!check)
		pgBackupWriteIni(&current);
旧文件清理

根据保留策略删除旧的归档WAL和服务器日志文件 backup.c:1004-1009 。

cpp 复制代码
	/*
	 * Delete old files (archived WAL and serverlog) after update of status.
	 */
	if (HAVE_ARCLOG(&current))
		delete_old_files(arclog_path, files_arclog, keep_arclog_files,
			keep_arclog_days, true);
	if (current.with_serverlog)
		delete_old_files(srvlog_path, files_srvlog, keep_srvlog_files,
			keep_srvlog_days, false);
备份清理

删除旧的数据备份文件 backup.c:1012

cpp 复制代码
	/* Delete old backup files after all backup operation. */
	pgBackupDelete(keep_data_generations, keep_data_days);

三、 do_backup_database() 函数

函数签名和初始化

复制代码
static parray *do_backup_database(parray *backup_list, pgBackupOption bkupopt)

函数接收现有备份列表和备份选项,返回备份的文件列表 backup.c:80-93 。

前置检查和准备

1. 服务器版本检查

调用 check_server_version() 验证 PostgreSQL 版本兼容性 backup.c:94 。

2. 数据校验和初始化

调用 init_data_checksum_enabled() 从控制文件读取数据校验和设置 backup.c:96 。

3. 备份模式验证

检查当前备份模式是否需要数据库备份 backup.c:98-127 :

  • 对于归档WAL备份,检查是否存在有效的全量备份
  • 如果没有有效备份且设置了 full_backup_on_error,自动转为全量备份

备份执行流程

1. PostgreSQL 备份启动

调用 pg_backup_start() 通知 PostgreSQL 开始备份 backup.c:136-138 :

  • 生成包含时间戳的备份标签
  • 执行平滑检查点(如果配置)
  • 获取备份起始 LSN
2. 备用服务器重启点

如果从备用服务器备份,执行重启点确保数据一致性 backup.c:141-151 。

3. 目录结构生成

生成 mkdirs.sh 脚本用于恢复时重建目录结构 backup.c:154-178 。

4. 增量备份基准查找

对于增量备份,查找上次验证的备份作为基准 backup.c:185-232 :

  • 获取基准备份的文件列表
  • 设置 LSN 基准用于增量过滤

文件备份实现

1. 快照脚本检查

检查是否存在快照脚本,决定使用快照备份还是文件复制备份 backup.c:241-243 。

2. 普通文件复制备份

如果没有快照脚本:

  • 列出 PGDATA 中的所有文件 backup.c:252
  • 根据备份模式记录调试信息 backup.c:254-257
  • 调用 backup_files() 执行实际文件复制 backup.c:263
3. 快照备份

如果存在快照脚本:

  • 验证不支持从备用服务器进行快照备份 backup.c:289-301
  • 执行表空间快照和挂载操作
  • 处理快照卷中的文件

备份完成和清理

1. PostgreSQL 备份停止

调用 pg_backup_stop() 结束备份模式并获取备份标签文件 backup.c:269 。

2. 文件列表生成

创建 file_database.txt 记录所有备份的文件 backup.c:278 。

关键实现细节

错误处理机制

  • 使用 pgut_atexit_push()/pgut_atexit_pop() 确保异常时正确清理
  • backup_cleanup() 函数处理备份失败时的清理工作
  • 严格的参数验证和状态检查

并发控制

  • 通过目录锁防止多个备份同时运行
  • 备份状态跟踪确保操作原子性

性能优化

  • 增量备份只复制变更的文件
  • 压缩选项减少存储空间
  • 进度显示支持大备份的监控

四、 三种备份的细节

三种备份的判断条件

1. 数据库文件备份判断条件

do_backup_database() 的执行条件基于 HAVE_DATABASE(&current) 宏 backup.c:98-99 :

复制代码
复制代码
#define HAVE_DATABASE(backup) ((backup)->backup_mode >= BACKUP_MODE_INCREMENTAL)
  • 全量备份 (BACKUP_MODE_FULL):始终执行
  • 增量备份 (BACKUP_MODE_INCREMENTAL):需要存在已验证的基准备份
  • 归档WAL备份 (BACKUP_MODE_ARCHIVE):仅在没有有效全量备份时执行

2. 归档WAL备份判断条件

do_backup_arclog() 的执行条件基于 HAVE_ARCLOG(&current) 宏 backup.c:616-617 :

复制代码
#define HAVE_ARCLOG(backup) ((backup)->backup_mode >= BACKUP_MODE_ARCHIVE)
  • 仅在备份模式为 BACKUP_MODE_ARCHIVE 或更高时执行
  • 需要配置 arclog_path 参数 backup.c:853-856

3. 服务器日志备份判断条件

do_backup_srvlog() 的执行条件基于 current.with_serverlog 标志 backup.c:748-749 :

  • 仅在用户明确指定 -s/--with-serverlog 选项时执行
  • 需要配置 srvlog_path 参数 backup.c:859-862

实现细节对比

数据库文件备份实现

核心流程

  1. 调用 pg_backup_start() 通知 PostgreSQL 开始备份 backup.c:136-138
  2. 生成目录结构脚本 mkdirs.sh backup.c:154-178
  3. 查找增量备份基准(如需要) backup.c:193-232
  4. 执行文件备份(支持快照和普通复制) backup.c:241-263
  5. 调用 pg_backup_stop() 结束备份 backup.c:269

特殊处理

  • 支持从备用服务器备份,需要执行重启点 backup.c:141-151
  • 增量备份依赖 LSN 比较和文件修改时间 backup.c:224-230

归档WAL备份实现

核心流程

  1. 检查备份模式,不满足条件直接返回 backup.c:616-617
  2. 如果数据库未备份,调用 pg_switch_wal() 切换WAL backup.c:629-630
  3. 查找上次归档备份作为增量基准 backup.c:636-645
  4. 列出并过滤WAL文件(基于时间戳) backup.c:647-669
  5. 备份WAL文件到 arclog/ 目录 backup.c:671-674
  6. 特殊处理时间线历史文件 backup.c:709-721

关键特点

  • 基于WAL文件名的时间戳过滤,只备份到 stop_lsn 为止的文件
  • 独立于数据库备份,可以单独执行

服务器日志备份实现

核心流程

  1. 检查 with_serverlog 标志 backup.c:748-749
  2. 查找上次服务器日志备份作为增量基准 backup.c:764-773
  3. 列出服务器日志文件 backup.c:775-777
  4. 备份文件到 srvlog/ 目录 backup.c:779-780

关键特点

  • 最简单的备份类型,仅复制日志文件
  • 不需要与PostgreSQL服务器交互
  • 支持基于文件修改时间的增量备份

代码实现的主要区别

特性 数据库文件备份 归档WAL备份 服务器日志备份
入口函数 do_backup_database() backup.c:80-93 do_backup_arclog() backup.c:602-603 do_backup_srvlog() backup.c:736-737
判断条件 backup_mode >= BACKUP_MODE_INCREMENTAL backup_mode >= BACKUP_MODE_ARCHIVE with_serverlog == true
PostgreSQL交互 需要(pg_backup_start/stop) 需要(pg_switch_wal) 不需要
增量基准 基于LSN和文件列表 基于文件列表 基于文件列表
特殊处理 备用服务器重启点、快照支持 WAL切换、时间线历史
目标目录 database/ arclog/ srvlog/

执行顺序和依赖关系

do_backup() 主函数中,三种备份按固定顺序执行:

  1. 数据库文件备份:首先执行,为其他备份提供时间基准
  2. 归档WAL备份 :其次执行,依赖数据库备份的 stop_lsn
  3. 服务器日志备份:最后执行,独立于前两者

这种顺序确保了备份的一致性和完整性,数据库备份提供的LSN信息用于限制WAL备份的范围。

Notes

  1. 三种备份可以独立配置,但数据库备份是其他备份的基础
  2. 归档WAL备份和服务器日志备份都支持增量模式,但实现机制不同
  3. 数据库备份是最复杂的,涉及与PostgreSQL的深度集成
  4. 每种备份都有独立的文件列表和元数据管理机制

未完待续

参考:https://deepwiki.com/ossc-db/pg_rman/4.1-backup-operations#main-backup-flow

相关推荐
Hehuyi_In3 天前
pg_rman源码学习(1) ーー 学习路线 与 整体架构
postgresql·源码学习·pg_rman
源文雨1 个月前
PVE实现USB硬盘盒在备份前自动上电/结束后自动断电脚本
linux·运维·服务器·备份·perl·pve·usb硬盘盒
问道飞鱼1 个月前
【数据库知识】MySQL 数据库备份与还原详细解读
数据库·mysql·备份·还原
小时候的阳光1 个月前
使用Docker版Percona Xtrabackup备份恢复MySQL8.0.x
mysql·docker·备份·xtrabackup
siriuuus3 个月前
MySQL 数据备份
数据库·mysql·备份
闲人编程3 个月前
自动化文件管理:分类、重命名和备份
python·microsoft·分类·自动化·备份·重命名·自动化文件分类
G果3 个月前
RDK X5 镜像备份(详细)
备份·镜像·rdk x5·地瓜机器人·d-robotics
奥尔特星云大使3 个月前
MySQL 备份基础(一)
数据库·sql·mysql·备份·mysql备份
虚伪的空想家4 个月前
生产环境K8S的etcd备份脚本
运维·容器·kubernetes·脚本·备份·etcd