我们知道nacos是支持CP与AP模型的,如果有小伙伴不知道AP CP代表啥含义,可以百度搜下CAP理论学习下。像我们常见的注册中心Eureka是AP模型,保证服务可用性的,Zookeeper 是CP模型,保证数据的一致性。我们之前介绍过nacos基于raft协议来保证数据的一致性,是CP模型,但是并不是说nacos存储的所有内容完全保证一致性也就是所谓的CP模型,我们还知道nacos注册中心存储节点信息支持临时数据与持久数据的,持久数据集群间的同步就是使用的CP模型,使用raft协议保证数据的一致性,但是临时数据使用的是AP模型,保证服务的可用性,临时数据集群间的同步就没有某种协议来保证一致性了,接下来我们就来分析下临时数据集群间是怎样同步的。
1.回顾服务注册原理
建议看下Nacos注册中心8-Server端(处理注册请求)这篇服务注册的文章,介绍的很清楚了,其实就是nacos客户端将服务的一些信息发送到nacos注册中心服务端,服务端收到消息后进行异步注册,所谓的异步注册就是先写到一个map(dataStore)中,写一个事件到内存队列中,到这nacos服务端就告诉客户端服务注册成功了(其实还需要将数据同步给集群的别的机器,不过也是异步的,往内存队列中塞个延时任务),但是这个时候,你进行服务发现并不会拿到刚才注册的服务,因为它并没有将服务注册信息放在对应的数据结构下面,它只是放到一个map+ 内存队列,后台一个线程专门从队列中取数据,然后放到对应的数据结构下面,这个时候服务发现才会拿到它,也就是服务发现是找到对应数据结构,找某个namespace,group的一些实例列表,而不是去map中查找,map中只能通过key来定位。这就是nacos处理临时数据服务注册的核心原理,当然还有一些通知机制,集群间同步。
2.临时数据集群间同步原理
服务注册的时候,将数据写到map+ 内存队列后,nacos还干了一件事情,就是集群间的同步,说实话,它这个临时数据集群间同步有点麻烦了。
-
它有两个执行引擎,一个是延时任务的,一个是普通任务的。
- 延时任务引擎中有个map集合,专门用来存放任务(task),有个延时任务,每100ms扫描一下map中的任务,只要达到分发间隔时间的任务,就会获取对应任务的processor来执行任务。
- 普通任务执行引擎 有很多的worker,也就是个worker数组,每个worker中有个一个任务队列,每个worker中还有一个内部线程,专门从任务队列中获取任务,执行任务。 如图:

-
当我们服务注册的时候,会将服务信息存储到一个map中+ 内存队列事件,同时会进行集群同步
- 集群同步的时候,根据集群成员信息,封装任务,每个成员一个同步任务(除去自己)。
- 先将任务交给延时任务引擎,这个时候分发任务时间是1s(总共2s),它首先会被加到任务map中,然后定时任务每100ms扫描一下,看看任务有没有超过分发任务时间(1s),就这样定时任务走了10遍,这个任务才符合要求,获取对应的processor并执行,这个processor将任务封装,交给了任务执行引擎,这个时候,任务执行引擎会选择一个worker,将任务添加到worker对应的任务队列中。worker内部的线程不断从队列中获取任务,执行任务。 如图:

-
这个时候从队列中获取任务并执行,执行的时候,将任务封装一层,又交给了延时任务执行引擎,设置的任务分发时间为1s,这个时候任务分发时间才会达到默认的2s。
- 接着又会走一遍流程,不过在获取processor的时候,获取到的processor并不是之前的,而是延时任务执行引擎默认的一个processor,它又会封装一个task,交给任务引擎,任务引擎还是会交给同一个worker里面的队列,内部线程不停的从队列中获取任务并执行,这个时候任务执行就会找到发送网络请求的组件,组装请求信息,发送同步请求。如图:

3.源码分析
临时服务注册先调用DistroConsistencyServiceImpl 的put方法,将服务信息存储到map中 + 内存队列中,然后就是集群之间的同步了。
// put 往存储器中放入数据
@Override
public void put(String key, Record value) throws NacosException {
// 1.往本地里面存储 + 内存队列
onPut(key, value);
// 2.集群间同步
distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
/// 延迟1s ,默认是2000 ms ,然后/2
globalConfig.getTaskDispatchPeriod() / 2);
}
复制代码
我们可以看到put方法干了2件事情,1是往本地map中存储+ 内存队列中塞事件,2是 集群间同步。
这里需要注意的是,它的延时时间间隔默认是2s ,然后它取了一半。 接下来我们看下DistroProtocol 的sync方法
public void sync(DistroKey distroKey, DataOperation action, long delay) {
/// 遍历所有的成员,抛去自己
for (Member each : memberManager.allMembersWithoutSelf()) {
// 重新封装DistroKey ,然后将对方的地址封装进去
DistroKey distroKeyWithTarget = new DistroKey(distroKey.getResourceKey(), distroKey.getResourceType(),
each.getAddress());
// 封装延时task
DistroDelayTask distroDelayTask = new DistroDelayTask(distroKeyWithTarget, action, delay);
/// key是distroKeyWithTarget , value是distroDelayTask
distroTaskEngineHolder.getDelayTaskExecuteEngine().addTask(distroKeyWithTarget, distroDelayTask);
if (Loggers.DISTRO.isDebugEnabled()) {
Loggers.DISTRO.debug("[DISTRO-SCHEDULE] {} to {}", distroKey, each.getAddress());
}
}
}
复制代码
可以看到,遍历除了自己以外集群成员,封装延时任务,然后交给延时任务执行引擎来处理,给集群每个成员封装延时任务,将任务交

最低0.47元/天 解锁文章
1665

被折叠的 条评论
为什么被折叠?



