2020年12月10日
DCache支持全量恢复和增量恢复(类似于redis的RDB和AOF)。
本文介绍CacheServer恢复线程(SlaveCreateThread)的功能以及工作方式。
功能描述
从dump文件和binlog文件中恢复数据到内存,用于将数据快速恢复到对应节点的共享内存中。
dump文件和binlog文件的生成详见 DCache-CacheServer分析(四)。
相关文件:
SlaveCreateThread.h
SlaveCreateThread.cpp
触发方式
数据恢复时,需要手动调用 BackUpObj 的服务,这时会初始化该线程并启动,调用的接口如下:
tars::Int32 BackUpImp::restore(const std::string & mirrorPath, const vector<std::string> & binlogPath, tars::Int32 lastTime, tars::Bool normal, std::string &errmsg, tars::TarsCurrentPtr current)
参数解释详见MKBackUp.tars。
注意: 这个操作必须在故障(从)节点执行,原因见下方分析。
配置解析
配置文件中与之相关的配置项如下:
#备份恢复操作日志的按天日志文件名后缀
BackupDayLog=dumpAndRecover
处理逻辑分析
初始化
通过如下函数进行初始化:
int SlaveCreateThread::init(const string &mirrorPath, const vector<string> &binlogPath, time_t recoverTime, bool normal, const string &sConf)
参数 | 作用 |
mirrorPath | mirror文件绝对路径 |
binlogPath | binlog文件绝对路径 |
recoverTime | 恢复到该时间点,unix时间戳,精度:秒。 |
normal | 是否强制恢复。正常的恢复流程在加载完镜像和binlog后,会再从主机同步一条binlog;强制恢复流程,则是加载完镜像和binlog后,就认为恢复完成 |
注意:recoverTime 不能是未来的时间
线程处理流程
- 创建临时文件 SlaveCreating.dat(data路径下),为空文件;
- 等待另外3个线程将现有活动处理完毕,包括BinlogThread(binlog同步)、BinlogTimeThread(binlog进度)和TimerThread(心跳上报、同步路由等);
- 加载镜像文件(mirrorPath):
-
int SlaveCreateThread::restoreFromDumpMirrorFile()
- 加载镜像成功后开始加载按小时的binlog
-
int SlaveCreateThread::restoreFromHourBinLogFile()
- 是否是正常恢复。
- 正常的恢复流程是:在加载完镜像和binlog后,会再从主节点同步binlog(调用主节点的MKBinLogImp::getLog服务)
- 而强制恢复流程,则是加载完镜像和binlog后,就认为恢复完成
- 保存binlog的指针位置到文件 sync_point.data
- 最后,无论成功还是失败,都会删除临时文件 SlaveCreating.dat
SlaveCreating.dat作用
该文件存在,说明该节点正处于数据恢复状态。
这里需要注意,数据恢复操作一定要在从节点上进行。如果(由于误操作)这个文件出现在主节点,DCache的自我保护机制会禁止CacheServer启动,并报如下错误:
server can not changed from SLAVE to MASTER when in creating data status
这是为了保护主节点数据避免因误操作而被覆盖。
如何使用(重要!!!)
故障恢复可能会在3个场景中使用:
- 主节点异常
- 从节点异常
- 主从节点都发生异常
在上述情况出现时,如果想快速恢复内存数据,就需要执行这个恢复过程。
执行
恢复时,需要将全量备份的镜像文件和增量的binlog放到故障节点上,然后调用故障节点的 BackupObj->restore 方法(指定镜像文件和binglog文件路径)进行恢复。
格外注意:
故障节点恢复之后,它的角色必须是Slave。
大多数情况下,故障节点恢复后都会自动切换成了Slave状态。
DCache提供了数据保护机制,当调用主节点的恢复服务时,不会有任何效果,在日志中会有提示:
server changed from SLAVE to MASTER, slave try to create data failed
下面以模块table主从部署为例,初始状态如下:
主机 | CacheServer模块名(某一个表) | 角色 |
A | tableM | Master |
B | tableS | Slave |
主节点异常
模拟场景:
由于某种原因导致主节点tableM所在主机A忽然宕机。
启用自动切换
为了保证服务的可用性,一般情况下我们都会设置“主从自动切换”。当出现异常时会自动触发。那么,之前的从节点tableS的状态从Slave切换成Master,变成了主节点。
节点M恢复时,会先到Router中获取自身的角色。正常情况下Router列表已经将主从关系更改了,此时tableM被标记为Slave。状态如下:
主机 | CacheServer模块名(某一个表) | 角色 |
A | tableM | Slave |
B | tableS | Master |
此时,由tableS继续对外提供读写服务。
由于tableM所在的主机A刚刚恢复,它的共享内存中是空的。这种情况下我们可以什么都不做,等tableM的内存数据慢慢从tableS的binlog里追上来。但是如果此时tableS的主机B也发生了宕机,服务就彻底中断了,会对业务造成影响。
处理流程:
为了避免此类问题发生,就需要快速恢复内存数据。将dump下来的镜像文件和按小时的binlog放在主机A上,然后直接寻址调用tableM的BackupObj->restore方法。
未启用自动切换
这种情况下由 tableS(状态为Slave)提供只读服务,不可写,会对业务有一定的影响。当主机A恢复后,tableM会被自动拉起、以Master身份继续提供读写服务。
但此时由于内存中没数据,读操作会出现“缓存击穿”的现象(前提是tableM配置了DBFlag=Y和ReadDbFlag = Y),会对后端的mysql造成压力。
因此,保险起见,在tableM恢复之后,需要人工在控制台上点击“手动切换”,然后参考“启用自动切换”章节里面的处理流程。
从节点异常
这种情况下不会发生切换。从节点共享内存被破坏、数据丢失,但是由于主节点还存活,不影响读写。
我们可以选择无视,也可以保险一点按照“启用自动切换”章节里面的处理流程进行处理。
主从节点同时异常
这种情况比较少见,但是也不能完全排除,需要准备应急预案。按照如下步骤处理:
- 2个节点都恢复后,先到从节点上按照“处理流程”恢复数据;
- 然后到控制台上手动切换主从节点(此步骤需要考虑这个表的tps以及配置的binlog同步时间差,否则可能丢数据);
- 再到另一个节点(切换了,此时是从节点)上,按照“处理流程”恢复数据。
说明:
上述步骤1、2,“从节点恢复数据后切换为主节点”,是为了避免可能出现的“缓存击穿”现象。
总结
在数据备份、恢复方面,DCache为我们提供了较为完备的解决方案。可以通过手动调用dump和restore来完成数据的全量备份与恢复。
- 选择主-从集群部署时,有较低概率2个节点同时宕机,可以根据业务需要选择性恢复某些模块的从节点数据;
- 主-从-异地镜像部署时,有极低的概率3个节点同时宕机。这时候完全不需要考虑数据恢复问题,让故障节点慢慢追数据即可。因为当其中一个节点宕机了,一般情况下都会及时介入解决,保证DCache至少同时有2个节点在线。因此,极力推荐3节点部署。
内存库的数据恢复需要考虑到很多细节,更需要大量的时间和实践来验证。DCache在这方面做得很不错,考虑到了在数据恢复过程中所有可能出现的问题,我们可以放心使用。但在项目实践中,也发现了一些缺点:
- 个人分析DCache的设计初衷,就是“以内存数据为基准”,没有提供反向更新(从数据库更新到内存)的工具,需要自行开发。我们在项目中自研了数据加载工具,实现从mysql将表数据全量或单条加载到内存库中。
- 数据恢复不能自动执行,而且是Moudule(表)级的,一次操作只能针对某一个Moudule的备份、恢复。需要自行编写批量处理脚本。
- 主从切换时有数据丢失的风险,需要根据系统压力灵活调整参数配置(如主备机binlog差异的阈值 SwitchBinLogDiffLimit,详见《主备切换》)