Berkeley DB实现分析(1) ——多进程下数据库环境的恢复:DB_REGISTER

一个进程在打开Berkeley DB环境时(DbEnv::open),通常需要恢复环境(DB_RECOVER)以确保数据的完整性。

但DB_RECOVER的语义是强制恢复,即任何情况下都会删除旧环境并创建新环境,以确保环境的正确性。

 

1.每次程序启动时都进行recovery是没有必要的:

1. 环境很有可能是正常的,每个访问进程退出时都正确地使用了DbEnv::close()。

2. 很多时候,Environment的重建是一个比较耗时的行为,增加了恢复服务的等待时间,影响了系统的可用性

恢复环境时,会在BDB环境的描述区域(dbinc/region.h中的REGENV结构,该结构映射到__db.001文件的最开始N个字节)设置panic标记,并删除所有的区域文件(__db.001 ~ __db.006),而BDB库中的几乎所有操作都会检测panic标记(返回错误或抛出DbRunRecoveryException异常)。

3. 因此,当前仍然在正常环境下工作的进程将会由于一个指定了DB_RECOVER进程的加入而被迫退出。

 

2.怎样设置某一个环境为“需要时才进行recovery”?

所有打开同一个环境的进程,如果指定了DB_RECOVER,需要同时指定DB_REGISTER

DB_REGISTER保证只在检测到data corruption时进行数据库环境的恢复。

对于成百上千兆的环境文件,显然不可能对内容进行逐字节验证。那么BDB怎样检测到环境文件的data corruption呢?

 

3.Process Registry实现原理:

既然没法逐字节验证环境文件内容,不妨换个思路:

如果可以确保之前所有的进程都是正常退出(正确调用DbEnv::close),则可以确保环境文件的内容是一致的。

 

于是register文件粉墨登场,每个进程打开/关闭环境时都会在此留下自己的记录:

进程打开环境时:在register文件的某一行记录自己的process id,并使用文件锁(fcntl)锁住该行的第一个byte

正常退出环境时:将其记录“擦除”,并解锁。

register文件所有的行都是等宽的,每一行不是一个process ID slot,就是一个空记录(empty slot),格式如下:

__db.register :

|<--- 25 bytes --->|

                    12345 # prcess ID slot 1

X                            # empty slot

                     12346 # process ID slot 2

                     12347 # process ID slot 3

X                             # empty slot

X

这样每个进程打开环境时,都会遍历register文件所有的行:

1. 该行符合empty slot的格式(X后面跟24个空格),则跳过

2. 该行是一个process ID slot,且该pocess ID不等于该进程本身的pid,则检查是否可以锁住该行的第一个byte:

lock失败:说明该行对应的process依然在环境中,正常并跳过该行

lock成功:说明该行对应的process已经crash(而没有来得及更新register文件),但进程退出时由OS回收了所拥有的文件锁,需要进行recover

3. 该行不够宽度(只可能发生在最后一行):说明进程在更新register文件时被interrupt,需要进行recover。

 

如果不需要进行recovery,则再次遍历register文件所有行:

1. 找到一个empty slot并且可以lock该行的第一个byte,写入自己的process id

2. 直到读到register文件末尾。

等宽行的优势在此体现:结合文件锁,很好地保证了多个进程不会同时写一个文件的同一个位置。

 

4.其他实现方案:

使用flock也可以在某个文件上加上建议锁。这个方法要求每个进程都需要创建并锁住自己的register文件,退出环境时解锁并删除该register文件。这样进程打开环境时只需遍历所有其他进程的register文件,并确保是否已上锁即可。

但这种方式可能会产生大量的文件(进程数量*打开的DbEnv数量),按作者的话说:

"but flock would require a separate file for each process of control (and probably each DbEnv handle) in the database environment, which is fairly ugly."

 

5.多进程下恢复环境依然有风险:

当一个进程检查register文件并决定要恢复数据库环境时,会将整个环境设置panic标志,此时所有正在该环境上操作的进程都会检测到错误并退出。

但是仍然会有一个corruption的时间窗口:当一个进程进行了panic检测并将要进行写操作时,另一个进程决定恢复环境。(这个时间窗口是BDB本身就存在的,与是否进行register检测无关。)

按作者的话说,"That's very, very unlikely to happen.",并且有一个可能的解决办法,在一个进程决定要recover时,向环境内的已有进程发送SIGKILL以强制其退出,缺点在于该进程不一定有这样的权限,而且不能准确地判断该进程是否已死。所以该方法默认是不采用的。

/* in file env/env_register.c */
#define DB_ENVREG_KILL_ALL 0

 

 

 

posted @ 2012-05-09 23:49  PromisE_谢  阅读(2755)  评论(2编辑  收藏  举报