jraft日志复制
- jraft的日志复制是指从leader往follower复制logEntry的过程。
- 日志复制从节点成为leader开始。在nodeImpl的becomeLeader中
private void becomeLeader() {
Requires.requireTrue(this.state == State.STATE_CANDIDATE, "Illegal state: " + this.state);
LOG.info("Node {} term {} become leader of group {} {}", this.getNodeId(), this.currTerm, this.conf.getConf(),
this.conf.getOldConf());
// cancel candidate vote timer
stopVoteTimer();
this.state = State.STATE_LEADER;
this.leaderId = this.serverId.copy();
this.replicatorGroup.resetTerm(this.currTerm);
for (final PeerId peer : this.conf.listPeers()) {
if (peer.equals(this.serverId)) {
continue;
}
LOG.debug("Node {} term {} add replicator {}", this.getNodeId(), this.currTerm, peer);
//这里为每个follower节点创建一个replicator开始复制。
if (!this.replicatorGroup.addReplicator(peer)) {
LOG.error("Fail to add replicator for {}", peer);
}
}
// init commit manager
this.ballotBox.resetPendingIndex(this.logManager.getLastLogIndex() + 1);
// Register _conf_ctx to reject configuration changing before the first log
// is committed.
if (this.confCtx.isBusy()) {
throw new IllegalStateException();
}
this.confCtx.flush(this.conf.getConf(), this.conf.getOldConf());
this.stepDownTimer.start();
}
- 在replicator的start方法中
public static ThreadId start(ReplicatorOptions opts, RaftOptions raftOptions) {
if (opts.getLogManager() == null || opts.getBallotBox() == null || opts.getNode() == null) {
throw new IllegalArgumentException("Invalid ReplicatorOptions.");
}
final Replicator r = new Replicator(opts, raftOptions);
if (!r.rpcService.connect(opts.getPeerId().getEndpoint())) {
LOG.error("Fail to init sending channel to {}", opts.getPeerId());
//Return and it will be retried later.
return null;
}
//Register replicator metric set.
final MetricRegistry metricRegistry = opts.getNode().getNodeMetrics().getMetricRegistry();
if (metricRegistry != null) {
try {
final String replicatorMetricName = getReplicatorMetricName(opts);
if (!metricRegistry.getNames().contains(replicatorMetricName)) {
metricRegistry.register(replicatorMetricName, new ReplicatorMetricSet(opts, r));
}
} catch (final IllegalArgumentException e) {
//ignore
}
}
//Start replication
r.id = new ThreadId(r, r);
r.id.lock();
LOG.info("Replicator={}@{} is started", r.id, r.options.getPeerId());
r.catchUpClosure = null;
final long now = Utils.nowMs();
r.lastRpcSendTimestamp = now;
// 不断hearterBeat
r.startHeartbeatTimer(now);
//id.unlock in sendEmptyEntries
///发送探针消息,获取当前follower上的日志index。
r.sendEmptyEntries(false);
return r.id;
}
- 在startHeartbeatTimer中,每隔一段时间,产出一个心跳超时的事件,通过distruptor消费这个事件来发送一次heartbeat,这里很绕,不懂为啥不直接在schedulerExecutor中发送heartbeat事件。
- 在sendEmptyEntries方法中,先填充一些基础信息,
private boolean fillCommonFields(AppendEntriesRequest.Builder rb, long prevLogIndex, boolean isHeartbeat) {
final long prevLogTerm = options.getLogManager().getTerm(prevLogIndex);
//说明当前是第一任期,节点刚启动或者刚从故障中恢复,试图从snapshot中恢复数据
if (prevLogTerm == 0 && prevLogIndex != 0) {
if (!isHeartbeat) {
Requires.requireTrue(prevLogIndex < options.getLogManager().getFirstLogIndex());
LOG.debug("logIndex={} was compacted", prevLogIndex);
return false;
} else {
// The log at prev_log_index has been compacted, which indicates
// we is or is going to install snapshot to the follower. So we let
// both prev_log_index and prev_log_term be 0 in the heartbeat
// request so that follower would do nothing besides updating its
// leader timestamp.
prevLogIndex = 0;
}
}
rb.setTerm(options.getTerm());
rb.setGroupId(options.getGroupId());
rb.setServerId(options.getServerId().toString());
rb.setPeerId(options.getPeerId().toString());
//当前leader的最新一条日志index。
rb.setPrevLogIndex(prevLogIndex);
rb.setPrevLogTerm(prevLogTerm);
rb.setCommittedIndex(options.getBallotBox().getLastCommittedIndex());
return true;
}
- 发送完rpc消息后,会执行
private void addInflight(RequestType reqType, long startIndex, int count, int size, int seq,
Future<Message> rpcInfly) {
this.rpcInFly = new Inflight(reqType, startIndex, count, size, seq, rpcInfly);
this.inflights.add(this.rpcInFly);
nodeMetrics.recordSize("replicate-inflights-count", this.inflights.size());
}
- 这里维护了两个数据结构,一个rpcInfly,表示最新发送的rpc消息。inflight这个list表示所有发送出去但还没有接受到返回的request集合。
- 当这个探针消息返回时,进入到onRpcReturned方法中。
- 这里要保证收到请求时的处理顺序和发送请求的顺序是一致的,这里用了一个优先级队列,每个rpcResponse携带一个发送时的request seq。按照seq从小到大排列。
if (queuedPipelinedResponse.seq != r.requiredNextSeq) {
if (processed > 0) {
if (isLogDebugEnabled) {
sb.append("has processed ").append(processed).append(" responses,");
}
break;
} else {
//Do not processed any responses, UNLOCK id and return.
continueSendEntries = false;
id.unlock();
return;
}
}
- 这里判断期望的response顺序和收到的response顺序是否一致。
- 如果顺序都一致,先发送的先收到response的话。进入到onAppendEntriesReturned逻辑。这一段时raft日志复制算法的实现。
private static boolean onAppendEntriesReturned(ThreadId id, Inflight inflight, Status status,
AppendEntriesRequest request, AppendEntriesResponse response,
long rpcSendTime, final long startTimeMs, Replicator r) {
if (inflight.startIndex != request.getPrevLogIndex() + 1) {
//inflight中记录对request信息和resonse中记录的不一致,直接重新发送probe,重新发送logEntry。
LOG.warn(
"Replicator {} received invalid AppendEntriesResponse, in-flight startIndex={}, requset prevLogIndex={}, reset the replicator state and probe again.",
r, inflight.startIndex, request.getPrevLogIndex());
r.resetInflights();
r.state = State.Probe;
//unlock id in sendEmptyEntries
r.sendEmptyEntries(false);
return false;
}
//record metrics
if (request.getEntriesCount() > 0) {
r.nodeMetrics.recordLatency("replicate-entries", Utils.nowMs() - rpcSendTime);
r.nodeMetrics.recordSize("replicate-entries-count", request.getEntriesCount());
r.nodeMetrics.recordSize("replicate-entries-bytes",
request.getData() != null ? request.getData().size() : 0);
}
//如果不开启debug,就不用耗费stringBuilder去拼接
final boolean isLogDebugEnabled = LOG.isDebugEnabled();
StringBuilder sb = null;
if (isLogDebugEnabled) {
sb = new StringBuilder("Node "). //
append(r.options.getGroupId()).append(":").append(r.options.getServerId()). //
append(" received AppendEntriesResponse from "). //
append(r.options.getPeerId()). //
append(" prevLogIndex=").append(request.getPrevLogIndex()). //
append(" prevLogTerm=").append(request.getPrevLogTerm()). //
append(" count=").append(request.getEntriesCount());
}
if (!status.isOk()) {
// If the follower crashes, any RPC to the follower fails immediately,
// so we need to block the follower for a while instead of looping until
// it comes back or be removed
// dummy_id is unlock in block
if (isLogDebugEnabled) {
sb.append(" fail, sleep.");
LOG.debug(sb.toString());
}
r.state = State.Probe;
if (++r.consecutiveErrorTimes % 10 == 0) {
LOG.warn("Fail to issue RPC to {}, consecutiveErrorTimes={}, error={}", r.options.getPeerId(),
r.consecutiveErrorTimes, status);
}
r.resetInflights();
//unlock in in block
r.block(startTimeMs, status.getCode());
return false;
}
r.consecutiveErrorTimes = 0;
if (!response.getSuccess()) {
// 本节点已经不是leader了,follower节点已经收到了更高term leader的信息。
if (response.getTerm() > r.options.getTerm()) {
if (isLogDebugEnabled) {
sb.append(" fail, greater term ").append(response.getTerm()).append(" expect term ")
.append(r.options.getTerm());
LOG.debug(sb.toString());
}
final NodeImpl node = r.options.getNode();
r.notifyOnCaughtUp(RaftError.EPERM.getNumber(), true);
r.destroy();
node.increaseTermTo(response.getTerm(), new Status(RaftError.EHIGHERTERMRESPONSE,
"Leader receives higher term hearbeat_response from peer:%s", r.options.getPeerId()));
return false;
}
if (isLogDebugEnabled) {
sb.append(" fail, find nextIndex remote lastLogIndex ").append(response.getLastLogIndex())
.append(" local nextIndex ").append(r.nextIndex);
LOG.debug(sb.toString());
}
if (rpcSendTime > r.lastRpcSendTimestamp) {
r.lastRpcSendTimestamp = rpcSendTime;
}
//Fail, reset the state to try again from nextIndex.
r.resetInflights();
// prev_log_index and prev_log_term doesn't match
if (response.getLastLogIndex() + 1 < r.nextIndex) {
// 这里说明,follower节点还落后于当前leader节点的日志状态。
LOG.debug("LastLogIndex at peer={} is {}", r.options.getPeerId(), response.getLastLogIndex());
// The peer contains less logs than leader
r.nextIndex = response.getLastLogIndex() + 1;
} else {
// The peer contains logs from old term which should be truncated,
// decrease _last_log_at_peer by one to test the right index to keep
//这里说明follower节点的日志超前于leader节点,leader 下标回退,直到和follower匹配
if (r.nextIndex > 1) {
LOG.debug("logIndex={} dismatch", r.nextIndex);
r.nextIndex--;
} else {
LOG.error("Peer={} declares that log at index=0 doesn't match, which is not supposed to happen",
r.options.getPeerId());
}
}
// dummy_id is unlock in _send_heartbeat
r.sendEmptyEntries(false);
return false;
}
if (isLogDebugEnabled) {
sb.append(", success");
LOG.debug(sb.toString());
}
// success
if (response.getTerm() != r.options.getTerm()) {
r.resetInflights();
r.state = State.Probe;
LOG.error("Fail, response term {} dismatch, expect term {}", response.getTerm(), r.options.getTerm());
id.unlock();
return false;
}
if (rpcSendTime > r.lastRpcSendTimestamp) {
r.lastRpcSendTimestamp = rpcSendTime;
}
final int entriesSize = request.getEntriesCount();
if (entriesSize > 0) {
//记录对应的follower已经成功写入logEntry,超过半数时,应用到stateMachine
r.options.getBallotBox().commitAt(r.nextIndex, r.nextIndex + entriesSize - 1, r.options.getPeerId());
if (LOG.isDebugEnabled()) {
LOG.debug("Replicated logs in [{}, {}] to peer {}", r.nextIndex, r.nextIndex + entriesSize - 1,
r.options.getPeerId());
}
} else {
//The request is probe request, change the state into Replicate.
r.state = State.Replicate;
}
//对于探针信息,entriesSize为0,下一次要发送的日志下标为nextIndex。
//如果不是探针信息,说明这次发送的entriesSize个logEntry已经写入到follower的日志里了。
r.nextIndex += entriesSize;
r.hasSucceeded = true;
r.notifyOnCaughtUp(0, false);
// dummy_id is unlock in _send_entries
if (r.timeoutNowIndex > 0 && r.timeoutNowIndex < r.nextIndex) {
r.sendTimeoutNow(false, false);
}
return true;
}
- 当收到follower的返回时,如果:
- 任何异常,队列里最前面的response出问题了,所有已发送未返回的信息全部丢弃,重发。
- reponse返回的follower的最新的日志index>leader当前最新的日志index,则follower需要删除
- reponse返回的follower的最新的日志index<leader当前最新的日志index,则复制leader日志到follower。
jraft日志复制的更多相关文章
- Paxos 实现日志复制同步
Paxos 实现日志复制同步 本篇文章以 John Ousterhout(斯坦福大学教授) 和 Diego Ongaro(斯坦福大学获得博士学位,Raft算法发明人) 在 Youtube 上的讲解视频 ...
- Paxos 实现日志复制同步(Multi-Paxos)
Paxos 实现日志复制同步 这篇文章以一种易于理解的方式来解释 Multi-Paxos 的机制. Multi-Paxos 的是为了创建日志复制 一种实现方式是用一组基础 Paxos 实例,每条记录都 ...
- Raft 实现日志复制同步
Raft 实现日志复制同步 本篇文章以 John Ousterhout(斯坦福大学教授) 和 Diego Ongaro(斯坦福大学获得博士学位,Raft算法发明人) 在 Youtube 上的讲解视频及 ...
- 解读Raft(二 选举和日志复制)
Leader election Raft采用心跳机制来触发Leader选举.Leader周期性的发送心跳(如果有正常的RPC的请求情况下可以不发心跳)包保持自己Leader的角色(避免集群中其他节点认 ...
- 图解Raft之日志复制
日志复制可以说是Raft集群的核心之一,保证了Raft数据的一致性,下面通过几张图片介绍Raft集群中日志复制的逻辑与流程: 在一个Raft集群中只有Leader节点能够接受客户端的请求,由Leade ...
- Paxos 实现日志复制同步(Basic Paxos)
Paxos 实现日志复制同步 本篇文章以 John Ousterhout(斯坦福大学教授) 和 Diego Ongaro(斯坦福大学获得博士学位,Raft算法发明人) 在 Youtube 上的讲解视频 ...
- 8. SOFAJRaft源码分析— 如何实现日志复制的pipeline机制?
前言 前几天和腾讯的大佬一起吃饭聊天,说起我对SOFAJRaft的理解,我自然以为我是很懂了的,但是大佬问起了我那SOFAJRaft集群之间的日志是怎么复制的? 我当时哑口无言,说不出是怎么实现的,所 ...
- 源码分析 RocketMQ DLedger(多副本) 之日志复制(传播)
目录 1.DLedgerEntryPusher 1.1 核心类图 1.2 构造方法 1.3 startup 2.EntryDispatcher 详解 2.1 核心类图 2.2 Push 请求类型 2. ...
- 基于 raft 协议的 RocketMQ DLedger 多副本日志复制设计原理
目录 1.RocketMQ DLedger 多副本日志复制流程图 1.1 RocketMQ DLedger 日志转发(append) 请求流程图 1.2 RocketMQ DLedger 日志仲裁流程 ...
随机推荐
- Puppeteer爬虫实战(二)
连接浏览器 上一篇说到了Puppeteer本质是使用了Chrome Devtools协议控制浏览器,本篇就说说连接方式. 常规Hook浏览器 此方式其实就是需要一个浏览器可执行文件(不同平台需要下载对 ...
- 虚拟化-SDDC软件定义数据中心
一.什么是SDDC? SDDC依赖于虚拟化和云计算技术, SDDC的目标是虚拟化数据中心的一切物理资源,通过虚拟化的技术,构建一个由虚拟资源组成的资源池,不仅是对服务器进行虚拟化,还包括存储虚拟化和网 ...
- django 学习记录(一)
不使用 drf 来实现django 的 api 接口 json序列化 from django.shortcuts import render from django.views.generic.bas ...
- js原型、原型链
之前有说过继承,在js中没有类,所以在new的后面,放的是构造函数,在构造函数中有一个属性prototype,js的继承全靠它. 在js中对象的类型有很多,常见的就是普通对象,和函数对象,在对象中都会 ...
- element ui表格实现单选 但是单选取消会报错
1.在el-table中添加两个事件 @selection-change="handleSelectionChange" @current-change="choos ...
- 大数据篇:一文读懂@数据仓库(PPT文字版)
大数据篇:一文读懂@数据仓库 1 网络词汇总结 1.1 数据中台 数据中台是聚合和治理跨域数据,将数据抽象封装成服务,提供给前台以业务价值的逻辑概念. 数据中台是一套可持续"让企业的数据用起 ...
- JSONObject遍历
导入JSONObject和JSONArray所需要的jar包 JSONObject所必需的6个jar包: commons-beanutils-1.7.0.jar commons-collections ...
- arcgis for js 如何用contains过滤数据
添加全部数据 // 构建map容器 var view = new MapView({ container: 'mapId', map: map }); /******************** * ...
- random随机数函数
- 什么是PHP 面向对象
PHP 面向对象 在面向对象的程序设计(英语:Object-oriented programming,缩写:OOP)中,对象是一个由信息及对信息进行处理的描述所组成的整体,是对现实世界的抽象. 在现实 ...