2014年(20)
分类: Mysql/postgreSQL
2014-10-14 11:43:52
原文地址:MySQL源码分析(Log)—TC_LOG 作者:king_wangheng
目的
MySQL的日志子系统主要包括error log、binlog、general log三种类型的日志。TC_LOG数据结构及相关处理方法是日志子系统中为两阶段事务(2PC)提供的,并且在binlog中继承了该结构。本文主要对TC_LOG数据结构及主要处理方法进行分析,一方面为binlog分析处理做准备,另一方面进一步深入理解两阶段事务(2PC)日志的处理策略。
数据结构
TC_LOG数据结构的定义在源码文件的sql/log.h中,其中在TC_LOG派生出两个子类,TC_LOG_DUMMY是一个假的类,派生类TC_LOG_MMAP,通过mmap策略实现事务日志的策略。主要的类方法包括:open()打开日志文件、close()关闭日志文件、log_xid()记录2PC事务日志id、unlog()删除2PC事务日志id四个操作。TC_LOG及子类关系图如下所示:
图1 TC_LOG类关系图
首先,对子类TC_LOG_MMAP中的重要参数进行说明:filename是mmap的日志文件名;fd是文件描述符;file_length是日志文件的大小;npages是日志文件的页数,这是由于日志文件按照页进行组织;inited是一个流程状态参数,用于表明当前操作所处的状态;data是用于存储xid数据内容;pages、syncing、active、pool、pool_last是st_page结构体的指针,pages是st_page数据结构,用于组织数据页。日志页分为syncing、active、pool三种状态存在。pool_last指针指向pool队列中的最后一个元素;LOCK_active、LOCK_pool、LOCK_sync三个锁结构,用于并发控制访问日志;COND_pool和COND_active条件变量分别用于并发控制中,唤醒pool队列和active页访问。
在TC_LOG_MMAP中,一个重要的结构体就是st_page,使得日志按照页的方式进行组织和操作。定义如下所示:
typedef struct st_page { struct st_page *next; // page a linked in a fifo queue my_xid *start, *end; // usable area of a page my_xid *ptr; // next xid will be written here int size, free; // max and current number of free xid slots on the page int waiters; // number of waiters on condition PAGE_STATE state; // see above mysql_mutex_t lock; // to access page data or control structure mysql_cond_t cond; // to wait for a sync } PAGE; |
由以上定义可知,日志页按照队列的方式进行组织,next指针指向下一个日志页;start和end指针分别指向页的第一个和最后一个位置;ptr指向下一个记录xid的位置;size是日志页可以存储xid的数目;free是空闲的存储空间数目;waiters是当前页处于条件等待的操作数目;state是当前页的状态,主要包括PS_POOL、PS_ERROR、PS_DIRTY三种状态;lock和cond分别用于并发控制日志页操作。
源码分析
基于以上数据结构,进一步分析源码的实现策略,具体的参考源码文件sql/log.cc。以下对实现策略和处理方法进行详细分析。
首先,根据源码可知,TC_LOG日志文件默认每页大小为8K、文件大小最小为3页,可以根据配置文件和启动参数指定日志文件大小。定义如下所示:
#define TC_LOG_PAGE_SIZE 8192 #define TC_LOG_MIN_SIZE (3*TC_LOG_PAGE_SIZE) |
TC_LOG_MMAP组织结构
通过分析TC_LOG_MMAP::open()函数的处理逻辑,可知日志文件通过mmap将文件映射至内存,按照页进行组织分配,每一页按照页大小8K(windows下按照系统页大小分配)划分。第一页需要有一个页头信息,具体参考日志第一页组织格式。每一页中存储xid信息,通过页大小和xid占用的空间,计算出size和free。日志页通过next指针,组织成日志页的链式结构。在日志页分配后,将第一页作为active页,其他页为pool页。具体组织方式如下所示:
图2 TC_LOG_MMAP页组织结构
在日志页的第一页中,有一个头信息,用于表示2PC日志类型文件。首先存储4个字节的文件唯一标示,然后一个字节存储支持2PC的存储引擎的数目。其中日志的第一页组织如下所示:
图3 TC_LOG_MMAP日志第一页结构
TC_LOG_MMAP日志页类型转换
通过分析TC_LOG_MMAP的log_xid()和unlog()方法,可知TC_LOG_MMAP页状态转化过程如下所示:
图4 TC_LOG_MMAP日志页类型转换图
在TC_LOG_MMAP日志页逻辑处理中,只有一页为active页,一个页处于syncing页,其他页处于pool页。首先从pool队列中,取出一页为active页。然后可以将xid存储在active页,并标示该页为PS_DIRTY状态,等待sync。syncing页是处于将页信息从内存同步到磁盘的过程,同步后的页添加到pool队列中,此时active页变为syncing页。
其中,从pool队列中取出一页为active页,有两种策略:
1)取pool队列中的第一页,但是当第一页处于忙状态,即waiters不为0时,采用第2种策略;
2)选取pool队列中free值最大且空闲的一页(free值越大表明空闲空间越大),作为active页。
此外,从以上转换图和源码处理逻辑中可以发现,active页和syncing页在并发访问时,都是序列化的操作,syncing页的序列化是必须的,而active页是完全可以并行处理的。源码注释中解释到:syncing过程是整个处理的瓶颈(即IO操作过程是瓶颈),因此active页的序列化访问不会造成性能问题。但是当2PC事务频繁时,而IO的sync操作短暂阻塞时,会导致active页写满,由于active页是序列化的操作,如果2PC事务的实现依赖于日志的处理过程时,就会阻塞2PC事务的处理。
TC_LOG_MMAP日志时间周期
TC_LOG_MMAP日志时间周期是指日志记录xid的时间和日志清除xid的时间。从2PC事务的理论来看,在prepare阶段时,调用TC_LOG_MMAP::log_xid()函数记录xid,在commit时,调用TC_LOG_MMAP::unlog()函数删除xid记录。这样可以保证2PC事务在prepare失败的时候,通过恢复策略进行恢复,保证数据的一致性。
TC_LOG_MMAP回滚
TC_LOG_MMAP的回滚依赖于存储引擎的实现,只有支持2PC的存储引擎,才能进行数据的回滚。在回滚时,首先将日志文件的所有页中的xid信息(日志页中存在xid表明当前事务没有commit,是需要回滚的事务),全部插入的hash结构中,然后调用ha_recover()函数(sql/handler.cc)进行事务回滚,具体的回滚过程依赖存储引擎的回滚策略。
结论
通过以上分析TC_LOG的数据结构、日志页结构以及实现的策略,对TC_LOG的整个处理逻辑有深入的理解。TC_LOG_MMAP使用mmap策略,将日志文件映射到内存,按照日志页的方式组织,可以有效提高并发访问的能力,并且日志的sync操作按照页的方式,可以将多次操作一次同步到磁盘。
然而在设计中,active页的访问作为序列化操作,在极端情况下,从理论上推断可能会影响2PC事务的处理。具体的情况,还需要通过多种场景的测试获得真实的结果。