Nacos数据一致性
目录
NamingProxy.getServers()获取集群节点
NamingProxy.refreshSrvIfNeed()得到节点信息
NamingProxy.refreshServerListFromDisk()获取集群节点信息
GlobalExecutor.register(new MasterElection())注册选举定时任务
MasterElection.sendVote()发送定时任务
(1)RaftCommands.vote()处理/v1/ns/raft/vote请求
GlobalExecutor.register(new HeartBeat())注册心跳定时任务
(·)RaftCommands.beat()方法处理/v1/ns/raft/beat请求
(3)/raft/datum 接口 和 /raft/datum/commit 接口
一、Raft算法
Raft通过当选的领导者达成共识。筏集群中的服务器是领导者或追随者,并且在选举的精确情况下可以是候选者(领导者不可用)。领导者负责将日志复制到关注者。它通过发送心跳消息定期通知追随者它的存在。每个跟随者都有一个超时(通常在150到300毫秒之间),它期望领导者的心跳。接收心跳时重置超时。如果没有收到心跳,则关注者将其状态更改为候选人并开始领导选举。
详见:Raft算法
二、Nacos中Raft部分源码
Nacos server在启动时,会通过RunningConfig.onApplicationEvent()方法调用RaftCore.init()方法。
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方法主要做了如下几件事:
- 1. 获取Raft集群节点 peers.add(NamingProxy.getServers());
- 2. Raft集群数据恢复 RaftStore.load();
- 3. Raft选举 GlobalExecutor.register(new MasterElection());
- 4. Raft心跳 GlobalExecutor.register(new HeartBeat());
- 5. Raft发布内容
- 6. Raft保证内容一致性
1. 获取Raft集群节点
NamingProxy.getServers()获取集群节点
- NamingProxy.refreshSrvIfNeed()得到节点信息
- 返回List<String> servers
NamingProxy.refreshSrvIfNeed()得到节点信息
-
如果单机模式
则本主机的ip:port为Raft节点信息;
否则
调用下面的NamingProxy.refreshServerListFromDisk()获取Raft集群节点信息
-
获取到Raft集群节点信息之后(即ip:port列表),更新NamingProxy的List<String> serverlistFromConfig属性和List<String> servers属性。
NamingProxy.refreshServerListFromDisk()获取集群节点信息
从磁盘或系统环境变量种读取Raft集群节点信息,即ip:port列表
2. Raft集群数据恢复
Nacos启动/重启时会从磁盘加载Datum和term数据进行数据恢复。
nacos server端启动后->RaftCore.init()方法->RaftStore.load()方法。
RaftStore.load()
-
从磁盘获取Datum数据:
将Datum放到RaftCore的ConcurrentMap<String, Datum> datums集合中,key为Datum的key;
将Datum和ApplyAction.CHANGE封装成Pair放到Notifier的tasks队列中,通知相关的RaftListener;
-
从META_FILE_NAME:<user.home>\nacos\raft\meta.properties获取任期term值(long值):
调用RaftSet.setTerm(long term)方法更新Raft集群中每个节点的term值
3. Raft选举
GlobalExecutor.register(new MasterElection())注册选举定时任务
Nacos的Raft选举是通过MasterElection这个线程任务完成的。
- 更新候选节点的election timeout、heart timeout。
- 调用MasterElection.sendVote()进行投票。
-
public class MasterElection implements Runnable {
-
@Override
-
public void run() {
-
try {
-
if (!peers.isReady()) {
-
return;
-
}
-
RaftPeer local = peers.local();
-
local.leaderDueMs -= GlobalExecutor.TICK_PERIOD_MS;
-
if (local.leaderDueMs > 0) {
-
return;
-
}
-
// 重置选举超时时间,每次心跳以及收到数据包都会重置
-
local.resetLeaderDue();
-
local.resetHeartbeatDue();
-
// 发起选举
-
sendVote();
-
} catch (Exception e) {
-
Loggers.RAFT.warn("[RAFT] error while master election {}", e);
-
}
-
}
-
}
MasterElection.sendVote()发送定时任务
- 重置Raft集群数据:
leader置为null; 所有Raft节点的voteFor字段置为null;
- 更新候选节点数据:
任期term自增1;(通过自增1制造和其它节点的term差异,避免所有节点term一样选举不出Leader)
候选节点的voteFor字段设置为自己;
state置为CANDIDATE;
- 候选节点向除自身之外的所有其它Raft节点的/v1/ns/raft/vote发送HTTP POST请求:
请求内容为vote:JSON.toJSONString(local)
- 候选节点收到其他节点投的候选节点数据,交给PeerSet.decideLeader()方法处理
把超半数的voteFor对应的RaftPerr设置为Leader。
-
public void sendVote() {
-
RaftPeer local = peers.get(NetUtils.localServer());
-
Loggers.RAFT.info("leader timeout, start voting,leader: {}, term: {}",
-
JSON.toJSONString(getLeader()), local.term);
-
//重置Raft集群数据
-
peers.reset();
-
//更新候选节点数据
-
local.term.incrementAndGet();
-
local.voteFor = local.ip;
-
local.state = RaftPeer.State.CANDIDATE;
-
//候选节点向除自身之外的所有其它Raft节点的/v1/ns/raft/vote发送HTTP POST请求
-
//请求内容为vote:JSON.toJSONString(local)
-
Map<String, String> params = new HashMap<String, String>(1);
-
params.put("vote", JSON.toJSONString(local));
-
for (final String server : peers.allServersWithoutMySelf()) {
-
final String url = buildURL(server, API_VOTE);
-
try {
-
HttpClient.asyncHttpPost(url, null, params, new AsyncCompletionHandler<Integer>() {
-
@Override
-
public Integer onCompleted(Response response) throws Exception {
-
if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
-
Loggers.RAFT.error("NACOS-RAFT vote failed: {}, url: {}", response.getResponseBody(), url);
-
return 1;
-
}
-
RaftPeer peer = JSON.parseObject(response.getResponseBody(), RaftPeer.class);
-
Loggers.RAFT.info("received approve from peer: {}", JSON.toJSONString(peer));
-
//候选节点收到其他节点投的候选节点数据,交给PeerSet.decideLeader
-
//方法处理
-
peers.decideLeader(peer);
-
return 0;
-
}
-
});
-
} catch (Exception e) {
-
Loggers.RAFT.warn("error while sending vote to server: {}", server);
-
}
-
}
-
}
-
}
(1)RaftCommands.vote()处理/v1/ns/raft/vote请求
选举请求的 http 接口
-
@RestController
-
@RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft")
-
public class RaftController {
-
......
-
@NeedAuth
-
@RequestMapping(value = "/vote", method = RequestMethod.POST)
-
public JSONObject vote(HttpServletRequest request, HttpServletResponse response) throws Exception {
-
// 处理选举请求
-
RaftPeer peer = raftCore.receivedVote(
-
JSON.parseObject(WebUtils.required(request, "vote"), RaftPeer.class));
-
return JSON.parseObject(JSON.toJSONString(peer));
-
}
-
......
-
}
调用RaftCore.MasterElection.receivedVote()方法
如果收到的候选节点term比本地节点term要小,则:
本地节点的voteFor更新为自己;(意思是我自己更适合做leader,这一票我投给自己)
否则:
&