一、快速了解Raft算法
Raft 适用于一個管理日志一緻性的協定,相比于 Paxos 協定 Raft 更易于了解和去實作它。
為了提高了解性,Raft 将一緻性算法分為了幾個部分,包括上司選取(leader selection)、日志複制(log replication)、安全(safety),并且使用了更強的一緻性來減少了必須需要考慮的狀态。
相比Paxos,Raft算法了解起來更加直覺。
Raft算法将Server劃分為3種狀态,或者也可以稱作角色:
-
Leader
負責Client互動和log複制,同一時刻系統中最多存在1個。
-
Follower
被動響應請求RPC,從不主動發起請求RPC。
- Candidate
一種臨時的角色,隻存在于leader的選舉階段,某個節點想要變成leader,那麼就發起投票請求,同時自己變成candidate。如果選舉成功,則變為candidate,否則退回為follower
狀态或者說角色的流轉如下:
在Raft中,問題分解為:上司選取、日志複制、安全和成員變化。
複制狀态機通過複制日志來實作:
日志:每台機器儲存一份日志,日志來自于用戶端的請求,包含一系列的指令
狀态機:狀态機會按順序執行這些指令
一緻性模型:分布式環境下,保證多機的日志是一緻的,這樣回放到狀态機中的狀态是一緻的
Raft算法選主流程
Raft中有Term的概念,Term類比中國曆史上的朝代更替,Raft 算法将時間劃分成為任意不同長度的任期(term)。
選舉流程
1、follower增加目前的term,轉變為candidate。
2、candidate投票給自己,并發送RequestVote RPC給叢集中的其他伺服器。
3、收到RequestVote的伺服器,在同一term中隻會按照先到先得投票給至多一個candidate。且隻會投票給log至少和自身一樣新的candidate。
關于Raft更詳細的描述,可以檢視這裡,
從分布式一緻性到共識機制(二)Raft算法二、Nacos中的CP一緻性
Spring Cloud Alibaba Nacos 在 1.0.0 正式支援 AP 和 CP 兩種一緻性協定,其中的CP一緻性協定實作,是基于簡化的 Raft 的 CP 一緻性。
如何實作Raft算法
Nacos server在啟動時,會通過RunningConfig.onApplicationEvent()方法調用RaftCore.init()方法。
啟動選舉
public static void init() throws Exception {
Loggers.RAFT.info("initializing Raft sub-system");
// 啟動Notifier,輪詢Datums,通知RaftListener
executor.submit(notifier);
// 擷取Raft叢集節點,更新到PeerSet中
peers.add(NamingProxy.getServers());
long start = System.currentTimeMillis();
// 從磁盤加載Datum和term資料進行資料恢複
RaftStore.load();
Loggers.RAFT.info("cache loaded, peer count: {}, datum count: {}, current term: {}",
peers.size(), datums.size(), peers.getTerm());
while (true) {
if (notifier.tasks.size() <= 0) {
break;
}
Thread.sleep(1000L);
System.out.println(notifier.tasks.size());
}
Loggers.RAFT.info("finish to load data from disk, cost: {} ms.", (System.currentTimeMillis() - start));
GlobalExecutor.register(new MasterElection()); // Leader選舉
GlobalExecutor.register1(new HeartBeat()); // Raft心跳
GlobalExecutor.register(new AddressServerUpdater(), GlobalExecutor.ADDRESS_SERVER_UPDATE_INTERVAL_MS);
if (peers.size() > 0) {
if (lock.tryLock(INIT_LOCK_TIME_SECONDS, TimeUnit.SECONDS)) {
initialized = true;
lock.unlock();
}
} else {
throw new Exception("peers is empty.");
}
Loggers.RAFT.info("timer started: leader timeout ms: {}, heart-beat timeout ms: {}",
GlobalExecutor.LEADER_TIMEOUT_MS, GlobalExecutor.HEARTBEAT_INTERVAL_MS);
}
在init方法主要做了如下幾件事:
- 擷取Raft叢集節點 peers.add(NamingProxy.getServers());
- Raft叢集資料恢複 RaftStore.load();
- Raft選舉 GlobalExecutor.register(new MasterElection());
- Raft心跳 GlobalExecutor.register(new HeartBeat());
- Raft釋出内容
- Raft保證内容一緻性
其中,raft叢集内部節點間是通過暴露的Restful接口,代碼在 RaftController 中。
RaftController控制器是raft叢集内部節點間通信使用的,具體的資訊如下
POST HTTP://{ip:port}/v1/ns/raft/vote : 進行投票請求
POST HTTP://{ip:port}/v1/ns/raft/beat : Leader向Follower發送心跳資訊
GET HTTP://{ip:port}/v1/ns/raft/peer : 擷取該節點的RaftPeer資訊
PUT HTTP://{ip:port}/v1/ns/raft/datum/reload : 重新加載某日志資訊
POST HTTP://{ip:port}/v1/ns/raft/datum : Leader接收傳來的資料并存入
DELETE HTTP://{ip:port}/v1/ns/raft/datum : Leader接收傳來的資料删除操作
GET HTTP://{ip:port}/v1/ns/raft/datum : 擷取該節點存儲的資料資訊
GET HTTP://{ip:port}/v1/ns/raft/state : 擷取該節點的狀态資訊{UP or DOWN}
POST HTTP://{ip:port}/v1/ns/raft/datum/commit : Follower節點接收Leader傳來得到資料存入操作
DELETE HTTP://{ip:port}/v1/ns/raft/datum : Follower節點接收Leader傳來的資料删除操作
GET HTTP://{ip:port}/v1/ns/raft/leader : 擷取目前叢集的Leader節點資訊
GET HTTP://{ip:port}/v1/ns/raft/listeners : 擷取目前Raft叢集的所有事件監聽者
RaftPeerSet
心跳機制
Raft中使用心跳機制來觸發leader選舉。心跳定時任務是在GlobalExecutor 中,
通過 GlobalExecutor.register(new HeartBeat())注冊心跳定時任務,具體操作包括:
- 重置Leader節點的heart timeout、election timeout;
- sendBeat()發送心跳包
public class HeartBeat implements Runnable {
@Override
public void run() {
try {
if (!peers.isReady()) {
return;
}
RaftPeer local = peers.local();
local.heartbeatDueMs -= GlobalExecutor.TICK_PERIOD_MS;
if (local.heartbeatDueMs > 0) {
return;
}
local.resetHeartbeatDue();
sendBeat();
} catch (Exception e) {
Loggers.RAFT.warn("[RAFT] error while sending beat {}", e);
}
}
}
簡單說明了下Nacos中的Raft一緻性實作,更詳細的流程,可以下載下傳源碼,檢視 RaftCore 進行了解。源碼可以通過以下位址檢出:
git clone https://github.com/alibaba/nacos.git