9.3.3 主备同步

8.4.1节已经介绍了UpdateServer的一致性选择。OceanBase选择了强一致性,主UpdateServer往备UpdateServer同步操作日志,如果同步成功,主UpdateServer操作本地后返回客户端更新成功,否则,主UpdateServer会把备UpdateServer从同步列表中剔除。另外,剔除备UpdateServer之前需要通知RootServer,从而防止RootServer将不一致的备UpdateServer选为主UpdateServer。

如图9-7所示,主UpdateServer往备机推送操作日志,备UpdateServer的接收线程接收日志,并写入到一块全局日志缓冲区中。备UpdateServer只要接收到日志就可以回复主UpdateServer同步成功,主UpdateServer接着更新本地内存并将日志刷到磁盘文件中,最后回复客户端写入操作成功。这种方式实现了强一致性,如果主UpdateServer出现故障,备UpdateServer包含所有的修改操作,因而能够完全无缝地切换为主UpdateServer继续提供服务。另外,主备同步过程中要求主机刷磁盘文件,备机只需要写内存缓冲区,强同步带来的额外延时也几乎可以忽略。

9.3.3 主备同步 - 图1

图 9-7 UpdateServer主备同步原理

正常情况下,备UpdateServer的日志回放线程会从全局日志缓冲区中读取操作日志,在内存中回放并同时将操作日志刷到备机的日志文件中。如果发生异常,比如备UpdateServer刚启动或者主备之间网络刚恢复,全局日志缓冲区中没有日志或者日志不连续,此时,备UpdateServer需要主动请求主UpdateServer拉取操作日志。主UpdateServer首先查找日志缓冲区,如果缓冲区中没有数据,还需要读取磁盘日志文件,并将操作日志回复备UpdateServer。代码如下:


class ObReplayLogSrc

{

public:

//读取一批待回放的操作日志

//@param[in]start_cursor日志起始点

//@param[out]end_id读取到的最大日志号加1,即下一次读取的起始日志号

//@param[in]buf日志缓冲区

//@param[in]len日志缓冲区长度

//@param[out]read_count读取到的有效字节数

int get_log(const ObLogCursor&start_cursor,int64_t&end_id,char*buf,const int64_t len,int64_t&read_count);

};

class ObUpsLogMgr

{

public:

enum WAIT_SYNC_TYPE

{

WAIT_NONE=0,

WAIT_COMMIT=1,

WAIT_FLUSH=2,

};

public:

//备UpdateServer接收主UpdateServer发送的操作日志

int slave_receive_log(const char*buf,int64_t len,const int64_t wait_sync_time_us,const WAIT_SYNC_TYPE wait_event_type);

//备UpdateServer获取并回放操作日志

int replay_log();

};


备UpdateServer接收到主UpdateServer发送的操作日志后,调用ObUpsLogMgr类的slave_receive_log将操作日志保存到日志缓冲区中。备UpdateServer可以配置成不等待(WAIT_NONE)、等待提交到MemTable(WAIT_COMMIT)或者等待提交到MemTable且写入磁盘(WAIT_FLUSH)。另外,备UpdateServer有专门的日志回放线程不断地调用ObUpsLogMgr中的replay_log函数获取并回放操作日志。

备UpdateServer执行replay_log函数时,首先调用ObReplayLogSrc的get_log函数读取一批待回放的操作日志,接着,将操作日志应用到MemTable中并写入日志文件持久化。Get_log函数执行时首先查看本机的日志缓冲区,如果缓冲区中不存在日志起始点(start_cursor)开始的操作日志,那么,生成一个异步任务,读取主UpdateServer。一般情况下,slave_receive_log接收的日志刚加入日志缓冲区就被get_log读走了,不需要读取主UpdateServer。