nacos服务注册之服务器端Raft
Raft是持久化,数据存储在\nacos\data\naming\data目录
nacos启动后首先从数据存储目录加载数据
Raft协议中节点只有一个LEADER,只有LEADER节点负责数据写入,FOLLOWER节点接受到写入请求后转发给LEADER节点处理
Raft协议中LEADER节点接受写入请求后首先写入本机,然后同步到集群中其他节点,许超过半数节点返回成功,才认为写入成功。
Raft协议中LEADER定时发送心跳数据(包含全量数据)同步FOLLOWER。
Raft存储代码分析; RaftStore类负责数据的存储,数据存储在\nacos\data\naming\data\public(namespaceId)目录,
com.alibaba.nacos.naming.domains.meta.public##@@nacos.test.5
com.alibaba.nacos.naming.iplist.public##@@nacos.test.5
@Component
public class RaftStore {
/**
* 数据持久化到文件,文件内容就json字符串
* @param datum
* @throws Exception
*/
public synchronized void write(final Datum datum) throws Exception {
String namespaceId = KeyBuilder.getNamespace(datum.key);
File cacheFile = new File(cacheDir + File.separator + namespaceId + File.separator + encodeFileName(datum.key));
FileChannel fc = null;
ByteBuffer data = ByteBuffer.wrap(JSON.toJSONString(datum).getBytes(StandardCharsets.UTF_8));
try {
fc = new FileOutputStream(cacheFile, false).getChannel();
fc.write(data, data.position());
fc.force(true);
} catch (Exception e) {
MetricsMonitor.getDiskException().increment();
throw e;
} finally {
if (fc != null) {
fc.close();
}
}
}
}
Raft服务注册源码分析:
只有LEADER节点负责数据写入,FOLLOWER节点接受到写入请求后转发给LEADER节点处理
LEADER节点接受写入请求后首先写入本机,然后同步到集群中其他节点,许超过半数节点返回成功,才认为写入成功。
@Component
public class RaftCore {
/**
* 服务注册
* @param key
* @param value
* @throws Exception
*/
public void signalPublish(String key, Record value) throws Exception {
//如果是FOLLOWER节点则转发到LEADER节点处理
if (!isLeader()) {
JSONObject params = new JSONObject();
params.put("key", key);
params.put("value", value);
Map<String, String> parameters = new HashMap<>(1);
parameters.put("key", key);
raftProxy.proxyPostLarge(getLeader().ip, API_PUB, params.toJSONString(), parameters);
return;
}
// LEADER节点处理
try {
OPERATE_LOCK.lock();
final Datum datum = new Datum();
datum.key = key;
datum.value = value;
datum.timestamp.set(getDatum(key).timestamp.incrementAndGet());
JSONObject json = new JSONObject();
json.put("datum", datum);
json.put("source", peers.local());
//数据注册到本地节点
onPublish(datum, peers.local());
final String content = JSON.toJSONString(json);
//只有大多数服务器(majorityCount=(peers.size() / 2 + 1))返回成功,我们才能认为这次更新成功
final CountDownLatch latch = new CountDownLatch(peers.majorityCount());
//数据同步到集群中的所有节点
for (final String server : peers.allServersIncludeMyself()) {
if (isLeader(server)) {
latch.countDown();
continue;
}
// 数据同步地址:/nacos/v1/ns/raft/datum/commit"
final String url = buildURL(server, API_ON_PUB);
HttpClient.asyncHttpPostLarge(url, Arrays.asList("key=" + key), content, new AsyncCompletionHandler<Integer>() {
@Override
public Integer onCompleted(Response response) throws Exception {
if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
return 1;
}
latch.countDown();
return 0;
}
@Override
public STATE onContentWriteCompleted() {
return STATE.CONTINUE;
}
});
}
//等待大多数服务器成功或超时(RAFT_PUBLISH_TIMEOUT=5000)
if (!latch.await(UtilsAndCommons.RAFT_PUBLISH_TIMEOUT, TimeUnit.MILLISECONDS)) {
// only majority servers return success can we consider this update success
throw new IllegalStateException("data publish failed, caused failed to notify majority, key=" + key);
}
} finally {
OPERATE_LOCK.unlock();
}
}
/**
* 数据注册到本地节点
* @param datum
* @param source
* @throws Exception
*/
public void onPublish(Datum datum, RaftPeer source) throws Exception {
//验证数据
.....................
RaftPeer local = peers.local();
local.resetLeaderDue();
// if data should be persisted, usually this is true:
if (KeyBuilder.matchPersistentKey(datum.key)) {
raftStore.write(datum);
}
// 存入内存(ConcurrentHashMap)
datums.put(datum.key, datum);
if (isLeader()) {
local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT);
} else {
if (local.term.get() + PUBLISH_TERM_INCREASE_COUNT > source.term.get()) {
//set leader term:
getLeader().term.set(source.term.get());
local.term.set(getLeader().term.get());
} else {
local.term.addAndGet(PUBLISH_TERM_INCREASE_COUNT);
}
}
//更新任期
raftStore.updateTerm(local.term.get());
//通知其他类
notifier.addTask(datum.key, ApplyAction.CHANGE);
}
}
Raft协议中LEADER定时(TICK_PERIOD_MS=500毫秒)发送心跳数据(包含全量数据)同步FOLLOWER。
public class HeartBeat implements Runnable {
/**
* 发送心跳
* @throws IOException
* @throws InterruptedException
*/
public void sendBeat() throws IOException, InterruptedException {
RaftPeer local = peers.local();
if (local.state != RaftPeer.State.LEADER && !STANDALONE_MODE) {
return;
}
local.resetLeaderDue();
// 构造报文
JSONObject packet = new JSONObject();
packet.put("peer", local);
JSONArray array = new JSONArray();
if (!switchDomain.isSendBeatOnly()) {
//遍历所有服务
for (Datum datum : datums.values()) {
JSONObject element = new JSONObject();
if (KeyBuilder.matchServiceMetaKey(datum.key)) {
element.put("key", KeyBuilder.briefServiceMetaKey(datum.key));
} else if (KeyBuilder.matchInstanceListKey(datum.key)) {
element.put("key", KeyBuilder.briefInstanceListkey(datum.key));
}
element.put("timestamp", datum.timestamp);
array.add(element);
}
}
packet.put("datums", array);
// broadcast
Map<String, String> params = new HashMap<String, String>(1);
params.put("beat", JSON.toJSONString(packet));
// 压缩数据
String content = JSON.toJSONString(params);
ByteArrayOutputStream out = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(out);
gzip.write(content.getBytes(StandardCharsets.UTF_8));
gzip.close();
byte[] compressedBytes = out.toByteArray();
String compressedContent = new String(compressedBytes, StandardCharsets.UTF_8);
//发送集群中所有节点
for (final String server : peers.allServersWithoutMySelf()) {
try {
final String url = buildURL(server, API_BEAT);
HttpClient.asyncHttpPostLarge(url, null, compressedBytes, new AsyncCompletionHandler<Integer>() {
@Override
public Integer onCompleted(Response response) throws Exception {
if (response.getStatusCode() != HttpURLConnection.HTTP_OK) {
MetricsMonitor.getLeaderSendBeatFailedException().increment();
return 1;
}
peers.update(JSON.parseObject(response.getResponseBody(), RaftPeer.class));
return 0;
}
});
} catch (Exception e) {
Loggers.RAFT.error("error while sending heart-beat to peer: {} {}", server, e);
MetricsMonitor.getLeaderSendBeatFailedException().increment();
}
}
}
}
Raft协议选举流程:
nacos启动时启动一个选举定时任务:executorService.scheduleAtFixedRate(runnable, 0, TICK_PERIOD_MS=500L, TimeUnit.MILLISECONDS);
nacos节点定时任务检测如果超过15秒没有收到LEADER心跳则发起选举投票(选自己为LEADER),发送到集群其他节点,自己状态为CANDIDATE。
nacos节点收到选举投票如果CANDIDATE节点term大于本地的term则同意发送节点为LEADER,否则投票自己为LEADER。
CANDIDATE节点依次收到其他节点的投票回复,统计投票,只要某个节点超过半数投票则确认为LEADER。
LEADER节点同过心跳通知其他节点,自己为新LEADER。
public class MasterElection implements Runnable {
@Override
public void run() {
try {
if (!peers.isReady()) {
return;
}
RaftPeer local = peers.local();
local.leaderDueMs -= GlobalExecutor.TICK_PERIOD_MS;
//是否超过15秒没有收到LEADER心跳
if (local.leaderDueMs > 0) {
return;
}
// reset timeout
local.resetLeaderDue();
local.resetHeartbeatDue();
// 发送选举
sendVote();
} catch (Exception e) {
Loggers.RAFT.warn("[RAFT] error while master election {}", e);
}
}
/**
* 发送选举流程
*/
public void sendVote() {
RaftPeer local = peers.get(NetUtils.localServer());
peers.reset();
local.term.incrementAndGet();
local.voteFor = local.ip; //选自己
local.state = RaftPeer.State.CANDIDATE;
Map<String, String> params = new HashMap<>(1);
params.put("vote", JSON.toJSONString(local));
//发送集群其他节点: /nacos/v1/ns/raft/vote
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 {
//收到回复统计选票确定谁是LEADER
RaftPeer peer = JSON.parseObject(response.getResponseBody(), RaftPeer.class);
peers.decideLeader(peer);
return 0;
}
});
} catch (Exception e) {
Loggers.RAFT.warn("error while sending vote to server: {}", server);
}
}
}
}
收到选举投票请求的处理
/**
* 收到选举投票请求
* @param remote CANDIDATE节点
* @return 自己投票的节点
*/
public synchronized RaftPeer receivedVote(RaftPeer remote) {
RaftPeer local = peers.get(NetUtils.localServer());
// 本地节点term大于等于CANDIDATE节点term则投票自己为LEADER
if (remote.term.get() <= local.term.get()) {
if (StringUtils.isEmpty(local.voteFor)) {
local.voteFor = local.ip;
}
return local;
}
// CANDIDATE节点term大于本地的term则同意CANDIDATE节点为LEADER
local.resetLeaderDue();
local.state = RaftPeer.State.FOLLOWER;
local.voteFor = remote.ip;
local.term.set(remote.term.get());
return local;
}
收到投票回复统计选票确定谁是LEADER
/**
* 统计选票确定谁是LEADER
* @param candidate 一次计票
* @return
*/
public RaftPeer decideLeader(RaftPeer candidate) {
//放到投票箱
peers.put(candidate.ip, candidate);
SortedBag ips = new TreeBag();
int maxApproveCount = 0;
String maxApprovePeer = null;
//统计投票找出最大的投票节点
for (RaftPeer peer : peers.values()) {
if (StringUtils.isEmpty(peer.voteFor)) {
continue;
}
ips.add(peer.voteFor);
if (ips.getCount(peer.voteFor) > maxApproveCount) {
maxApproveCount = ips.getCount(peer.voteFor);
maxApprovePeer = peer.voteFor;
}
}
// 只要超过半数投票则确认为LEADER。
if (maxApproveCount >= majorityCount()) {
RaftPeer peer = peers.get(maxApprovePeer);
peer.state = RaftPeer.State.LEADER;
if (!Objects.equals(leader, peer)) {
leader = peer;
applicationContext.publishEvent(new LeaderElectFinishedEvent(this, leader));
}
}
return leader;
}
nacos服务注册之服务器端Raft的更多相关文章
- nacos服务注册之服务器端Distro
一致性协议算法Distro阿里自己的创的算法吧,网上能找到的资料很少.Distro用于处理ephemeral类型数据 Distro协议算法看代码大体流程是: nacos启动首先从其他远程节点同步全部数 ...
- nacos服务注册源码解析
1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...
- SpringCloud Alibaba Nacos服务注册与配置管理
Nacos SpringCloud Alibaba Nacos是一个狗抑郁构建云原生应用的动态服务发现.配置管理和服务管理平台. Nacos:Dynamic Naming and Configurat ...
- Nacos 服务注册的原理
Nacos 服务注册需要具备的能力: 服务提供者把自己的协议地址注册到Nacos server 服务消费者需要从Nacos Server上去查询服务提供者的地址(根据服务名称) Nacos Serve ...
- Spring Cloud Alibaba Nacos 服务注册与发现功能实现!
Nacos 是 Spring Cloud Alibaba 中一个重要的组成部分,它提供了两个重要的功能:服务注册与发现和统一的配置中心功能. 服务注册与发现功能解决了微服务集群中,调用者和服务提供者连 ...
- Spring Cloud Alibaba | Nacos服务注册与发现
目录 Spring Cloud Alibaba | Nacos服务注册与发现 1. 服务提供者 1.1 pom.xml项目依赖 1.2 配置文件application.yml 1.3 启动类Produ ...
- Spring Cloud Alibaba(一) 如何使用nacos服务注册和发现
Nacos介绍 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮助您更敏捷和容易地构 ...
- Alibaba Nacos 学习(三):Spring Cloud Nacos Discovery - FeignClient,Nacos 服务注册与发现
Alibaba Nacos 学习(一):Nacos介绍与安装 Alibaba Nacos 学习(二):Spring Cloud Nacos Config Alibaba Nacos 学习(三):Spr ...
- SpringCloud Alibaba Nacos 服务注册
业务服务接入Nacos服务治理中心 启动Nacos访问地址为:http://101.200.201.195:8848/nacos/ 创建bom工程用于管理依赖(下方附加源码地址) 准备工作完成后开始接 ...
随机推荐
- codeforces622E Ants in Leaves (dfs)
Description Tree is a connected graph without cycles. A leaf of a tree is any vertex connected with ...
- CentOS 6 修改/etc/security/limits.conf不生效办法
我们使用CentOS系统,在部署新的服务经常会遇到 打开最大文件数限制 too many open files的警告,通常我们只需要修改/etc/security/limits.conf该文件,增加两 ...
- 什么样的 SQL 不走索引
参考: MySQL 索引优化全攻略 索引建立的规则 1.能创建唯一索引就创建唯一索引 2.为经常需要排序.分组和联合操作的字段建立索引 3.为常作为查询条件的字段建立索引 如果某个字段经常用来做查询条 ...
- 关于vmwaretools
今天安装Ubuntu16.04-i386,vmware15.5,使用的快速安装,然后安装vmwaretools出现问题:无法复制粘贴,顶部管理"重新安装vmware-tools"选 ...
- spfa+链式前向星模板
#include<bits/stdc++.h> #define inf 1<<30 using namespace std; struct Edge{ int nex,to,w ...
- hdu 6242 Geometry Problem
Geometry Problem Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 262144/262144 K (Java/Other ...
- C# 类 (7) - 抽象 Abstract
Abstract 抽象类,关键字Abstract ,最典型的应用就是在 继承机制里 作为base类,抽象类是不能被实例化的(前面说的static 类也不能被实例化)它必须作为 基类,被别人继承,然后必 ...
- [USACO15JAN]Moovie Mooving G
[USACO15JAN]Moovie Mooving G 状压难题.不过也好理解. 首先我们根据题意: she does not want to ever visit the same movie t ...
- git hooks All In One
git hooks All In One $ xgqfrms git:(main) cd .git/ $ .git git:(main) ls COMMIT_EDITMSG HEAD branches ...
- vue 的 computed 属性在什么时间执行
vue 的 computed 属性在什么时间执行