数据恢复流程
如果一虚拟节点(vnode B) 处于unsynced状态,master存在(vnode A),而且其版本号比master的低,它将立即启动数据恢复流程。在理解恢复流程时,需要澄清几个关于文件的概念和处理规则。
- 每个文件(无论是archived data的file还是wal)都有一个index, 这需要应用来维护(vnode里,该index就是fileId*3 + 0/1/2, 对应data, head与last三个文件)。如果index为0,表示系统里最老的数据文件。对于mode里的文件,数量是固定的,对应于acct, user, db, table等文件。
- 任何一个数据文件(file)有名字、大小,还有一个magic number。只有文件名、大小与magic number一致时,两个文件才判断是一样的,无需同步。Magic number可以是checksum, 也可以是简单的文件大小。怎么计算magic,换句话说,如何检测数据文件是否有效,完全由应用决定。
- 文件名的处理有点复杂,因为每台服务器的路径可能不一致。比如node A的TDengine的数据文件存放在 /etc/taos目录下,而node B的数据存放在 /home/jhtao目录下。因此同步模块需要应用在启动一个同步实例时提供一个path,这样两台服务器的绝对路径可以不一样,但仍然可以做对比,做同步。
- 当sync模块调用回调函数getFileInfo获得数据文件信息时,有如下的规则
- index 为0,表示获取最老的文件,同时修改index返回给sync模块。如果index不为0,表示获取指定位置的文件。
- 如果name为空,表示sync想获取位于index位置的文件信息,包括magic, size。Master节点会这么调用
- 如果name不为空,表示sync想获取指定文件名和index的信息,slave节点会这么调用
- 如果某个index的文件不存在,magic返回0,表示文件已经是最后一个。因此整个系统里,文件的index必须是连续的一段整数。
- 当sync模块调用回调函数getWalInfo获得wal信息时,有如下规则
- index为0,表示获得最老的WAL文件, 返回时,index更新为具体的数字
- 如果返回0,表示这是最新的一个WAL文件,如果返回值是1,表示后面还有更新的WAL文件
- 返回的文件名为空,那表示没有WAL文件
- 无论是getFileInfo, 还是getWalInfo, 只要获取出错(不是文件不存在),返回-1即可,系统会报错,停止同步
整个数据恢复流程分为两大步骤,第一步,先恢复archived data(file), 然后恢复wal。具体流程如下:
- 通过已经建立的TCP连接,发送sync req给master节点
- master收到sync req后,以client的身份,向vnode B主动建立一新的专用于同步的TCP连接(syncFd)
- 新的TCP连接建立成功后,master将开始retrieve流程,对应的,vnode B将同步启动restore流程
- Retrieve/Restore流程里,先处理所有archived data (vnode里的data, head, last文件),后处理WAL data。
- 对于archived data,master将通过回调函数getFileInfo获取数据文件的基本信息,包括文件名、magic以及文件大小。
- master 将获得的文件名、magic以及文件大小发给vnode B
- vnode B将回调函数getFile获得magic和文件大小,如果两者一致,就认为无需同步,如果两者不一致 ,就认为需要同步。vnode B将结果通过消息FileAck发回master
- 如果文件需要同步,master就调用sendfile把整个文件发往vnode B
- 如果文件不需要同步,master(vnode A)就重复5,6,7,8,直到所有文件被处理完
对于WAL同步,流程如下:
- master节点调用回调函数getWalInfo,获取WAL的文件名。
- 如果getWalInfo返回值大于0,表示该文件还不是最后一个WAL,因此master调用sendfile一下把该文件发送给vnode B
- 如果getWalInfo返回时为0,表示该文件是最后一个WAL,因为文件可能还处于写的状态中,sync模块要根据WAL Head的定义逐条读出记录,然后发往vnode B。
- vnode A读取TCP连接传来的数据,按照WAL Head,逐条读取,如果版本号比现有的大,调用回调函数writeToCache,交给应用处理。如果小,直接扔掉。
- 上述流程循环,直到所有WAL文件都被处理完。处理完后,master就会将新来的数据包通过Forward消息转发给slave。
从同步文件启动起,sync模块会通过inotify监控所有处理过的file以及wal。一旦发现被处理过的文件有更新变化,同步流程将中止,会重新启动。因为有可能落盘操作正在进行(比如历史数据导入,内存数据落盘),把已经处理过的文件进行了修改,需要重新同步才行。
对于最后一个WAL (LastWal)的处理逻辑有点复杂,因为这个文件往往是打开写的状态,有很多场景需要考虑,比如:
- LastWal文件size在增长,需要重新读;
- LastWal文件虽然已经打开写,但内容为空;
- LastWal文件已经被关闭,应用生成了新的Last WAL文件;
- LastWal文件没有被关闭,但数据落盘的原因,没有读到完整的一条记录;
- LastWal文件没有被关闭,但数据落盘的原因,还有部分记录暂时读取不到;
sync模块通过inotify监控LastWal文件的更新和关闭操作。而且在确认已经尽可能读完LastWal的数据后,会将对方同步状态设置为SYNC_CACHE。该状态下,master节点会将新的记录转发给vnode B,而此时vnode B并没有完成同步,需要把这些转发包先存在recv buffer里,等WAL处理完后,vnode A再把recv buffer里的数据包通过回调writeToCache交给应用处理。
等vnode B把这些buffered forwards处理完,同步流程才算结束,vnode B正式变为slave。