SOFAJRaft 源码分析三(状态机、线性一致性读)

1.概述今天来看一下jraft如何将日志写入到状态机,其实就是业务真正的存储工作。如果我们需要使用jraft,我们对这里的实现就需要足够的了解。然后还会介绍jraft的读取逻辑。2.思路整理对于状态机,我们关注问题如下:何时会将日志同步到状态机?对于节点变化,状态机会做什么?状态机为了业务解藕做了怎么样封装?对于读取操作:主要就是如何做读取优化操作?那我们带着这几个问题一起深入源码,寻找答案吧!3.状态机源码分析主要就是leader和follower将日志应用到状态机的过程。当然le
摘要由CSDN通过智能技术生成

1.概述

今天来看一下jraft如何将日志写入到状态机,其实就是业务真正的存储工作。如果我们需要使用jraft,我们对这里的实现就需要足够的了解。然后还会介绍jraft的读取逻辑。

2.思路整理

对于状态机,我们关注问题如下:

  • 何时会将日志同步到状态机?
  • 对于节点变化,状态机会做什么?
  • 状态机为了业务解藕做了怎么样封装?
    对于读取操作:主要就是如何做读取优化操作?
    那我们带着这几个问题一起深入源码,寻找答案吧!

3.状态机源码分析

主要就是leader和follower将日志应用到状态机的过程。当然leader和follower应用的时机不一样,但是过程都是一样的。

我们先来看leader。leader再增加日志的时候,会有一个回调,如果成功会执行这个回调方法(具体时机为将日志添加到本地磁盘后,也就是AppendBatcher 的flush方法)。

这个回调回执行ballotBox的commitAt方法。

@Override
public void run(final Status status) {
   
    if (status.isOk()) {
   
        NodeImpl.this.ballotBox.commitAt(this.firstLogIndex, this.firstLogIndex + this.nEntries - 1,
            NodeImpl.this.serverId);
    } else {
   
        LOG.error("Node {} append [{}, {}] failed, status={}.", getNodeId(), this.firstLogIndex,
            this.firstLogIndex + this.nEntries - 1, status);
    }
}
commitAt方法

这里说一下,ballotBox主要记录了日志提交的状态。每个节点提交日志成功后都会调用这个方法。

上面只说了leader成功,如果follower提交成功,则会以响应的形式告诉leader。在onAppendEntriesReturned 中也会调用该方法。如下图。


这就很清晰了。其实上篇博客都介绍了这个方法的作用。因为和状态机实现衔接,所以我们在来回顾一下这个方法。

final long startAt = Math.max(this.pendingIndex, firstLogIndex);
Ballot.PosHint hint = new Ballot.PosHint();
for (long logIndex = startAt; logIndex <= lastLogIndex; logIndex++) {
   
    final Ballot bl = this.pendingMetaQueue.get((int) (logIndex - this.pendingIndex));
    hint = bl.grant(peer, hint);
    if (bl.isGranted()) {
   
        lastCommittedIndex = logIndex;
    }
}
if (lastCommittedIndex == 0) {
   
    return true;
}
this.pendingMetaQueue.removeFromFirst((int) (lastCommittedIndex - this.pendingIndex) + 1);
LOG.debug("Committed log fromIndex={}, toIndex={}.", this.pendingIndex, lastCommittedIndex);
this.pendingIndex = lastCommittedIndex + 1;
this.lastCommittedIndex = lastCommittedIndex;
...
this.waiter.onCommitted(lastCommittedIndex);
  • pendingIndex:当前已经ok的日志索引+1(何为ok,就是大多数节点都持久化的)
  • firstLogIndex:本次提交成功日志的起始值。
  • lastLogIndex:本次提交成功日志的终止值。

为什么这里startAt为max,因为这个有很大的可能pendingIndex比firstLogIndex大,原因是这个节点响应比较慢。在他响应之前Ballot的isGranted已经返回true了。

这样的话,我们能理解这个方法其实就是用来维护this.lastCommittedIndex这个成员变量。最后他会调用this.waiter.onCommitted方法。

onCommitted方法

其实这个方法就是commit到状态机的入口。
当然这个方法也会在两处被调用。一处是被leader,一处是被follower调用。

@Override
public boolean onCommitted(final long committedIndex) {
   
    return enqueueTask((task, sequence) -> {
   
        task.type = TaskType.COMMITTED;
        task.committedIndex = committedIndex;
    });
}

这个方法逻辑比较简单,就是创建一个commit时间丢到FSMCallerImpl 的队列。
我们顺藤摸瓜看看follower何时调用这个onCommitted方法。

FollowerStableClosure#run

1.在follower增加日志成功之后,有执行FollowerStableClosure 回调,上篇文章说过,他就是用来响应leader。当然在响应之前回执行node.ballotBox.setLastCommittedIndex方法。其实这个方法最后会调用onCommitted方法。

2.在follower处理心跳或者探针消息的时候。也会调用setLastCommittedIndex方法。ok,到这里我们已经了解了。follower和leader何时会给FSMCallerImpl 的队列提交commit事件。接下来我们只需要关注如何处理事件了。

if (entriesCount == 0) {
   
    // heartbeat
    final AppendEntriesResponse.Builder respBuilder = AppendEntriesResponse.newBuilder() //
        .setSuccess(true) //
        .setTerm(this.currTerm) //
        .setLastLogIndex(this.logManager.getLastLogIndex());
    doUnlock = false;
    this.writeLock.unlock();
    // see the comments at FollowerStableClosure#run()
    this.ballotBox.setLastCommittedIndex(Math.min(request.getCommittedIndex(), prevLogIndex));
    return respBuilder.build
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值