QJM的基本原理就是用2N+1台JournalNode存储EditLog,每次写数据操作有大多数(>=N+1)返回成功时即认为该次写成功,数据不会丢失了。当然这个算法所能容忍的是最多有N台机器挂掉,如果多于N台挂掉,这个算法就失效了。
第二部分
2.1.写日志机制
写操作由主节点来完成,当主节点调用flush操作,会调用RPC同时向N个JN服务异步写日志,有N/2+1个节点返回成功,本次写操作才算成功。
主节点会标记返回失败的JN节点,下次写日志将不再写该节点,直到下次调用滚动日志操作,如果此时该JN节点恢复正常,之后主节点会向其写日志。虽然该节点丢失部分日志,由于主节点写入了多份,因此相应的日志并没有丢失。
为了保证写入每个日志文件txid的连续性,主节点保证分配的txid是连续的,同时JN节点在接受写日志的时候,首先会检查txid是否跟上次写连续,如果不连续会向主节点报错,连续则写入日志文件。
2.2读日志机制
1、选择日志文件,建立输入流
从节点遍历出所有还没有消化的日志文件,剔除不处理的文件。对于每个JN节点上的日志文件,均按照txid从小到大进行排序放入一个集合。每个JN节点在从节点端均对应这样一个集合。再将每个JN节点间相同的日志文件进行归类为一组(组内日志会检查fisrtTxid是否相等,及其lastTxid是否相等);每个组之间再按照txid从小到大进行排序,这样方便从节点按照txid顺序消化日志;同时也会判断每个组之间txid是否连续。
2、消化日志
准备好输入流以后,开始消化日志,从节点按照txid先后顺序从每个日志组里面消化日志。在每个日志组里面,首先会检查起始txid是否正确,如果正确,从节点先消化第一个日志文件,如果消化第一个日志文件失败则消化第二个日志文件,以此类推,如果日志组内文件遍历完还没有找到需要的日志,则该日志消化失败,消化每个日志的如果消化的上一个txid等于该日志文件的lastTxid,则该日志文件消化结束。
处理如下图:
2.3.日志恢复
在从节点切换为主节点的过程中,会进行最近的日志段状态检查,如果没有转换为finalized状态会将其转换为该状态,日志恢复就处于该过程当中。
2.3.1.触发条件
QJM在从切换为主的情况下才会进行最新的一个日志文件的数据一致性检查,然后决定是否触发数据修复流程。
之前的日志文件HDFS会确保之前的操作中已经将其由inprocess状态转化为finalized状态,处于一个高可用的状态,因此只有最新的一个日志文件是新增的,可能需要进行恢复此处理。
2.3.2.恢复流程
2.3.2.1.prepareRecovery准备恢复
该操作向JN端发送RPC请求,查询需要恢复的日志段文件是否存在,如果存在则判断日志段文件状态(inprocess或finalized),同时也会返回epoch编号,NameNode根据返回的查询信息通过修复算法选择修复的源节点,准备进行数据修复。
(修复策略:
1、首先判断JN节点是否有指定的txid,如果某节点没有,则该节点不会作为源节点;
2、如果JN节点存在指定的txid,然后判断该文件是否为finalized状态,如果不同的JN节点,txid所在的文件既有finalized状态的文件又有inprocess状态的文件,以finalized状态文件为候选源节点,当然finalized状态的文件之间还需要判断结束txid是否相等,然后返回其中任意一个节点作为源节点
如果节点间文件均是inprocess状态的文件,首先判断其epoch编号,如果epoch编号不一致,则以epoch编号大的作为候选源节点;如果epoch编号一致,则选择结束txid更大的作为源节点。)
2.3.2.2.acceptRecovery接受恢复
计算获得源节点后,NameNode会向JN端发送恢复操作,JN节点根据接收到的RPC恢复请求,判断当前节点是否需要进行日志修复,如果需要进行修复,则通过doGet方式到源节点下载需要恢复的目标日志文件。下载过程中,先将下载的文件放到临时目录(tmp)目录下,下载完成后进行md5校验,检查是否有数据丢失,数据检查通过再将下载的文件放置到工作目录(current)下,这样数据恢复完成。
在JN节点执行该方法中,有两个问题需要考虑:
可以考虑返回URL数组而不是单个URL,这样一个URL不能连接还可以尝试连接另外的JN节点进行文件下载;
2、有可能某JN节点下载日志文件的时候,自己进程挂掉,在QJM中,有对该问题的处理方式;
开始接触的时候我担心是否可能有文件既在被从节点读取,又在恢复该日志文件,通过分析后发现不会有何种情况,因为从节点消化的日志均是finalized状态的文件而不是inprocess状态的文件。