# 海山数据库(He3DB)源码详解:主备复制read_backup_label
主备复制
时间点恢复——概述
在数据库主备复制(如 PostgreSQL 的流复制等场景)中,时间点恢复是一种数据库恢复技术,它允许将数据库恢复到指定的历史时间点。
时间点恢复——read_backup_label
read_backup_label函数主要用于读取和解析 BACKUP_LABEL_FILE 文件,初始化相关输出参数,包括检查点位置、时间线 ID 等,并根据文件内容设置一些标志,同时进行错误检查和文件关闭操作,最终返回是否成功读取并解析该文件的结果。

- 初始化输出参数
将*checkPointLoc设置为InvalidXLogRecPtr,表示初始时检查点位置
将*backupLabelTLI设置为0,表示初始时时间线ID
将*backupEndRequired和*backupFromStandby都设置为false,分别表示初始时不需要处理到WAL的末尾,且备份不是来自备用服务器
static bool
read_backup_label(XLogRecPtr *checkPointLoc, TimeLineID *backupLabelTLI,
bool *backupEndRequired, bool *backupFromStandby)
{
char startxlogfilename[MAXFNAMELEN];
TimeLineID tli_from_walseg,
tli_from_file;
FILE *lfp;
char ch;
char backuptype[20];
char backupfrom[20];
char backuplabel[MAXPGPATH];
char backuptime[128];
uint32 hi,
lo;
/* suppress possible uninitialized-variable warnings */
*checkPointLoc = InvalidXLogRecPtr; //给输出参数赋初始值来避免潜在的未初始化变量警告
*backupLabelTLI = 0;
*backupEndRequired = false;
*backupFromStandby = false;
- 尝试打开
BACKUP_LABEL_FILE文件
使用AllocateFile函数以读模式打开BACKUP_LABEL_FILE文件,并将文件指针存储在lfp中
如果文件不存在(errno == ENOENT),则函数返回false,表示没有找到文件但这不是一个错误
如果文件存在但无法打开(errno != ENOENT),则记录一个致命错误并退出
lfp = AllocateFile(BACKUP_LABEL_FILE, "r"); //尝试以读模式打开文件
if (!lfp)
{
if (errno != ENOENT)
// 如果错误码不是ENOENT(即文件不存在),则记录一个致命错误
// ereport是一个可能用于数据库系统(如PostgreSQL)的错误报告函数
// 它记录一个错误,可能包括错误码和错误消息
// errcode_for_file_access()可能是一个返回与文件访问相关错误码的函数
// errmsg是一个格式化字符串,用于生成错误消息,%m会被替换为系统错误消息
// %s会被替换为BACKUP_LABEL_FILE的值
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m",
BACKUP_LABEL_FILE)));
// 如果文件不存在(errno == ENOENT),或者我们已经处理了其他错误,则函数返回false
// 表示没有找到文件,但这在某些上下文中是可以接受的
return false; /* it's not there, all is fine */
}
- 读取并解析文件内容
读取并解析START WAL LOCATION行,以获取WAL的起始位置和对应的时间线ID(tli_from_walseg)
使用解析出的高32位(hi)和低32位(lo)构造64位的RedoStartLSN,并设置RedoStartTLI
读取并解析CHECKPOINT LOCATION行,以获取检查点的位置,并更新*checkPointLoc和*backupLabelTLI
尝试读取BACKUP METHOD行,如果备份方法是streamed,则将*backupEndRequired设置为true
尝试读取BACKUP FROM行,如果备份来自standby,则将*backupFromStandby设置为true
尝试读取START TIME和LABEL行,并将它们作为调试信息记录
尝试读取START TIMELINE行,并与通过WAL段获得的时间线ID进行比较,如果不匹配则记录致命错误;如果匹配,则记录调试信息
//格式应为:"START WAL LOCATION: %X/%X (file %08X%16s)%c",其中%X表示十六进制 整数,%s表示字符串,%c表示字符
if (fscanf(lfp, "START WAL LOCATION: %X/%X (file %08X%16s)%c",
&hi, &lo, &tli_from_walseg, startxlogfilename, &ch) != 5 || ch != '\n')
// 如果fscanf没有返回5(表示没有成功读取5个项),或者读取的最后一个字符不是换行符'\n'
// 则记录一个致命错误,错误码为ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE,错误消息包含文件名
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
// 使用读取的hi和lo构造64位的RedoStartLSN
RedoStartLSN = ((uint64) hi) << 32 | lo;
RedoStartTLI = tli_from_walseg;
// 尝试从文件中读取检查点位置的信息
// 格式应为:"CHECKPOINT LOCATION: %X/%X%c",其中%X表示十六进制整数,%c表示字符
if (fscanf(lfp, "CHECKPOINT LOCATION: %X/%X%c",
&hi, &lo, &ch) != 3 || ch != '\n')
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE)));
*checkPointLoc = ((uint64) hi) << 32 | lo;
*backupLabelTLI = tli_from_walseg;
if (fscanf(lfp, "BACKUP METHOD: %19s\n", backuptype) == 1)
{
/*
* 尝试从文件中读取 "BACKUP METHOD:" 后面的字符串,并存储在 backuptype 中。
* 如果成功读取了一个字符串,则继续比较。
*/
if (strcmp(backuptype, "streamed") == 0)
/*
* 如果备份类型是 "streamed",则设置 backupEndRequired 为 true,
* 表示需要处理到WAL的末尾。
*/
*backupEndRequired = true;
}
/*
* BACKUP FROM lets us know if this was from a primary or a standby. If
* it was from a standby, we'll double-check that the control file state
* matches that of a standby.
*/
if (fscanf(lfp, "BACKUP FROM: %19s\n", backupfrom) == 1)
{
if (strcmp(backupfrom, "standby") == 0)
/*
* 如果备份来源是 "standby",则设置 backupFromStandby 为 true,
* 表示备份来自备用服务器。
*/
*backupFromStandby = true;
}
if (fscanf(lfp, "START TIME: %127[^\n]\n", backuptime) == 1)
ereport(DEBUG1,
(errmsg_internal("backup time %s in file \"%s\"",
backuptime, BACKUP_LABEL_FILE)));
if (fscanf(lfp, "LABEL: %1023[^\n]\n", backuplabel) == 1)
ereport(DEBUG1,
(errmsg_internal("backup label %s in file \"%s\"",
backuplabel, BACKUP_LABEL_FILE)));
/*
* START TIMELINE is new as of 11. Its parsing is not mandatory, still use
* it as a sanity check if present.
*/
// 尝试从文件中读取START TIMELINE字段,并将其存储在tli_from_file变量中
if (fscanf(lfp, "START TIMELINE: %u\n", &tli_from_file) == 1)
{
// 如果从文件中解析的时间线ID与通过WAL段获得的时间线ID不匹配
if (tli_from_walseg != tli_from_file)
ereport(FATAL,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("invalid data in file \"%s\"", BACKUP_LABEL_FILE),
errdetail("Timeline ID parsed is %u, but expected %u.",
tli_from_file, tli_from_walseg)));
// 如果时间线ID匹配,使用DEBUG1级别记录日志
ereport(DEBUG1,
(errmsg_internal("backup timeline %u in file \"%s\"",
tli_from_file, BACKUP_LABEL_FILE)));
}
- 错误检查和文件关闭
检查文件操作是否遇到错误(使用ferror(lfp))
尝试关闭文件(使用FreeFile(lfp)),并检查关闭操作是否遇到错误
如果在文件操作或关闭过程中遇到错误,则记录致命错误并退出
// 检查文件操作是否有错误,或者关闭文件时是否有错误
if (ferror(lfp) || FreeFile(lfp))
ereport(FATAL,
(errcode_for_file_access(),
errmsg("could not read file \"%s\": %m",
BACKUP_LABEL_FILE)));
- 返回成功
如果所有步骤都成功完成,则函数返回true,表示成功读取并解析了BACKUP_LABEL_FILE文件
return true;
}
函数调用栈

作者介绍
周雨慧 中移(苏州)软件技术有限公司 数据库内核开发工程师
成功完成,则函数返回true,表示成功读取并解析了BACKUP_LABEL_FILE文件
return true;
}
函数调用栈
[外链图片转存中…(img-14UHJ2iu-1731658229710)]
作者介绍
周雨慧 中移(苏州)软件技术有限公司 数据库内核开发工程师
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。




