微服务-Nacos数据一致性

 

Nacos数据一致性

目录

一、Raft算法

二、Nacos中Raft部分源码

init()

1. 获取Raft集群节点 

NamingProxy.getServers()获取集群节点

NamingProxy.refreshSrvIfNeed()得到节点信息

NamingProxy.refreshServerListFromDisk()获取集群节点信息

2. Raft集群数据恢复

RaftStore.load()

3. Raft选举

GlobalExecutor.register(new MasterElection())注册选举定时任务

MasterElection.sendVote()发送定时任务

(1)RaftCommands.vote()处理/v1/ns/raft/vote请求

(2)PeerSet.decideLeader()选举

4. Raft心跳

GlobalExecutor.register(new HeartBeat())注册心跳定时任务

HeartBeat.sendBeat()发送心跳包

(·)RaftCommands.beat()方法处理/v1/ns/raft/beat请求

5. Raft发布内容

注册入口

实例信息持久化

(1)Service.put()

(2)RaftCore.signalPublish()

(3)/raft/datum 接口 和 /raft/datum/commit 接口

发布入口 RaftCommands.publish()

6. Raft保证内容一致性


一、Raft算法

Raft通过当选的领导者达成共识。筏集群中的服务器是领导者或追随者,并且在选举的精确情况下可以是候选者(领导者不可用)。领导者负责将日志复制到关注者。它通过发送心跳消息定期通知追随者它的存在。每个跟随者都有一个超时(通常在150到300毫秒之间),它期望领导者的心跳。接收心跳时重置超时。如果没有收到心跳,则关注者将其状态更改为候选人并开始领导选举。

详见:Raft算法

二、Nacos中Raft部分源码

Nacos server在启动时,会通过RunningConfig.onApplicationEvent()方法调用RaftCore.init()方法。

init()

 
  1. public static void init() throws Exception {

  2.  
  3. Loggers.RAFT.info("initializing Raft sub-system");

  4.  
  5. // 启动Notifier,轮询Datums,通知RaftListener

  6. executor.submit(notifier);

  7.  
  8. // 获取Raft集群节点,更新到PeerSet中

  9. peers.add(NamingProxy.getServers());

  10.  
  11. long start = System.currentTimeMillis();

  12.  
  13. // 从磁盘加载Datum和term数据进行数据恢复

  14. RaftStore.load();

  15.  
  16. Loggers.RAFT.info("cache loaded, peer count: {}, datum count: {}, current term: {}",

  17. peers.size(), datums.size(), peers.getTerm());

  18.  
  19. while (true) {

  20. if (notifier.tasks.size() <= 0) {

  21. break;

  22. }

  23. Thread.sleep(1000L);

  24. System.out.println(notifier.tasks.size());

  25. }

  26.  
  27. Loggers.RAFT.info("finish to load data from disk, cost: {} ms.", (System.currentTimeMillis() - start));

  28.  
  29. GlobalExecutor.register(new MasterElection()); // Leader选举

  30. GlobalExecutor.register1(new HeartBeat()); // Raft心跳

  31. GlobalExecutor.register(new AddressServerUpdater(), GlobalExecutor.ADDRESS_SERVER_UPDATE_INTERVAL_MS);

  32.  
  33. if (peers.size() > 0) {

  34. if (lock.tryLock(INIT_LOCK_TIME_SECONDS, TimeUnit.SECONDS)) {

  35. initialized = true;

  36. lock.unlock();

  37. }

  38. } else {

  39. throw new Exception("peers is empty.");

  40. }

  41.  
  42. Loggers.RAFT.info("timer started: leader timeout ms: {}, heart-beat timeout ms: {}",

  43. GlobalExecutor.LEADER_TIMEOUT_MS, GlobalExecutor.HEARTBEAT_INTERVAL_MS);

  44. }

在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()进行投票。
 
  1. public class MasterElection implements Runnable {

  2. @Override

  3. public void run() {

  4. try {

  5. if (!peers.isReady()) {

  6. return;

  7. }

  8.  
  9. RaftPeer local = peers.local();

  10. local.leaderDueMs -= GlobalExecutor.TICK_PERIOD_MS;

  11. if (local.leaderDueMs > 0) {

  12. return;

  13. }

  14.  
  15. // 重置选举超时时间,每次心跳以及收到数据包都会重置

  16. local.resetLeaderDue();

  17. local.resetHeartbeatDue();

  18.  
  19. // 发起选举

  20. sendVote();

  21. } catch (Exception e) {

  22. Loggers.RAFT.warn("[RAFT] error while master election {}", e);

  23. }

  24. }

  25. }

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。

 
  1. public void sendVote() {

  2.  
  3. RaftPeer local = peers.get(NetUtils.localServer());

  4. Loggers.RAFT.info("leader timeout, start voting,leader: {}, term: {}",

  5. JSON.toJSONString(getLeader()), local.term);

  6.  
  7. //重置Raft集群数据

  8. peers.reset();

  9.  
  10. //更新候选节点数据

  11. local.term.incrementAndGet();

  12. local.voteFor = local.ip;

  13. local.state = RaftPeer.State.CANDIDATE;

  14.  
  15.  
  16. //候选节点向除自身之外的所有其它Raft节点的/v1/ns/raft/vote发送HTTP POST请求

  17. //请求内容为vote:JSON.toJSONString(local)

  18. Map<String, String> params = new HashMap<String, String>(1);

  19. params.put("vote", JSON.toJSONString(local));

  20. for (final String server : peers.allServersWithoutMySelf()) {

  21. final String url = buildURL(server, API_VOTE);

  22. try {

  23. HttpClient.asyncHttpPost(url, null, params, new AsyncCompletionHandler<Integer>() {

  24. @Override

  25. public Integer onCompleted(Response response) throws Exception {

  26. if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {

  27. Loggers.RAFT.error("NACOS-RAFT vote failed: {}, url: {}", response.getResponseBody(), url);

  28. return 1;

  29. }

  30.  
  31. RaftPeer peer = JSON.parseObject(response.getResponseBody(), RaftPeer.class);

  32.  
  33. Loggers.RAFT.info("received approve from peer: {}", JSON.toJSONString(peer));

  34.  
  35. //候选节点收到其他节点投的候选节点数据,交给PeerSet.decideLeader

  36. //方法处理

  37. peers.decideLeader(peer);

  38.  
  39. return 0;

  40. }

  41. });

  42. } catch (Exception e) {

  43. Loggers.RAFT.warn("error while sending vote to server: {}", server);

  44. }

  45. }

  46. }

  47. }

(1)RaftCommands.vote()处理/v1/ns/raft/vote请求

选举请求的 http 接口

 
  1. @RestController

  2. @RequestMapping(UtilsAndCommons.NACOS_NAMING_CONTEXT + "/raft")

  3. public class RaftController {

  4.  
  5. ......

  6.  
  7. @NeedAuth

  8. @RequestMapping(value = "/vote", method = RequestMethod.POST)

  9. public JSONObject vote(HttpServletRequest request, HttpServletResponse response) throws Exception {

  10. // 处理选举请求

  11. RaftPeer peer = raftCore.receivedVote(

  12. JSON.parseObject(WebUtils.required(request, "vote"), RaftPeer.class));

  13.  
  14. return JSON.parseObject(JSON.toJSONString(peer));

  15. }

  16.  
  17.  
  18. ......

  19. }

调用RaftCore.MasterElection.receivedVote()方法

如果收到的候选节点term比本地节点term要小,则:

                   本地节点的voteFor更新为自己;(意思是我自己更适合做leader,这一票我投给自己)

否则:

         &

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值