一致性协议算法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. 通过SignalR技术整合即时通讯(IM)在.NET中应用落地

    1.引言 即时通讯(IM)是RDIFramework.NET敏捷开发框架全新提供的一个基于Web的即时通讯.内部聊天沟通的工具.界面美观大方对于框架内部进行消息的沟通非常方便.基于RDIFramewo ...

  2. int和longlong的范围

    unsigned   int     0-4294967295   (10位数,4e9) int                        -2147483648-2147483647  (10位 ...

  3. 【noi 2.6_9270】&【poj 2440】DNA(DP)

    题意:问长度为L的所有01串中,有多少个不包含"101"和"111"的串. 解法:f[i][j]表示长度为i的01串中,结尾2位的十进制数是j的合法串的个数.那 ...

  4. 【noi 2.6_8471】切割回文(DP)

    题意:给一个字符串,问至少切割几次使每子串都是回文的. 解法:f[i]表示前i个字符至少需要切割几次,预处理p[i][j]表示子串s[i]~s[j]是否为回文串.O(n^2) 另外,这题也类似&quo ...

  5. poj3087 Shuffle'm Up

    Description A common pastime for poker players at a poker table is to shuffle stacks of chips. Shuff ...

  6. IntelliJ IDEA 运行java程序时出现“程序发生找不到或无法加载主类 cn.test1.test1”错误

    在你程序不出现错误,而且你的编译器已经成功导入后 成功导入的样子 你可以重新打开一个项目 这就可以了^_^

  7. hdu5305 Friends

    Problem Description There are n people and m pairs of friends. For every pair of friends, they can c ...

  8. 大数据开发-Spark-初识Spark-Graph && 快速入门

    1.Spark Graph简介 GraphX 是 Spark 一个组件,专门用来表示图以及进行图的并行计算.GraphX 通过重新定义了图的抽象概念来拓展了 RDD: 定向多图,其属性附加到每个顶点和 ...

  9. 国产网络测试仪MiniSMB - 如何配置VLAN数据流

    国产网络测试仪MiniSMB(www.minismb.com)是复刻smartbits的IP网络性能测试工具,是一款专门用于测试智能路由器,网络交换机的性能和稳定性的软硬件相结合的工具.可以通过此以太 ...

  10. 微服务架构学习Day01-SpringBoot入门

    基本概念 SpringBoot的优点: 可以创建独立的Spring应用 SpringBoot嵌入Tomcat,Jetty和Unsertow, 不需要部署war文件 根据需要通过maven获取start ...