苏童 发表于 2016-11-21 08:01:44

PostgreSQL启动过程中的那些事十六:启动进程三:CheckPointGuts刷出共享内存里所有数据

       话说启动进程调用StartupXLOG启动xlog,根据情况,如果需要就排除系统故障引起的数据库不一致状态,做相应的REDO或UNDO,然后创建一个检查点,把所有共享内存磁盘缓冲和提交数据缓冲写并文件同步到磁盘、把检查点插入xlog文件、更新控制文件,使数据库达到一种状态。
这节接着讨论启动进程在创建检查点时调用的CheckPointGuts方法(在创建重启点时也会调用这个方法)。CheckPointGuts方法功能是刷出所有共享内存中的数据到磁盘并做文件同步,共享内存中的数据包括clog、subtrans、multixact、predicate、relationmap、buffer(数据文件)和twophase相关数据。CheckPointGuts方法定义和“CheckPointGuts方法调用序列图”见下面。
 
static void
CheckPointGuts(XLogRecPtrcheckPointRedo, int flags)
{
    CheckPointCLOG();
    CheckPointSUBTRANS();
    CheckPointMultiXact();
    CheckPointPredicate();
    CheckPointRelationMap();
    CheckPointBuffers(flags);   /* performs all required fsyncs */
    /* We deliberately delay 2PC checkpointingas long as possible */
    CheckPointTwoPhase(checkPointRedo);
}
 

CheckPointGuts方法调用序列图
  
 
  
CheckPointGuts方法主要是通过调用提交事务日志管理器的方法CheckPointClog,子事务日志管理器的方法CheckPointSUBTRANS,多事务日志管理器的方法CheckPointMultiXact,支持序列化事务隔离级别的谓词锁模块的方法CheckPointPredicate,目录/系统表到文件节点映射模块的方法CheckPointRelationMap,缓存管理器的方法CheckPointBuffers,两阶段提交模块的方法CheckPointTwoPhase把共享内存里的数据刷出并文件同步到磁盘。
  
其中提交事务日志管理器的方法CheckPointClog、子事务日志管理器的方法CheckPointSUBTRANS、多事务日志管理器的方法CheckPointMultiXact、多事务日志管理器的方法CheckPointMultiXact、支持序列化事务隔离级别的谓词锁模块的方法CheckPointPredicate最后都调用了SLRU模块的SimpleLruFlush方法,把相关共享内存数据写到磁盘,并调用pg_fsync方法把相关内容文件同步到磁盘上对应文件。
  
在缓存管理器的方法CheckPointBuffers,两阶段提交模块的方法CheckPointTwoPhase里,因为没有使用SLRU算法,直接调用pg_fsync方法把相关内容文件同步到磁盘上对应文件。
  
在目录/系统表到文件节点映射模块的方法CheckPointRelationMap里,在释放RelationMappingLock时,会完成共享内存里相关系统表和对应物理文件映射的文件同步到磁盘工作。
  
我们看一下各种日志管理,日志对数据库是至关重要的一部分,出现系统故障时,数据库通过重放日志恢复数据,保证数据库一致性和完整性。
  
Pg里有XLOG、CLOG、SUBTRANS LOG、MultiXactID LOG四种事务日志,XLOG是事务日志,就是平时常说的REDOLOG,记录了事务操作数据库的过程信息和事务最终状态;CLOG是XLOG里事务的提交状态日志;SUBTRANS是子事务日志,为每一个事务存储父事务ID。这是嵌套事务实现的基础部分,SUBTRANS仅需要为当前打开的事务记住信息,没有必要在崩溃并重启后保留数据;MultiXactID是组合事务日志,由一组事务ID组成,是共享行锁shared-row-lock实现的基础部分,共享锁锁住的元组在其Xmax字段存储MultiXactId。各种日志都存放在对应的日志文件里。
  
有了文件就有了I/O,为了降低I/O开销,pg设置了各种日志的缓存区,由对应的日志管理器管理日志的写、文件同步和读等日志维护工作。Pg使用简单最近最少使用(SLRU)算法来管理事务日志。使用轻量锁LWLock的ControlLock锁保护整个缓冲区,其中的每个缓冲块(默认8K)还有一个LWLock锁保护,以控制并发操作。SLRU及事务日志的部分相关数据结构在下面。
  
为CLOG控制链接到共享内存数据结构
  
static SlruCtlData ClogCtlData;
  
#define ClogCtl (&ClogCtlData)
  
 
  
为SUBTRANS控制链接到共享内存数据结构
  
static SlruCtlData SubTransCtlData;
  
#define SubTransCtl (&SubTransCtlData)
  
 
  
为MultiXact控制链接到共享内存数据结构
  
  
static SlruCtlData MultiXactOffsetCtlData;
  
static SlruCtlData MultiXactMemberCtlData;
  
#define MultiXactOffsetCtl  (&MultiXactOffsetCtlData)
  
#define MultiXactMemberCtl  (&MultiXactMemberCtlData)
  
 
  
 
  
typedef SlruCtlData *SlruCtl;
  
 
  
/* SlruCtlData是指向共享内存里的活跃信息的非共享结构*/
  
typedef struct SlruCtlData
  
{
  
    SlruShared shared;
  
 
  
    /*这个标志告诉 是否文件同步写(pg_clog和multixact成员是true,pg_subtrans和pg_notify是false) */
  
    bool       do_fsync;
  
 
  
    /*为截断目的决定两个页号哪一个是更旧的。为了用 包裹 XID算法(with wraparoundXID arithmetic)做正确的事,这儿我们需要用事务ID比较 */
  
    bool       (*PagePrecedes) (int, int);
  
 
  
    /* 在SimpleLruInit期间目录被设置,并且从那以后不变。因为它总是相同的,它不必放到共享内存里。 */
  
    char       Dir;
  
} SlruCtlData;
  
 
  
共享内存状态
  
typedef struct SlruSharedData
  
{
  
    LWLockId   ControlLock;
  
 
  
    /* 由这个SLRU结构管理的缓存块号*/
  
    int        num_slots;
  
 
  
    /*持有每一个缓存槽信息的数组。当状态是EMPTY时 缓存页/块号是未定义的,当作
  
page_lru_count。 */
  
    char     **page_buffer;
  
    SlruPageStatus *page_status;
  
    bool     *page_dirty;
  
    int       *page_number;
  
    int       *page_lru_count;
  
    LWLockId   *buffer_locks;
  
 
  
    /* 在SLRU页/块里的相关条目的WAL刷出LSN的可选数组。如果不是0/NULL,在写缓存页/块前 我们必须刷出WAL(pg_clog是true,multixact、pg_subtrans、pg_notify是false)。Group_lsn[]每缓存页/块槽有lsn_groups_per_page条目,在这个槽的缓存页/块上 为SLRU条目的一个临近组 每一个缓存页/块槽 包含最高已知LSN。 */
  
    XLogRecPtr *group_lsn;
  
    int        lsn_groups_per_page;
  
 
  
    /*我们通过设置page_lru_count[slotno] = ++cur_lru_count标记页“最近使用”;最老旧页因此是有表达式cur_lru_count - page_lru_count[slotno]值最高/大的那一个。这个数事实上包裹,但这个计算仍然工作 和缓存页/块的年龄(超过了INT_MAX数)一样长。   */
  
    int        cur_lru_count;
  
 
  
    /* latest_page_number是当前日志结尾的页/块号;这不是严格的数据,因为我们仅用它避免包裹swapping出了最后的页/块。 */
  
    int        latest_page_number;
  
} SlruSharedData;
  
 
  
typedef SlruSharedData *SlruShared;
  
 
  
/*页状态代码。注意这不包含"dirty"位。仅在VALID或者WRIT_IN_PROGRESS状态里page_dirty能是true;在后面的例子/情况里 它暗示 从这次写开始后页又被搞脏*/
  
typedef enum
  
{
  
    SLRU_PAGE_EMPTY,         /* buffer is not in use */
  
    SLRU_PAGE_READ_IN_PROGRESS, /* page is beingread in */
  
    SLRU_PAGE_VALID,         /* page is valid and not being written */
  
    SLRU_PAGE_WRITE_IN_PROGRESS /* page is beingwritten out */
  
} SlruPageStatus;
  
 
  
 
  
SLRU算法的缓存区操作在下面,其中包括了本节多次调用的SimpleLruFlush方法,将缓存数据刷出并文件同步到磁盘。
  
extern Size SimpleLruShmemSize(int nslots, int nlsns);
  
extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
  
             LWLockId ctllock, const char *subdir);
  
extern int SimpleLruZeroPage(SlruCtl ctl, int pageno);
  
extern int SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
  
                TransactionId xid);
  
extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno,
  
                        TransactionId xid);
  
extern void SimpleLruWritePage(SlruCtl ctl, int slotno);
  
extern void SimpleLruFlush(SlruCtl ctl, bool checkpoint);
  
extern void SimpleLruTruncate(SlruCtl ctl, int cutoffPage);
  
extern bool SlruScanDirectory(SlruCtl ctl, int cutoffPage, bool doDeletions);
  
 
就到这儿吧。
 


------------
转载请著明出处,来自博客:
blog.csdn.net/beiigang
beigang.iyunv.com
页: [1]
查看完整版本: PostgreSQL启动过程中的那些事十六:启动进程三:CheckPointGuts刷出共享内存里所有数据