大云海山数据库(He3DB)源码详解:主备复制-流复制
背景
He3DB 采用了先进的存储引擎和查询优化技术,能够快速处理大量数据和复杂查询。无论是 OLTP(在线事务处理)还是 OLAP(在线分析处理)场景,都能提供出色的性能表现。He3DB 具备完善的数据备份和恢复机制,能够在系统故障或数据损坏时快速恢复数据,确保业务的连续性。He3DB 支持水平扩展和垂直扩展,可以轻松应对不断增长的数据需求。He3DB 提供了严格的访问控制和数据加密功能,确保数据的安全性和隐私性。
流复制——概述
在数据库中,是一种数据库复制技术,它允许一主多从的数据库集簇实现实时数据复制和同步。
流复制——XLogInsert
XLogInsert
是用于插入预写日志(Write-Ahead Log,WAL)记录的关键函数。
- 检查前提条件
首先检查是否已经调用了XLogBeginInsert()
,如果没有调用,则通过elog(ERROR, "XLogBeginInsert was not called")
记录错误信息
检查传入的info
参数中的位掩码是否有效,只允许设置特定的位,否则通过elog(PANIC, "invalid xlog info mask %02X", info)
记录错误信息
XLogRecPtr
XLogInsert(RmgrId rmid, uint8 info)
{
XLogRecPtr EndPos;// 用于存储返回的记录末尾位置(LSN)
/* XLogBeginInsert() must have been called. */
/* 确保已经调用了XLogBeginInsert()。 */
if (!begininsert_called)
elog(ERROR, "XLogBeginInsert was not called");
/*
* The caller can set rmgr bits, XLR_SPECIAL_REL_UPDATE and
* XLR_CHECK_CONSISTENCY; the rest are reserved for use by me.
*/
/*
* 检查info参数中的位掩码是否有效。只允许设置RMID位、XLR_SPECIAL_REL_UPDATE和XLR_CHECK_CONSISTENCY。
*/
if ((info & ~(XLR_RMGR_INFO_MASK |
XLR_SPECIAL_REL_UPDATE |
XLR_CHECK_CONSISTENCY)) != 0)
elog(PANIC, "invalid xlog info mask %02X", info);// 如果设置了无效位,则记录恐慌
TRACE_POSTGRESQL_WAL_INSERT(rmid, info);// 跟踪WAL插入事件(宏或函数)
- 处理启动模式
如果处于启动模式(IsBootstrapProcessingMode()
为真)且资源管理器ID(rmid)
不是RM_XLOG_ID
,则重置插入状态(XLogResetInsertion()
),并返回一个模拟的记录指针(通常指向第一个检查点记录的开头)
/*
* 在启动模式(bootstrap)下,除了RM_XLOG_ID外,不实际记录任何内容;
* 返回一个模拟的记录指针。
*/
if (IsBootstrapProcessingMode() && rmid != RM_XLOG_ID)
{
XLogResetInsertion();// 重置插入状态
EndPos = SizeOfXLogLongPHD; /* start of 1st chkpt record */// 返回一个假定的位置,通常指向第一个检查点记录的开头
return EndPos;
}
- 正常插入流程
如果不是启动模式,则进入循环尝试插入记录。
在循环内部:
获取决定是否进行全页写的信息,这些值在获取锁之前可能会改变,但XLogInsertRecord
会在获得锁后重新检查它们。
组装XLOG
记录,包括收集所有之前通过XLogRegister*
函数注册的数据和缓冲区引用。
尝试将组装好的记录插入到XLOG
中,并处理全页写(如果需要)。如果插入失败(返回InvalidXLogRecPtr
),则继续循环尝试插入
插入成功后,重置插入状态,并返回记录末尾的LSN(EndPos)
do
{
// 局部变量声明,用于后续操作
XLogRecPtr RedoRecPtr;
bool doPageWrites;
bool topxid_included = false;
XLogRecPtr fpw_lsn;
XLogRecData *rdt;
int num_fpi = 0;
/*
* 获取决定是否需要全页写(full-page writes)的信息。
* 注意:这些值在获取锁之前可能会改变,但XLogInsertRecord会在获得锁后重新检查它们。
*/
GetFullPageWriteInfo(&RedoRecPtr, &doPageWrites);
/*
* 组装XLOG记录。这包括收集所有之前通过XLogRegister*函数注册的数据和缓冲区引用。
*/
rdt = XLogRecordAssemble(rmid, info, RedoRecPtr, doPageWrites,
&fpw_lsn, &num_fpi, &topxid_included);
/*
* 将组装好的记录插入到XLOG中,并处理全页写(如果需要)。
* 如果因为某些原因(如锁竞争)无法插入,则返回InvalidXLogRecPtr,并尝试再次插入。
*/
EndPos = XLogInsertRecord(rdt, fpw_lsn, curinsert_flags, num_fpi,
topxid_included);
} while (EndPos == InvalidXLogRecPtr);// 如果插入失败,则重试
XLogResetInsertion(); // 重置插入状态
return EndPos;// 返回记录末尾的LSN
}
函数调用栈
部分调用栈如下图所示
流复制——pg_physical_replication_slot_advance
pg_physical_replication_slot_advance
推进复制槽。
- 参数获取和初始化
函数接受一个参数XLogRecPtr moveto
,表示要推进到的WAL日志位置
声明变量startlsn
用于存储当前复制槽的起始WAL日志位置,初始化为MyReplicationSlot->data.restart_lsn
声明变量retlsn
用于存储返回的WAL日志位置,初始化为startlsn
static XLogRecPtr
pg_physical_replication_slot_advance(XLogRecPtr moveto)
{
XLogRecPtr startlsn = MyReplicationSlot->data.restart_lsn;
XLogRecPtr retlsn = startlsn;
- 参数检查
通过Assert(moveto!= InvalidXLogRecPtr)
断言传入的要推进到的位置不是无效的
Assert(moveto != InvalidXLogRecPtr);
- 检查并推进复制槽位置
如果startlsn
小于moveto
,表示可以推进复制槽位置
首先获取MyReplicationSlot
的互斥锁(SpinLockAcquire(&MyReplicationSlot->mutex)
)
将复制槽的restart_lsn
更新为moveto
,表示推进了复制槽的起始位置
将retlsn
更新为moveto
,作为要返回的WAL日志位置
释放互斥锁(SpinLockRelease(&MyReplicationSlot->mutex))
if (startlsn < moveto)
{
SpinLockAcquire(&MyReplicationSlot->mutex);
MyReplicationSlot->data.restart_lsn = moveto;
SpinLockRelease(&MyReplicationSlot->mutex);
retlsn = moveto;
- 标记复制槽为脏
调用ReplicationSlotMarkDirty
函数将复制槽标记为脏,这样在下次检查点时会将复制槽的信息写入磁盘,确保在干净关闭数据库后数据的一致性。即使在发生崩溃的情况下,推进的WAL日志位置可能会丢失,但这种方式可以保证在干净关闭后数据的一致性
ReplicationSlotMarkDirty();
返回结果
返回retlsn
,即推进后的WAL日志位置或原始位置(如果没有推进)
return retlsn;
}
函数调用栈
He3DB其余文章参考链接
海山数据库(He3DB)源码详解:He3DB-CLOG日志管理器函数之TransactionIdSetTreeStatus
海山数据库(He3DB)+AI(五):一种基于强化学习的数据库旋钮调优方法
海山数据库(He3DB)+AI(四):一种基于迁移学习的启发式数据库旋钮调优方法
海山数据库(He3DB)源码详解:海山PG 空闲空间映射表FSM
作者介绍
周雨慧 中移(苏州)软件技术有限公司 数据库内核开发工程师