海山数据库(He3DB)源码详解:主备复制read_backup_label

46 阅读7分钟

海山数据库(He3DB)源码详解:主备复制read_backup_label

背景

He3DB 采用了先进的存储引擎和查询优化技术,能够快速处理大量数据和复杂查询。无论是 OLTP(在线事务处理)还是 OLAP(在线分析处理)场景,都能提供出色的性能表现。He3DB 具备完善的数据备份和恢复机制,能够在系统故障或数据损坏时快速恢复数据,确保业务的连续性。He3DB 支持水平扩展和垂直扩展,可以轻松应对不断增长的数据需求。He3DB 提供了严格的访问控制和数据加密功能,确保数据的安全性和隐私性。

本文基于He3DB,针对主备复制模块进行源码解读分享

主备复制

时间点恢复——概述

   在数据库主备复制(如 PostgreSQL 的流复制等场景)中,时间点恢复是一种数据库恢复技术,它允许将数据库恢复到指定的历史时间点。

时间点恢复——read_backup_label

read_backup_label函数主要用于读取和解析 BACKUP_LABEL_FILE 文件,初始化相关输出参数,包括检查点位置、时间线 ID 等,并根据文件内容设置一些标志,同时进行错误检查和文件关闭操作,最终返回是否成功读取并解析该文件的结果。 在这里插入图片描述

  1. 初始化输出参数*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;
  1. 尝试打开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 */
	}
  1. 读取并解析文件内容 读取并解析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 TIMELABEL行,并将它们作为调试信息记录 尝试读取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)));
	}
  1. 错误检查和文件关闭 检查文件操作是否遇到错误(使用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)));
  1. 返回成功 如果所有步骤都成功完成,则函数返回true,表示成功读取并解析了BACKUP_LABEL_FILE文件
	return true;
}

函数调用栈

在这里插入图片描述

He3DB其余文章参考链接

海山数据库(He3DB)源码详解:He3DB-CLOG日志管理器函数之TransactionIdSetTreeStatus

海山数据库(He3DB)+AI(五):一种基于强化学习的数据库旋钮调优方法

海山数据库(He3DB)+AI(四):一种基于迁移学习的启发式数据库旋钮调优方法

海山数据库(He3DB)源码解读:海山PG 词法、语法分析

海山数据库(He3DB)源码详解:海山PG 空闲空间映射表FSM

作者介绍

周雨慧 中移(苏州)软件技术有限公司 数据库内核开发工程师