一致性协议算法Distro阿里自己的创的算法吧,网上能找到的资料很少。Distro用于处理ephemeral类型数据

  1. Distro协议算法看代码大体流程是:
  • nacos启动首先从其他远程节点同步全部数据
  • nacos每个节点是平等的都可以处理写入请求,同时把新数据同步到其他节点
  • 每个节点只负责部分数据,定时发送自己负责数据校验值到其他节点来保持数据一致性
  1. Distro代码分析, nacos负责数据一致性的服务是ConsistencyService接口,DistroConsistencyServiceImpl是EphemeralConsistencyService和ConsistencyService实现类。

Distro协议算法数据存储实现笔记简单,DataStore是存储实现类临时节点的数据都存储在ConcurrentHashMap里面

/**
* Store of data
*
* @author nkorange
* @since 1.0.0
*/
@Component
public class DataStore { private Map<String, Datum> dataMap = new ConcurrentHashMap<>(1024); public void put(String key, Datum value) {
dataMap.put(key, value);
} public Datum remove(String key) {
return dataMap.remove(key);
} public Set<String> keys() {
return dataMap.keySet();
} public Datum get(String key) {
return dataMap.get(key);
} public boolean contains(String key) {
return dataMap.containsKey(key);
} public Map<String, Datum> batchGet(List<String> keys) {
Map<String, Datum> map = new HashMap<>(128);
for (String key : keys) {
Datum datum = dataMap.get(key);
if (datum == null) {
continue;
}
map.put(key, datum);
}
return map;
} public int getInstanceCount() {
int count = 0;
for (Map.Entry<String, Datum> entry : dataMap.entrySet()) {
try {
Datum instancesDatum = entry.getValue();
if (instancesDatum.value instanceof Instances) {
count += ((Instances) instancesDatum.value).getInstanceList().size();
}
} catch (Exception ignore) {
}
}
return count;
} public Map<String, Datum> getDataMap() {
return dataMap;
}
}

nacos启动首先从其他节点同步全部数据

DistroConsistencyServiceImpl类构造完成后启动同步线程直到首次同步成功

@org.springframework.stereotype.Service("distroConsistencyService")
public class DistroConsistencyServiceImpl implements EphemeralConsistencyService {
private volatile Notifier notifier = new Notifier(); private LoadDataTask loadDataTask = new LoadDataTask(); @PostConstruct
public void init() {
GlobalExecutor.submit(loadDataTask);
GlobalExecutor.submitDistroNotifyTask(notifier);
} /**
* 从其它节点同步数据任务线程
*/
private class LoadDataTask implements Runnable { @Override
public void run() {
try {
load();
if (!initialized) {
GlobalExecutor.submit(this, globalConfig.getLoadDataRetryDelayMillis());
}
} catch (Exception e) {
Loggers.DISTRO.error("load data failed.", e);
}
}
} /**
* 初始化时从其它节点同步数据
* @throws Exception
*/
public void load() throws Exception {
//如果单列模式则返回
if (SystemUtils.STANDALONE_MODE) {
initialized = true;
return;
}
// size = 1 means only myself in the list, we need at least one another server alive:
while (serverListManager.getHealthyServers().size() <= 1) {
Thread.sleep(1000L);
Loggers.DISTRO.info("waiting server list init...");
}
//只要有一个节点同步成功则返回
for (Server server : serverListManager.getHealthyServers()) {
if (NetUtils.localServer().equals(server.getKey())) {
continue;
}
// try sync data from remote server:
if (syncAllDataFromRemote(server)) {
initialized = true;
return;
}
}
} /**
* 从指定nacos节点同步数据
* @param server 目标nacos节点
* @return 是否同步成功
*/
public boolean syncAllDataFromRemote(Server server) {
try {
byte[] data = NamingProxy.getAllData(server.getKey());
processData(data);
return true;
} catch (Exception e) {
return false;
}
} /**
* 处理同步报文信息
* 更新数据存储和通知监听器
* @param data
* @param data
* @throws Exception
*/
public void processData(byte[] data) throws Exception {
if (data.length > 0) {
Map<String, Datum<Instances>> datumMap = serializer.deserializeMap(data, Instances.class); for (Map.Entry<String, Datum<Instances>> entry : datumMap.entrySet()) { dataStore.put(entry.getKey(), entry.getValue()); if (!listeners.containsKey(entry.getKey())) {
// pretty sure the service not exist:
if (switchDomain.isDefaultInstanceEphemeral()) {
// create empty service
Loggers.DISTRO.info("creating service {}", entry.getKey());
Service service = new Service();
String serviceName = KeyBuilder.getServiceName(entry.getKey());
String namespaceId = KeyBuilder.getNamespace(entry.getKey());
service.setName(serviceName);
service.setNamespaceId(namespaceId);
service.setGroupName(Constants.DEFAULT_GROUP);
// now validate the service. if failed, exception will be thrown
service.setLastModifiedMillis(System.currentTimeMillis());
service.recalculateChecksum();
listeners.get(KeyBuilder.SERVICE_META_KEY_PREFIX).get(0).onChange(KeyBuilder.buildServiceMetaKey(namespaceId, serviceName), service);
}
}
} for (Map.Entry<String, Datum<Instances>> entry : datumMap.entrySet()) {
if (!listeners.containsKey(entry.getKey())) {
// Should not happen:
continue;
} try {
for (RecordListener listener : listeners.get(entry.getKey())) {
listener.onChange(entry.getKey(), entry.getValue().value);
}
} catch (Exception e) {
continue;
} // Update data store if listener executed successfully:
dataStore.put(entry.getKey(), entry.getValue());
}
}
}
} public class NamingProxy {
/**
* 获取指定nacos节点所有数据
* @param server 目标nacos节点
* @return 返回到数据
* @throws Exception
*/
public static byte[] getAllData(String server) throws Exception {
Map<String, String> params = new HashMap<>(8);
//http://120.0.0.1:8848/nacos/v1/ns/distro/datums
HttpClient.HttpResult result = HttpClient.httpGet("http://" + server + RunningConfig.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + ALL_DATA_GET_URL, new ArrayList<>(), params); if (HttpURLConnection.HTTP_OK == result.code) {
return result.content.getBytes();
} throw new IOException("failed to req API: " + "http://" + server + RunningConfig.getContextPath() + UtilsAndCommons.NACOS_NAMING_CONTEXT + DATA_GET_URL + ". code: " + result.code + " msg: " + result.content);
}
}

Distro两种数据的同步方式:1.定时同步 2.本节点接收到服务变更同步

DataSyncer启动定时同步线程和负责数据同步

TaskDispatcher负责接受服务变更后分发到DataSyncer即时同步


@Component
@DependsOn("serverListManager")
public class DataSyncer {
@PostConstruct
//启动定时同步的定时任务
public void init() {
startTimedSync();
} public void startTimedSync() {
//每5秒一次
//dataSyncExecutor.scheduleWithFixedDelay(runnable, PARTITION_DATA_TIMED_SYNC_INTERVAL=5000, PARTITION_DATA_TIMED_SYNC_INTERVAL=5000, TimeUnit.MILLISECONDS);
GlobalExecutor.schedulePartitionDataTimedSync(new TimedSync());
} public class TimedSync implements Runnable { @Override
public void run() { try {
// send local timestamps to other servers:
Map<String, String> keyChecksums = new HashMap<>(64);
for (String key : dataStore.keys()) {
//只同步自己负责的那部分数据
// int index = healthyList.indexOf(NetUtils.localServer());
// int target = distroHash(serviceName) % healthyList.size();
if (!distroMapper.responsible(KeyBuilder.getServiceName(key))) {
continue;
} Datum datum = dataStore.get(key);
if (datum == null) {
continue;
}
keyChecksums.put(key, datum.value.getChecksum());
} if (keyChecksums.isEmpty()) {
return;
}
for (Server member : getServers()) {
if (NetUtils.localServer().equals(member.getKey())) {
continue;
}
//请求路径/nacos/v1/ns/distro/checksum
NamingProxy.syncCheckSums(keyChecksums, member.getKey());
}
} catch (Exception e) {
Loggers.DISTRO.error("timed sync task failed.", e);
}
} }
}

TaskDispatcher负责接受服务变更后分发到DataSyncer即时同步

/**
* Data sync task dispatcher
*
* @author nkorange
* @since 1.0.0
*/
@Component
public class TaskDispatcher { @Autowired
private GlobalConfig partitionConfig; @Autowired
private DataSyncer dataSyncer; private List<TaskScheduler> taskSchedulerList = new ArrayList<>(); private final int cpuCoreCount = Runtime.getRuntime().availableProcessors(); @PostConstruct
public void init() {
for (int i = 0; i < cpuCoreCount; i++) {
TaskScheduler taskScheduler = new TaskScheduler(i);
taskSchedulerList.add(taskScheduler);
GlobalExecutor.submitTaskDispatch(taskScheduler);
}
} /**
* 接收服务变更后同步任务
* @param key 服务唯一标识
*/
public void addTask(String key) {
taskSchedulerList.get(UtilsAndCommons.shakeUp(key, cpuCoreCount)).addTask(key);
} public class TaskScheduler implements Runnable { private int index; private int dataSize = 0; private long lastDispatchTime = 0L; private BlockingQueue<String> queue = new LinkedBlockingQueue<>(128 * 1024); public TaskScheduler(int index) {
this.index = index;
} public void addTask(String key) {
queue.offer(key);
} public int getIndex() {
return index;
} @Override
public void run() {
List<String> keys = new ArrayList<>();
while (true) {
try {
String key = queue.poll(partitionConfig.getTaskDispatchPeriod(), TimeUnit.MILLISECONDS);
if (dataSyncer.getServers() == null || dataSyncer.getServers().isEmpty()) {
continue;
} if (StringUtils.isBlank(key)) {
continue;
} if (dataSize == 0) {
keys = new ArrayList<>();
} keys.add(key);
dataSize++; //批量同步; 1 数量达到batchSyncKeyCount=1000,2:距上次同步事件大于taskDispatchPeriod=2000
if (dataSize == partitionConfig.getBatchSyncKeyCount() ||
(System.currentTimeMillis() - lastDispatchTime) > partitionConfig.getTaskDispatchPeriod()) { for (Server member : dataSyncer.getServers()) {
if (NetUtils.localServer().equals(member.getKey())) {
continue;
}
SyncTask syncTask = new SyncTask();
syncTask.setKeys(keys);
syncTask.setTargetServer(member.getKey());
//分发任务到dataSyncer同步数据
dataSyncer.submit(syncTask, 0);
}
lastDispatchTime = System.currentTimeMillis();
dataSize = 0;
} } catch (Exception e) {
Loggers.DISTRO.error("dispatch sync task failed.", e);
}
}
}
}
} @Component
@DependsOn("serverListManager")
public class DataSyncer {
/**
*
* @param task 同步数据
* @param delay 延迟时间
*/
public void submit(SyncTask task, long delay) {
//各种监测
..................
GlobalExecutor.submitDataSync(() -> {
// 1. check the server
..................
// 2. get the datums by keys and check the datum is empty or not
.................. byte[] data = serializer.serialize(datumMap);
long timestamp = System.currentTimeMillis();
// DATA_ON_SYNC_URL = "/nacos/v1/ns/distro/datum"
boolean success = NamingProxy.syncData(data, task.getTargetServer());
..............
//不成功重试
}, delay);
}
}

nacos服务注册之服务器端Distro的更多相关文章

  1. nacos服务注册之服务器端Raft

    Raft是持久化,数据存储在\nacos\data\naming\data目录 nacos启动后首先从数据存储目录加载数据 Raft协议中节点只有一个LEADER,只有LEADER节点负责数据写入,F ...

  2. nacos服务注册源码解析

    1.客户端使用 compile 'com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery:2.2.3.RELEASE' compi ...

  3. Nacos 服务注册的原理

    Nacos 服务注册需要具备的能力: 服务提供者把自己的协议地址注册到Nacos server 服务消费者需要从Nacos Server上去查询服务提供者的地址(根据服务名称) Nacos Serve ...

  4. Spring Cloud Alibaba Nacos 服务注册与发现功能实现!

    Nacos 是 Spring Cloud Alibaba 中一个重要的组成部分,它提供了两个重要的功能:服务注册与发现和统一的配置中心功能. 服务注册与发现功能解决了微服务集群中,调用者和服务提供者连 ...

  5. Spring Cloud Alibaba | Nacos服务注册与发现

    目录 Spring Cloud Alibaba | Nacos服务注册与发现 1. 服务提供者 1.1 pom.xml项目依赖 1.2 配置文件application.yml 1.3 启动类Produ ...

  6. Spring Cloud Alibaba(一) 如何使用nacos服务注册和发现

    Nacos介绍 Nacos 致力于帮助您发现.配置和管理微服务.Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现.服务配置.服务元数据及流量管理. Nacos 帮助您更敏捷和容易地构 ...

  7. Alibaba Nacos 学习(三):Spring Cloud Nacos Discovery - FeignClient,Nacos 服务注册与发现

    Alibaba Nacos 学习(一):Nacos介绍与安装 Alibaba Nacos 学习(二):Spring Cloud Nacos Config Alibaba Nacos 学习(三):Spr ...

  8. SpringCloud Alibaba Nacos 服务注册

    业务服务接入Nacos服务治理中心 启动Nacos访问地址为:http://101.200.201.195:8848/nacos/ 创建bom工程用于管理依赖(下方附加源码地址) 准备工作完成后开始接 ...

  9. springcloudalibaba与nacos服务注册流程图

    springboot + springcloud + springcloudalibaba + nacos 服务注册流程图: springboot ①WebApplicationContext ②st ...

随机推荐

  1. C++的智能指针你了解吗?

  2. Codeforces Round #635 (Div. 1)

    传送门 A. Linova and Kingdom 题意: 给定一颗以\(1\)为根的树,现在要选定\(k\)个结点为黑点,一个黑点的贡献为从他出发到根节点经过的白点数量. 问黑点贡献总和最大为多少. ...

  3. P4570 [BJWC2011]元素 (线性基)

    题意:n个石头 每个石头有a,b两个属性 要求选出一些石头使得没有一个子集的a属性xor和为0 且b属性和最大 题解:线性基例题了.. 好像需要理解一些性质 1.原序列里任一数都可有由线性基xor得到 ...

  4. 洛谷P3796

    题目链接  题意:有n个由小写字母组成的模式串以及一个文本串T.每个模式串可能会在文本串中出现多次.哪些模式串在文本串T中出现的次数最多. 题解:ac自动机模板加强版,开一个数组单独记录各个字符串出现 ...

  5. java随机数的产生

    两种产生随机数的方法: 1.通过import java.util.Random来实现 2.Math.random() 一.第一种的话就是导入Random之后,先生成一个Random对象r,之后再利用r ...

  6. Codeforces Round #656 (Div. 3) A. Three Pairwise Maximums (数学)

    题意:给你三个正整数\(x\),\(y\),\(z\),问能够找到三个正整数\(a\),\(b\),\(c\),使得\(x=max(a,b)\),\(y=max(a,c)\),\(z=max(b,c) ...

  7. Spring web之restTemplate超时问题处理

    问题 项目中有个远程服务因为某些原因会访问不通,于是就在调用的那一步挂起无法结束了. 查看代码 代码大概如下 CloseableHttpClient closeableHttpClient = Htt ...

  8. IDE - vscode

    [一]VSCODE官方插件库 https://marketplace.visualstudio.com/ 最好能在文件->首选项->设置中,搜索update,将Auto Update关闭, ...

  9. C++ part8

    1.volatile关键字 在C++中,对volatile修饰的对象的访问,有编译器优化上的副作用: 不允许被编译器优化,提供特殊地址的稳定访问(只从内存中读取). 有序性,编译器进行优化时,不能把对 ...

  10. Python爬虫全网搜索并下载音乐

    现在写一篇博客总是喜欢先谈需求或者本内容的应用场景,是的,如果写出来的东西没有任何应用价值,确实也没有实际意义.今天的最早的需求是来自于如何免费[白嫖]下载全网优质音乐,我去b站上面搜索到了一个大牛做 ...