【关于作者】
关于作者,目前在蚂蚁金服搬砖任职,在营销投放领域工作了多年,目前在专注于内存数据库相关的应用学习,如果你有任何技术交流或大厂内推及面试咨询,都可以从我的个人博客(https://0522-isniceday.top/)联系我~
事务日志
类型
- 日志文件
命名
- log.XX(十六进制的数字-高32位代表epoch、低32位代表操作序号)
内容
01:07:41 session 0x144699552020000 cxid 0x0 zxid 0x300000002 createSession 30000
01:08:40 session 0x144699552020000 cxid 0x2 zxid 0x300000003 create
/test_log,#7631,v{s{31 ,s{/w orld,'anyone}}},F,2
FileTxnLog
-
复制维护日志的相关操作:事务日志的相关写入、读取以及数据恢复
-
写入事务日志流程
-
1.判断当前类是否关联一个日志文件,如果没有关联就以当前事务id为文件后缀创建一个日志文件,同时创建日志头,然后将文件流存入集合StreamsToFlush
-
2.每次写入都触发磁盘空间大小的检查,如果不足4kb则提前触发宽容(申请)64mb,扩容内容在没使用的情况下用0占满
- zookeeper.preAllocSize可调节扩容大小
-
3.将待写入的信息进行序列化,计算checkNum
-
4.将序列化后的事务头、事务体、checkNum写入文件流
- 注意:再故障leader选举后,可能存在之前的leader节点的zxid大于选举后的leader节点,这个时候新的leader节点会发送TRUNC命令给这个follower节点。强制对这部分日志进行截断,会从文件中将该部分的事务日志删除
-
Snapshot数据快照
概念
- 记录某一时刻zookeeper上的全量内容
命名
- 使用zxid的十六进制作为文件后缀
FileSnap类
-
复制处理快照的写入、读取
-
根据事务记录的个数snapCount来触发快照文件的创建
-
流程
-
1.每次事务日志的写入都会去判断是否写入快照文件
- 比较算法:logCount > (snapCount /2 + randRoll),randRoll为snapCount /2~snapCount /2-1大小的一个值
-
2.事务日志在写入snapCount个事务后,会重新创建一个事务日志文件,并会单独创建一个线程去执行快照的dump操作
-
3.根据已提交的最大的zxid来命名快照文件,并将序列化过后的文件头信息、会话信息、DataTree分别序列化写入文件
-
数据的初始化
FileTxnSnapLog类
-
流程
- 1.事物日志操作类–FileTxnSnapLog和快照管理类–FileSnap的初始化
- 2.初始化一些节点,例如/zookeeper、/zookeeper/quota.,创建PlayBackListener监听器,用于数据修正时候的回调
- 3.先读取100个快照文件,按照zxid的顺序从大开始读取,校验checkNum校验完整性,如果校验失败则放弃这个快照文件继续读取下一个。如果100个都检验失败了则直接启动失败
- 4.当全量数据恢复完成后,这个时候有DataTree实例和sessionsWithTimeOuts集合了,能够从快照中得到当前的zxid,再找到比该zxid大的事务日志(也就是还未存入快照的事务)。每一条事务日志恢复后同通过PlayBackListener回调生成proposal提议同步到follower
- 5.事务日志恢复完成后,得到最大的zxid并解析出epoch,同时在磁盘的currentEpoch和acceptedEpoch文件读取上次记录的epoch进行校验,至此数据初始化流程完成
数据同步
开始阶段
- 当zookeeper初始化完成后,集群选举后,Learner服务器会向Leader完成注册以后,就会触发数据同步环节
概念
- leader将没有在learner服务器上同步过的数据同步给learner服务器
流程
-
获得learn状态
- 1.learner服务器初始化完成后,learner服务器发送ACKEPOCH数据包给leader服务器,leader服务器从中获得learner服务器的currentEpoch和lastZxid
-
数据同步初始化
-
开始数据同步之前,leader服务器先数据同步初始化,会从zookeeper内存数据库TreeData中提取出“提议缓存队列”:proposals,也就是说选举出了leader,leader就立马先从快照、日志给自己先初始化数据
接着Leader服务器会从Zookeeper内存数据库中提取出事务对应的提议缓存队列:proposals
-
完成下面三个zxid的初始化(这几个参数都是从内存中读到的并非日志等文件)
- peerLastZxid:该Learner服务器最后处理的ZXID。
- minCommittedLog: Leader服务器提议缓存队列committedLog中的最小ZXID。
- maxCommittedLog:Leader服务器提议缓存队列committedLog中的最大ZXID
-
-
-
数据化同步
-
流程
- 首先leader节点会优先使用全量同步的方式进行数据同步,然后根据leader与learner之间的差异决定同步方式
-
同步方式
-
直接差异化同步(DIFF 同步)
-
场景(触发条件)
- peerlastZxid介于minCommittedLog和maxCommittedLog之间
- leader再第一阶段提交的proposal还没有commit给follower,然后该leader就挂了,然后某个接收到这个proposal的节点选举为master,然后重新数据初始化,再进行数据同步时,其他节点的lastZxid则会触发DIFF同步,因为他们只是将上个leader的proposal写入了日志文件但是没有接受到commit指令同步到内存
-
同步流程
-
leader首先发送一个DIFF指令,通知learner进入数据差异化同步阶段,即将推送proposal给learner
- 实际推送是推送两个包,proposal内容数据包和commit指令数据包
-
数据包推送完成后,leader会将learner放入forwardingFollowers或observingLearners队列,并发送一个newleader指令通知learner数据已同步
-
learner接收到newleader指令之后发送ack消息表示接收到了同步的数据包
-
leader接收到ack包之后进入“过半”策略的等待,即接收到过半的leader发送的ack包,此时再发送uptodata数据包给所有已完成数据同步的learner
-
learner接收到uptodata指令之后会结束数据同步流程并再次发送ack包给leader
-
-
-
先回滚再差异化同步(TRUNC+DIFF 同步)
-
场景
- 是Leader服务器在已经将事务记录到了本地事务日志中,但是没有成功发起Proposal流程的时候就挂了
-
同步流程
-
当leader服务器发现learner包含了一条自己没有的事务记录,那么就会让learner进行事务回滚到leader服务器存在且最接近于peerLastZxid的事务
-
解决了故障恢复阶段第二个问题
- leader服务器再第一阶段提交的消息,但是并没有发送给learner服务器,需要保证该事务消息被丢弃
-
-
再进行DIFF差异同步
-
-
-
仅回滚同步(TRUNC 同步)
-
场景
- peerLastZxid>maxCommittedLog
-
同步流程
- leader服务器要求learner同步回滚到maxCommitteLog
-
-
全量同步(SNAP同步)
-
场景
-
peerLastZxid 小于minCommittedLog。
-
Leader 服务器上没有提议缓存队列,peerLastZxid 不等于lastProcessedZxid(Leader服务器数据恢复后得到的最大ZXID)
- 也就是说当前leader从事务日志中找不到比快照最大的zxid还要大的事务id,并且leader最大的zxid又和learner出现了不一致,这个时候就只能全量同步数据
-
-
同步流程
- leader先向learner发送SNAP全量同步指令
- Leader会从内存数据库中获取到全量的数据节点和会话超时时间记录器,将它们序列化后传输给Learner
-
-
-