PostgreSQL启动过程中的那些事十六:启动进程二
这节主要讨论启动进程到了 StartupXLOG 。根据情况,如果需要就排除系统故障引起的数据库不一致状态,做相应的 REDO 或 UNDO ,然后创建一个检查点,把所有共享内存磁盘缓冲和提交数据缓冲写并文件同步到磁盘、把检查点插入 xlog 文件、更新控制文件,使数据库达到一种状态,设置共享内存中 XLogCtl 、 ShmemVariableCache 等对象信息 ;如果不需要,就根据控制文件从 xlog 文件读取最后的检查点信息,设置共享内存中 XLogCtl 、 ShmemVariableCache 等对象信息;启动完 XLOG ,启动进程完成使命,自己做了了断, postmaster 进程根据子进程结束信号响应句柄继续。
目前没有看到数据文件里记录了检查点,难道这个没有???
3
先上个图
方法调用序列示意图
4
上StartupXLog 方法的处理流程示意图
StartupXLog 流程示意图
数据库在什么情况下需要恢复。如果出现事务故障、系统故障或者介质故障时,数据库需要恢复。出现诸如运算溢出、死锁、违反完整性约束等事务故障时数据库在运行时可以通过强制回滚自行处理。 系统故障是主存数据丢失,未完成事务有些数据已写到物理数据库,此时数据库启动时需要事务回滚( UNDO/rollback )恢复。已完成事务有些或全部数据未写到物理数据库,此时数据库启动时需要重做( REDO )已提交事务。介质故障,需要从以前的备份做专门恢复。
话说 到了XLogStartup 方法,在这个方法里最主要的是根据情况判断是否需要恢复。如果不需要恢复,处理比较简单;如果需要恢复,根据不同状况做相应恢复。这儿这个状况就是判断系统是否在上次关闭时出现了系统故障。
XLogStartup 方法,先读取控制文件pg_control 到ControlFileData 数据结构,再看是否有恢复命令文件recovery.con f (判断是否是归档模式,如果归档模式,需要恢复的话,就是归档恢复),读取内容并 设置 InArchiveRecovery = true , 并根据情况 , 如果是 hot standby 的从系统 , 设置 StandbyMode = true 。接着读时间线历史文件。 然后 把来自控制文件的恢复目标时间线 recoveryTargetTLI 和归档清楚命令 archive_cleanup_command 保存于共享内存中的 xlog 控制结构 XLogCtl 的相关成员里 以备其它进程查看。 接着 调用 read_backup_label 方法 , 看是否有备份标签文件 $PGDATA$/backup_label ,如果有, 从中取检查点记录位置赋给 checkPointLoc ,如果没有备份标签文件,把控制文件里的检查点位置赋给 checkPointLoc 。根据这个检查点位置指针 checkPointLoc 从 xlog 文件中读这个检查点的 xlog 记录解析到检查点对象 checkpoint 。根据其自身位置指针和其记录的下一个 xlog 记录位置指针,或者控制文件记录的 数据库是否非正常关闭状态或 ( ControlFile->state != DB_SHUTDOWNED ) 者有无 recover.conf 文件, 判断是否需要恢复。
如果不需要恢复,更新控制文件的 state 等于 DB_IN_PRODUCTION , time 等于系统当前时间。接着 设置共享内存里的 XLogCtl 的成员 Write.lastSegSwitchTime 为当前时间,根据控制文件初始化XLogCtl 的最后的检查点的 XID/epoch , 再初始化共享内存里缓存变量结构 ShmemVariableCache 的 latestCompletedXid 以备份事务 ID ( ShmemVariableCache->latestCompletedXid = ShmemVariableCache->nextXid ) 然后调用 RecoverPreparedTransactions() 扫描 pg_twophase 文件夹 重新为 已 准备 好 的事务加载共享内存状态。如果任何关键 GUC 参数改变了,在我们允许 backend 进程写 WAL 日志以前记录到日志。所有这些事搞定后,设置 xlogctl->SharedRecoveryInProgress = false 允许 backend 进程写 WAL 日志。然后退出启动进程,postmaster 进程响应子进程退出信号其它相关进程。
如果需要恢复。这儿要处理的是崩溃时刻未完成 事务已写入 物理数据库的事务(处理方法是 UNDO )和崩溃时刻已完成 事务未写入 物理数据库的事务(处理方法是 REDO )。
根据数据库的运行模式(有无起归档,有无 hot standby 。有 hot standby 时,其主系统恢复和启归档情况一样进行恢复,从系统单独处理),调用 ReadRecord 方法从不同地方读取 xlog 日志记录,调用 xlog 的资源管理器 xmgr 的相应资源的重放方法做恢复。恢复完成后剩余步骤和不需要恢复的情况一样,处理后续事宜。 然后退出启动进程,postmaster 进程响应子进程退出信号其它相关进程。略详细过程见“ XLogStartup 流程示意图”
相关主要结构见下面:
控制文件的结构 ControlFileData 及检查点结构 CheckPoint 参见《 PostgreSQL 存储系统一:控制文件存储结构》。
XLog 日志文件相关结构参见《 PostgreSQL 存储系统二: REDOLOG 文件存储结构》。
VariableCache 是共享内存里用来跟踪OID 和XID 分配状态的数据结构。由于历史原因,由不同的轻量锁LWLock 保护这个结构中不同的字段。
typedef struct VariableCacheData
{
/* 这些字段由 OidGenLock 锁保护 */
Oid nextOid ; /* next OID to assign */
uint32 oidCount ; /* OIDs available before must do XLOG work */
/* 这些字段由 XidGenLock 锁保护 */
TransactionId nextXid ; /* next XID to assign */
TransactionId oldestXid ; /* cluster-wide minimum datfrozenxid */
TransactionId xidVacLimit ; /* start forcing autovacuums here */
TransactionId xidWarnLimit ; /* start complaining here */
TransactionId xidStopLimit ; /* refuse to advance nextXid beyond here */
TransactionId xidWrapLimit ; /* where the world ends */
Oid oldestXidDB ; /* database with minimum datfrozenxid */
/* 这些字段由 ProcArrayLock 锁保护 */
TransactionId latestCompletedXid ; /* newest XID that has committed or
* aborted */
} VariableCacheData ;
typedef VariableCacheData * VariableCache ;
XLOG 的共享内存总状态
typedef struct XLogCtlData
{
/* 由 WALInsertLock 锁保护 */
XLogCtlInsert Insert;
/* 由 info_lck 锁保护 */
XLogwrtRqst LogwrtRqst;
XLogwrtResult LogwrtResult;
uint32 ckptXidEpoch; /* nextXID & epoch of latest checkpoint */
TransactionId ckptXid;
XLogRecPtr asyncXactLSN; /* LSN of newest async commit/abort */
uint32 lastRemovedLog; /* latest removed/recycled XLOG segment */
uint32 lastRemovedSeg;
/* 由 WALWriteLock 锁保护 */
XLogCtlWrite Write;
/* 尽管这些值可以变,但在启动后不再改变。是否可以读 / 写页面和块的值依赖于 WALInsertLock 和 WALWriteLock 锁 */
char *pages; /* buffers for unwritten XLOG pages */
XLogRecPtr *xlblocks; /* 1st byte ptr-s + XLOG_BLCKSZ */
int XLogCacheBlck; /* highest allocated xlog buffer index */
TimeLineID ThisTimeLineID;
TimeLineID RecoveryTargetTLI;
/* archiveCleanupCommand 是从 recovery.conf 文件里读的,但需要放在共享内存里以使 bgwriter 进程能访问它 */
char archiveCleanupCommand[MAXPGPATH];
/* SharedRecoveryInProgress 指明本进程是否正在做崩溃或归档恢复。由
info_lck 锁保护 */
bool SharedRecoveryInProgress;
/*
* SharedHotStandbyActive 指明本进程是否正在做崩溃或归档恢复。由
info_lck 锁保护 */
bool SharedHotStandbyActive;
/* 如果正在等 WAL 到达或者 failover 的触发器文件出现, recoveryWakeupLatch 用于唤醒启动将成继续重放 WAL 。 */
Latch recoveryWakeupLatch;
/* 在恢复期间,我们在这儿保存最后一个检查点的拷贝。当 bgwriter 想创建一个重启点 restartpoint 时由 bgwriter 进程使用。由 info_lck 锁保护。 */
XLogRecPtr lastCheckPointRecPtr;
CheckPoint lastCheckPoint;
/* 最后一个检查点或被重放的检查点的结束位置加 1 */
XLogRecPtr replayEndRecPtr;
/* 被重放的最后一个记录的结束位置加 1 */
XLogRecPtr recoveryLastRecPtr;
/* 最后被重放的 COMMIT/ABORT 记录的时间戳 */
TimestampTz recoveryLastXTime;
/* 是否请求暂停恢复 ? */
bool recoveryPause;
slock_t info_lck; /* locks shared variables shown above */
} XLogCtlData;
static XLogCtlData *XLogCtl = NULL;
/* XLogInsert 的共享状态数据结构 */
typedef struct XLogCtlInsert
{
XLogwrtResult LogwrtResult; /* a recent value of LogwrtResult */
XLogRecPtr PrevRecord; /* start of previously-inserted record */
int curridx; /* current block index in cache */
XLogPageHeader currpage; /* points to header of block in cache */
char *currpos; /* current insertion point in cache */
XLogRecPtr RedoRecPtr; /* current redo point for insertions */
bool forcePageWrites; /* forcing full-page writes for PITR? */
/* 如果在进程里备份由 pg_start_backup() 开始, exclusiveBackup 是 true ; nonExclusiveBackups 是计数器,指明进程里当前基于流备份的数目。当上面两个任一个非 0 时(即有上面的备份时), forcePageWrites 是 ture 。 lastBackupStart 是最后一个检查点的 redo 值(下一个 xlog 记录的位置指针),作为在线备份的起始点。 */
bool exclusiveBackup;
int nonExclusiveBackups;
XLogRecPtr lastBackupStart;
} XLogCtlInsert;
XLOG 控制的共享内存数据结构, LogwrtRqst 指出我们需要写 / 文件同步到日志的那个字节位置(在这个位置之前的所有记录必须被写或做文件同步)。 LogwrtResult 指出我们已经写 / 文件同步了的字节位置。
typedef struct XLogwrtRqst
{
XLogRecPtr Write; /* last byte + 1 to write out */
XLogRecPtr Flush; /* last byte + 1 to flush */
} XLogwrtRqst;
typedef struct XLogwrtResult
{
XLogRecPtr Write; /* last byte + 1 written out */
XLogRecPtr Flush; /* last byte + 1 flushed */
} XLogwrtResult;
指向 XLOG 里位置的指针。这个指针是 64 位,因为我们不想它有溢出的时候。
注意:用来指明一个无效的指针。这个没问题,因为我们在 XLOG 页头用了页头结构,因此 XLOG 记录不可能从页头开始。
注意:这儿容易引起理解错乱,这个 xlogid (对应实际 XLOG 文件名字的中间八位)表示逻辑 XLOG 日志文件 ID ,因为组成 XLOG 逻辑文件的实际物理文件远小于 4Gb 。组成对应这个 xlogid 的逻辑日志文件的每一个实际物理文件是一个 XLogSegSize 字节大小的“段”( "segment" ,段号是实际 XLOG 文件名字的后八位)。前面加上用八位表示的一个时间线 ID 、逻辑日志文件号和段号一起标识一个物理的 XLOG 日志文件(“段”)。段号和物理文件里的偏移量由 xrecoff/XLogSegSize 和 xrecoff%XLogSegSize 计算。
typedef struct XLogRecPtr
{
uint32 xlogid ; /* log file #, 0 based */
uint32 xrecoff ; /* byte offset of location in log file */
} XLogRecPtr ;
/* XLogWrite/XLogFlush 的共享内存里的状态数据结构 */
typedef struct XLogCtlWrite
{
XLogwrtResult LogwrtResult; /* current value of LogwrtResult */
int curridx; /* cache index of next block to write */
pg_time_t lastSegSwitchTime; /* time of last xlog segment switch */
} XLogCtlWrite;
/* 系统状态指示器。 */
typedef enum DBState
{
DB_STARTUP = 0,
DB_SHUTDOWNED ,
DB_SHUTDOWNED_IN_RECOVERY ,
DB_SHUTDOWNING ,
DB_IN_CRASH_RECOVERY ,
DB_IN_ARCHIVE_RECOVERY ,
DB_IN_PRODUCTION
} DBState ;
XLogStartup 流程示意图中的两个红色方框红色字的框是 XLOG 资源管理器 xmgr 的处理方法,这个 XLOG 的资源管理器内容较多,单列主题讨论。还有恢复完成后调用了方法 CreateCheckPoint ,创建一个检查点 以将所有的恢复数据写到磁盘 。
5 创建检查点
创建一个检查点,会将共享内存里的所有磁盘缓冲和提交日志缓冲刷出并文件同步到磁盘。
下面这些情况可能引起创建检查点,为了使用方便,把这些情况定义成如下标志,这些标志可以按位做或运算。检查点的起因不同,创建检查点的行为也略有不同。
#define CHECKPOINT_IS_SHUTDOWN 0x0001 /* Checkpoint is for shutdown */
#define CHECKPOINT_END_OF_RECOVERY 0x0002 /* Like shutdown checkpoint,
* but issued at end of WAL
* recovery */
#define CHECKPOINT_IMMEDIATE 0x0004 /* Do it without delays */
#define CHECKPOINT_FORCE 0x0008 /* Force even if no activity */
/* These are important to RequestCheckpoint */
#define CHECKPOINT_WAIT 0x0010 /* Wait for completion */
/* These indicate the cause of a checkpoint request */
#define CHECKPOINT_CAUSE_XLOG 0x0020 /* XLOG consumption */
#define CHECKPOINT_CAUSE_TIME 0x0040 /* Elapsed time */
创建检查点的基本过程是先让存储管理器 smgr (以后单列状态讨论)为检查点做好准备, 根据情况填充检查点结构的成员,CheckpointGuts方法把共享内存里的磁盘缓冲和提交日志缓冲输出到磁盘(即写数据文件)。接着调用XlogInsert把这个检查点插入xlog文件。然后更新控制文件相关成员。最后更新共享内存里XlogCtl的检查点相关成员和检查点的统计信息结构。相关结构定义和创建检查点流程示意图见下面。
/* 检查点统计信息 */
typedef struct CheckpointStatsData
{
TimestampTz ckpt_start_t ; /* start of checkpoint */
TimestampTz ckpt_write_t ; /* start of flushing buffers */
TimestampTz ckpt_sync_t ; /* start of fsyncs */
TimestampTz ckpt_sync_end_t ; /* end of fsyncs */
TimestampTz ckpt_end_t ; /* end of checkpoint */
int ckpt_bufs_written ; /* # of buffers written */
int ckpt_segs_added ; /* # of new xlog segments created */
int ckpt_segs_removed ; /* # of xlog segments deleted */
int ckpt_segs_recycled ; /* # of xlog segments recycled */
int ckpt_sync_rels ; /* # of relations synced */
uint64 ckpt_longest_sync ; /* Longest sync for one relation */
uint64 ckpt_agg_sync_time ; /* The sum of all the individual sync
* times, which is not necessarily the
* same as the total elapsed time for
* the entire sync phase. */
} CheckpointStatsData ;
当前检查点的统计信息收集在这个全局结构变量里。
CheckpointStatsData CheckpointStats ;
创建检查点流程示意图
上图中,其中 CheckPointGuts 方法的定义见下面,刷出所有共享内存中的数据到磁盘并做文件同步。方法定义见下面,把 clog 、 subtrans 、 multixact 、 predicate 、 relationmap 、 buffer (数据文件)和 twophase 相关数据统统刷和文件同步到磁盘。这儿先不深入讨论这个方法了。
static void
CheckPointGuts(XLogRecPtr checkPointRedo, int flags)
{
CheckPointCLOG();
CheckPointSUBTRANS();
CheckPointMultiXact();
CheckPointPredicate();
CheckPointRelationMap();
CheckPointBuffers(flags); /* performs all required fsyncs */
/* We deliberately delay 2PC checkpointing as long as possible */
CheckPointTwoPhase(checkPointRedo);
}
结果这么多逻辑严谨的一系列行为后,数据库达到了正常状态,启动进程寿终正寝。然后, postmaster 进程响应该子进程退出,分别依次 fork 出 bgwriter 进程、 walwriter 进程、 autovaclauncher 进程、 archiver 进程、 pgstat 进程,然后抛出一句 ” database system is ready to accept connections ” 。然后进入 serverloop ,等待客户端请求到达,启动 postgres 服务进程,开始履行使命。
Serverloop 还检查 bgwriter 进程、 walwriter 进程、 autovaclauncher 进程、 archiver 进程、 pgstat 进程,还有前面启动的系统日志进程 sysloger 这些辅助检查是否正常运行,如果没有,就重启这些进程。此时, pg 服务器端有 postmaster 进程和这六个辅助进程运行,准备好为客户端进程提供服务,提供的服务由 postgres 服务进程完成。
------------
转载请著明出处,来自博客:
blog.csdn.net/beiigang
beigang.iteye.com