一口糕、快速了解Raft算法
Raft 適用于一個(gè)管理日志一致性的協(xié)議缅阳,相比于 Paxos 協(xié)議 Raft 更易于理解和去實(shí)現(xiàn)它。
為了提高理解性景描,Raft 將一致性算法分為了幾個(gè)部分十办,包括領(lǐng)導(dǎo)選取(leader selection)超棺、日志復(fù)制(log replication)向族、安全(safety),并且使用了更強(qiáng)的一致性來減少了必須需要考慮的狀態(tài)棠绘。
相比Paxos件相,Raft算法理解起來更加直觀。
Raft算法將Server劃分為3種狀態(tài)弄唧,或者也可以稱作角色:
Leader
負(fù)責(zé)Client交互和log復(fù)制适肠,同一時(shí)刻系統(tǒng)中最多存在1個(gè)。Follower
被動(dòng)響應(yīng)請(qǐng)求RPC候引,從不主動(dòng)發(fā)起請(qǐng)求RPC侯养。Candidate
一種臨時(shí)的角色,只存在于leader的選舉階段澄干,某個(gè)節(jié)點(diǎn)想要變成leader逛揩,那么就發(fā)起投票請(qǐng)求,同時(shí)自己變成candidate麸俘。如果選舉成功辩稽,則變?yōu)閏andidate,否則退回為follower
狀態(tài)或者說角色的流轉(zhuǎn)如下:
在Raft中从媚,問題分解為:領(lǐng)導(dǎo)選取逞泄、日志復(fù)制、安全和成員變化。
復(fù)制狀態(tài)機(jī)通過復(fù)制日志來實(shí)現(xiàn):
日志:每臺(tái)機(jī)器保存一份日志喷众,日志來自于客戶端的請(qǐng)求各谚,包含一系列的命令
狀態(tài)機(jī):狀態(tài)機(jī)會(huì)按順序執(zhí)行這些命令
一致性模型:分布式環(huán)境下,保證多機(jī)的日志是一致的到千,這樣回放到狀態(tài)機(jī)中的狀態(tài)是一致的
Raft算法選主流程
Raft中有Term的概念昌渤,Term類比中國(guó)歷史上的朝代更替,Raft 算法將時(shí)間劃分成為任意不同長(zhǎng)度的任期(term)憔四。
選舉流程
1膀息、follower增加當(dāng)前的term,轉(zhuǎn)變?yōu)閏andidate了赵。
2潜支、candidate投票給自己,并發(fā)送RequestVote RPC給集群中的其他服務(wù)器斟览。
3毁腿、收到RequestVote的服務(wù)器辑奈,在同一term中只會(huì)按照先到先得投票給至多一個(gè)candidate苛茂。且只會(huì)投票給log至少和自身一樣新的candidate。
關(guān)于Raft更詳細(xì)的描述鸠窗,可以查看這里妓羊,從分布式一致性到共識(shí)機(jī)制(二)Raft算法
二、Nacos中的CP一致性
Spring Cloud Alibaba Nacos 在 1.0.0 正式支持 AP 和 CP 兩種一致性協(xié)議稍计,其中的CP一致性協(xié)議實(shí)現(xiàn)躁绸,是基于簡(jiǎn)化的 Raft 的 CP 一致性。
如何實(shí)現(xiàn)Raft算法
Nacos server在啟動(dòng)時(shí)臣嚣,會(huì)通過RunningConfig.onApplicationEvent()方法調(diào)用RaftCore.init()方法净刮。
啟動(dòng)選舉
public static void init() throws Exception {
Loggers.RAFT.info("initializing Raft sub-system");
// 啟動(dòng)Notifier,輪詢Datums硅则,通知RaftListener
executor.submit(notifier);
// 獲取Raft集群節(jié)點(diǎn)淹父,更新到PeerSet中
peers.add(NamingProxy.getServers());
long start = System.currentTimeMillis();
// 從磁盤加載Datum和term數(shù)據(jù)進(jìn)行數(shù)據(jù)恢復(fù)
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集群節(jié)點(diǎn) peers.add(NamingProxy.getServers());
- Raft集群數(shù)據(jù)恢復(fù) RaftStore.load();
- Raft選舉 GlobalExecutor.register(new MasterElection());
- Raft心跳 GlobalExecutor.register(new HeartBeat());
- Raft發(fā)布內(nèi)容
- Raft保證內(nèi)容一致性
選舉流程
其中,raft集群內(nèi)部節(jié)點(diǎn)間是通過暴露的Restful接口怎虫,代碼在 RaftController 中暑认。
RaftController控制器是raft集群內(nèi)部節(jié)點(diǎn)間通信使用的,具體的信息如下
POST HTTP://{ip:port}/v1/ns/raft/vote : 進(jìn)行投票請(qǐng)求
POST HTTP://{ip:port}/v1/ns/raft/beat : Leader向Follower發(fā)送心跳信息
GET HTTP://{ip:port}/v1/ns/raft/peer : 獲取該節(jié)點(diǎn)的RaftPeer信息
PUT HTTP://{ip:port}/v1/ns/raft/datum/reload : 重新加載某日志信息
POST HTTP://{ip:port}/v1/ns/raft/datum : Leader接收傳來的數(shù)據(jù)并存入
DELETE HTTP://{ip:port}/v1/ns/raft/datum : Leader接收傳來的數(shù)據(jù)刪除操作
GET HTTP://{ip:port}/v1/ns/raft/datum : 獲取該節(jié)點(diǎn)存儲(chǔ)的數(shù)據(jù)信息
GET HTTP://{ip:port}/v1/ns/raft/state : 獲取該節(jié)點(diǎn)的狀態(tài)信息{UP or DOWN}
POST HTTP://{ip:port}/v1/ns/raft/datum/commit : Follower節(jié)點(diǎn)接收Leader傳來得到數(shù)據(jù)存入操作
DELETE HTTP://{ip:port}/v1/ns/raft/datum : Follower節(jié)點(diǎn)接收Leader傳來的數(shù)據(jù)刪除操作
GET HTTP://{ip:port}/v1/ns/raft/leader : 獲取當(dāng)前集群的Leader節(jié)點(diǎn)信息
GET HTTP://{ip:port}/v1/ns/raft/listeners : 獲取當(dāng)前Raft集群的所有事件監(jiān)聽者
RaftPeerSet
心跳機(jī)制
Raft中使用心跳機(jī)制來觸發(fā)leader選舉大审。心跳定時(shí)任務(wù)是在GlobalExecutor 中蘸际,
通過 GlobalExecutor.register(new HeartBeat())注冊(cè)心跳定時(shí)任務(wù),具體操作包括:
- 重置Leader節(jié)點(diǎn)的heart timeout徒扶、election timeout粮彤;
- sendBeat()發(fā)送心跳包
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);
}
}
}
簡(jiǎn)單說明了下Nacos中的Raft一致性實(shí)現(xiàn),更詳細(xì)的流程,可以下載源碼导坟,查看 RaftCore 進(jìn)行了解缠诅。源碼可以通過以下地址檢出:
git clone https://github.com/alibaba/nacos.git
掃碼關(guān)注公眾號(hào):架構(gòu)進(jìn)化論,獲得第一手的技術(shù)資訊和原創(chuàng)文章