一致性协议算法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#的dllimport使用方法详解(Port API)

    DllImport是System.Runtime.InteropServices命名空间下的一个属性类,其功能是提供从非托管DLL(托管/非托管是微软的.net framework中特有的概念,其中, ...

  2. Topo check failed. Mapred tasks exceed 1000000000

    Problem Description: java.sql.SQLException: EXECUTION FAILED: Task MAPRED-SPARK error SparkException ...

  3. Codeforces Round #648 (Div. 2) E. Maximum Subsequence Value(鸽巢原理)

    题目链接:https://codeforces.com/problemset/problem/1365/E 题意 有 $n$ 个元素,定义大小为 $k$ 的集合值为 $\sum2^i$,其中,若集合内 ...

  4. A - Promotions

    题目详见http://7xjob4.com1.z0.glb.clouddn.com/3f644de6844d64706eb36baa3a0c27b0 这题是普通的拓扑排序,要把每一层的都保存下来. # ...

  5. [Python] Pandas 对数据进行查找、替换、筛选、排序、重复值和缺失值处理

    目录 1. 数据文件 2. 读数据 3. 查找数据 4. 替换数据 4.1 一对一替换 4.2 多对一替换 4.3 多对多替换 5. 插入数据 6. 删除数据 6.1 删除列 6.2 删除行 7. 处 ...

  6. 使用DTK创建模糊背景窗口并自定义阴影效果

    DTK是deepin开发的基于Qt的开发套件,提供了大量的具有独特风格的美化控件,也提供了很多非常方便的API,下边我们用DTK实现一个模糊窗口,并设置其阴影效果. 使用场景 一切需要模糊窗口作为美化 ...

  7. 微服务架构Day03-SpringBoot之web开发配置

    概述 SpringBoot开发: 1.创建SpringBoot应用,选中需要的场景模块. 2.SpringBoot已经默认将场景模块配置好,只需要在配置文件中指定少量的配置(数据库地址,用户名,密码) ...

  8. NIST随机数测试软件安装与使用 && igamc:UNDERFLOW

    https://csrc.nist.gov/ https://csrc.nist.gov/projects/random-bit-generation/documentation-and-softwa ...

  9. C++中main函数的返回值一定要是int

    因为大学上课时候,经常是在主函数中做处理,直接用cout语句输出到显示设备,所以一直在用void main(). 直到后面具体编程的时候,才发现void main()这种用法是按 C89(C语言的早期 ...

  10. IGS OPC UA 配置

    igs项目-右键属性-选择OPC UA,如图配置 ,其他默认 如果打开的是IGS-administration,在右下角会有通知栏图标,右键图标选择 OPC UA 配置 添加服务器节点,网络适配器选择 ...