Alibaba Nacos 客户端注册从客户端项目到nacos项目的整体流程核心梳理

news2024/11/24 19:07:40

客户端篇:


1、NacosAutoServiceRegistration类继承了AbstractAutoServiceRegistration类,AbstractAutoServiceRegistration类实现了ApplicationListener,实现了ApplicationListener接口的类都必须实现一个onApplicationEvent方法,然后spring容器启动时会调用处理事件方法,源码中的调用链:
1、Spring启动AbstractAutoServiceRegistration类中的onApplicationEvent方法
2、执行方法中的bind(event) 绑定事件方法
3、执行start()方法,该方法用于发布事件,publishEvent就是发布事件,源码内没有对InstancePreRegisteredEvent做监听,开发者可以在此进行扩展,核心代码:
    if (!this.running.get()) {
            this.context.publishEvent(
                    new InstancePreRegisteredEvent(this, getRegistration()));
            register();
            if (shouldRegisterManagement()) {
                registerManagement();
            }
            this.context.publishEvent(
                    new InstanceRegisteredEvent<>(this, getConfiguration()));
            this.running.compareAndSet(false, true);
        }
4、register()方法详解:方法会调用到NacosServiceRegistry类的register方法,该方法中会调用NamingService的registerInstance方法,该方法用于:使用当前服务的实例属性向nacos服务注册实例并添加一个延时执行的定时心跳任务BeatTask,用于给nacos发送当前服务的心还跳着的信息,这样nacos就不会将该服务下掉。
心跳任务创建核心代码:
executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);解释:
创建并执行在给定延迟后启用的单次动作
参数1:要执行的任务
参数2:从现在开始延迟执行的事件
参数3:延迟参数的事件单位

new BeatTask(beatInfo)解释:
 class BeatTask implements Runnable {

        BeatInfo beatInfo;

        public BeatTask(BeatInfo beatInfo) {
            this.beatInfo = beatInfo;
        }

        @Override
        public void run() {
            if (beatInfo.isStopped()) {
                return;
            }
            long result = serverProxy.sendBeat(beatInfo);
            long nextTime = result > 0 ? result : beatInfo.getPeriod();
            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
        }
    }
BeatTask是个实现了Runnable的线程类,里面的run方法会在上述步骤参数2延迟时间到了之后进行执行,执行的主要内容就是:serverProxy.sendBeat(beatInfo);,该方法内容:
    public long sendBeat(BeatInfo beatInfo) {
        try {
            if (NAMING_LOGGER.isDebugEnabled()) {
                NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());
            }
            Map<String, String> params = new HashMap<String, String>(4);
            params.put("beat", JSON.toJSONString(beatInfo));
            params.put(CommonParams.NAMESPACE_ID, namespaceId);
            params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
            String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, HttpMethod.PUT);
            JSONObject jsonObject = JSON.parseObject(result);

            if (jsonObject != null) {
                return jsonObject.getLong("clientBeatInterval");
            }
        } catch (Exception e) {
            NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: " + JSON.toJSONString(beatInfo), e);
        }
        return 0L;
    }
    核心代码就是:String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, HttpMethod.PUT);,向nacos服务发送了一个:/nacos/v1/ns/instance/beat的put请求,请求体内携带了当前客户端的心跳信息

客户端实例注册方法核心代码:serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
该方法调用了nacos服务的实例注册接口:/nacos/v1/ns/instance,请求体包含:客户端的实例信息,ip、端口号、服务名称等


nacos服务端篇:


/nacos/v1/ns/instance对应的接口就在nacos项目中的InstanceController类中,接下来我们对/nacos/v1/ns/instance方法来进行一个详细的分析,该方法就用于客户端的服务注册,将客户端的实例信息异步的放入一个双重map中进行服务注册,异步的原因是为了给客户端一个快速的响应:
1、接口过来后,程序会执行InstanceController类中的register方法,该方法中的核心方法:serviceManager.registerInstance(namespaceId, serviceName, instance);,接下来就一步步的对这个方法进行详细的解析

2、registerInstance方法内的createEmptyService方法首先会用于将当前客户端的实例信息放入到nacos的双重map内存注册表中去,将结构创好,里面对应的数据还没有填入,核心代码:
    public void putService(Service service) {
        if (!serviceMap.containsKey(service.getNamespaceId())) {
            synchronized (putServiceLock) {
                if (!serviceMap.containsKey(service.getNamespaceId())) {
                    serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());
                }
            }
        }
        serviceMap.get(service.getNamespaceId()).put(service.getName(), service);
    }

然后createEmptyService方法链中,会执行一个init()方法,也是核心,代码如下:
    public void init() {
        HealthCheckReactor.scheduleCheck(clientBeatCheckTask);
        for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {
            entry.getValue().setService(this);
            entry.getValue().init();
        }
    }

HealthCheckReactor.scheduleCheck(clientBeatCheckTask);解释:线程池执行器,创建一个延时任务,初始化5秒后执行,然后以后每过5秒钟执行一次:
futureMap.putIfAbsent(task.taskKey(), GlobalExecutor.scheduleNamingHealth(task, 5000, 5000, TimeUnit.MILLISECONDS));

GlobalExecutor.scheduleNamingHealth(task, 5000, 5000, TimeUnit.MILLISECONDS):方法内代码如下:
    public static ScheduledFuture<?> scheduleNamingHealth(Runnable command, long initialDelay, long delay,
            TimeUnit unit) {
        /**
         * 线程池的执行器
         * NAMING_HEALTH_EXECUTOR:延时线程池
         * scheduleWithFixedDelay:执行延时线程任务
         * 创建并执行一个周期性操作,该操作在给定的初始延迟之后首先启用,然后在一次执行终止和下一次执行开始之间的给定延迟之后启用。
         */
        return NAMING_HEALTH_EXECUTOR.scheduleWithFixedDelay(command, initialDelay, delay, unit);
    }

该方法会创建一个基于传来的:ClientBeatCheckTask task线程类,没过一段时间执行一次ClientBeatCheckTask类中的run方法,该线程类的run方法的主要作用如下:
1:心跳检测run方法
2:用服务名称hash后对机器数去模,然后选择集群里的一台机器执行任务
3:如果某个实例超过15秒没有收到心跳,则将它的healthy属性值设为false
4:如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复发送心跳则会重新注册)

然后方法内还有个addOrReplaceService方法,该方法是基于持久化存储的时候才会调用,暂不分析

3、registerInstance方法内的第二个核心方法:addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);,该方法为添加客户端实例核心方法,接下来进行解析:
3.1:
// 通过namespaceId、serviceName、ephemeral生成一个key
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

3.2:
        // 通过namespaceId和serviceName获取客户端实例
        Service service = getService(namespaceId, serviceName);

3.3:核心同步方法:
        synchronized (service) {
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

            Instances instances = new Instances();
            instances.setInstanceList(instanceList);

            // instances里有新注册的实例
            /**
             * consistencyService的put方法有6个实现类
             * 看的方法:1:猜 (经验)
             *         2:在该行代码打个断点,然后F7进入内部即可知道具体调用了那个实现类  (慢)
             *         3:看consistencyService在此类是如何注入的,可以看到是注入了一个
             *         @Resource(name = "consistencyDelegate")的bean,就可以由
             *         consistencyDelegate来猜实现类的名称就是:DelegateConsistencyServiceImpl
             */
            consistencyService.put(key, instances);
        }
3.3.1:同步方法内的:     List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);为核心代码;该addIpAddresses方法用于将客户端的实例和老的注册表实例放在一个List<Instance>中,然后进行返回,最后instanceList的值就是:老的注册表实例 + 客户端新传来的实例

3.3.2:同步方法内的: consistencyService.put(key, instances);方法:该consistencyService有多个实现类,需要看一下这个接口注入的实现类是哪一个,然后去看这个实现类的put方法:
   @Resource(name = "consistencyDelegate")
    private ConsistencyService consistencyService;
可以看到,该接口注入的是Bean为consistencyDelegate的实例,然后查找一下该接口的实现类,就找到了对应的实现类DelegateConsistencyServiceImpl,该类在Spring容器中的Bean名称为consistencyDelegate,那么刚才的put方法就是执行的此类中的put方法:
@DependsOn("ProtocolManager")
@Service("consistencyDelegate")
public class DelegateConsistencyServiceImpl implements ConsistencyService 

该类的put方法代码:
    /**
     * Instances实现了Record接口,所以可以使用Record接口来接收
     * @param key   key of data, this key should be globally unique
     * @param value value of data
     * @throws NacosException
     */
    @Override
    public void put(String key, Record value) throws NacosException {
        /**
         * mapConsistencyService(key)方法返回的ConsistencyService对象的put也有6个实现类
         * 当前如果是临时实例:
         * 返回的就是:ephemeralConsistencyService,ephemeralConsistencyService的实现类就是:DistroConsistencyServiceImpl
         * 当前如果是持久化实例:
         * 返回的就是:persistentConsistencyService,persistentConsistencyService的实现类就是:PersistentConsistencyServiceDelegateImpl
         */
        mapConsistencyService(key).put(key, value);
    }
因为 Instances实现了Record接口,所以可以使用Record接口来接收,然后该方法内调用了mapConsistencyService(key)来返回一个实例,然后执行这个实例的put方法,看下mapConsistencyService(key)方法:
   private ConsistencyService mapConsistencyService(String key) {
        /**
         * 判断当前key是否以固定的值打头的,是的话就返回的true,不是的话就返回的false
         * AP架构
         * ephemeralConsistencyService来注册的话:临时实例的注册
         * persistentConsistencyService来注册的话:持久实例的注册
         */
        return KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService;
    }
    该方法用于判断该客户端实例是使用临时实例注册还是持久实例注册,当前流程是使用的临时实例注册,也就是注册在内存中,所以返回的是:ephemeralConsistencyService
那么也就是说会调用ephemeralConsistencyService对应实例的put方法,该put方法代码:
    public void put(String key, Record value) throws NacosException {
        // 核心注册逻辑 onPut方法 将客户端传来的信息封装到一个内存队列当中
        onPut(key, value);
        // 做一些同步操作,因为方法有sync
        distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                globalConfig.getTaskDispatchPeriod() / 2);
    }
方法内的核心方法是onPut方法,onPut方法用于将客户端传来的客户端实例信息放入一个阻塞队列当中,代码如下:
    public void onPut(String key, Record value) {

        if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
            Datum<Instances> datum = new Datum<>();
            // 强转成Instances,然后放入Datum里面
            datum.value = (Instances) value;
            datum.key = key;
            datum.timestamp.incrementAndGet();
            dataStore.put(key, datum);
        }

        if (!listeners.containsKey(key)) {
            return;
        }

        notifier.addTask(key, DataOperation.CHANGE);
    }
核心方法为:notifier.addTask(key, DataOperation.CHANGE);,该方法是客户端注册方法调用链路中最后执行的一个方法,该方法内用于将客户端的一些信息放入:private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);这个阻塞队列中,用于Notifier线程处理任务run方法取出来,然后异步的将当前注册的客户端的实例放入双重map中进行注册,注册的行为是异步的,在Notifier线程类中,该线程类的run方法代码如下:
 public void run() {
            Loggers.DISTRO.info("distro notifier started");

            /**
             * 死循环,不断的从tasks中取客户端信息,读出来后就进行客户端的注册,这个线程不能停止
             */
            for (; ; ) {
                try {
                 //从tasks阻塞队列中去取出一个一个的实例信息,然后直接handler方法
                 // 只有在tasks阻塞队列中有数据取出来的时候,才会往下执行,否则这里一直会处于阻塞状态,不会占据cpu的资源
                    Pair<String, DataOperation> pair = tasks.take();
                    // 进行客户端的注册,将客户端信息放入到注册表的map的value中
                    handle(pair);
                } catch (Throwable e) {
                    Loggers.DISTRO.error("[NACOS-DISTRO] Error while handling notifying task", e);
                }
            }
        }

该run方法内的究极核心方法:handle(pair);,该方法就是最终客户端进行注册的地方,将客户端信息放入到真正的注册表中,也就是双重map中,在handler方法的调用链当中,最核心的方法就是:Cluster.updateIps方法,updateIps方法的核心流程就是:
1:将真正的注册表copy一份放到副本注册表中,用于该方法所有的操作,这个注册表只是某个group组下的stockService内部的实例列表
2:将真正的注册表替换成处理好的注册表副本,注册表副本就是处理好的,直接将处理好的注册表副本直接赋值给真正的注册表,在赋值给真正的注册表这一大段代码之间,是没有用到真正的注册表的,所以这就实现了:copy on write 写时赋值思想

流程图:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/48831.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CSDN页面左上角出现红色“不安全 | https” ,并且把鼠标放在上面的头像和消息时无法下拉菜单

CSDN页面左上角出现红色“不安全 | https”&#xff0c;如下图&#xff1a; 我不说多了&#xff0c;直接说吧 “不安全 | https”&#xff0c;先点击“不安全”看看&#xff1a; 点击“证书无效”&#xff0c;—>“基本信息” 点击“详细信息” 这三个截图都是有问题…

【元胞自动机】元胞自动机双边教室疏散【含Matlab源码 1208期】

⛄一、元胞自动机简介 1 元胞自动机发展历程 最初的元胞自动机是由冯 诺依曼在 1950 年代为模拟生物 细胞的自我复制而提出的. 但是并未受到学术界重视. 1970 年, 剑桥大学的约翰 何顿 康威设计了一个电脑游戏 “生命游戏” 后, 元胞自动机才吸引了科学家们的注意. 1983 年…

Vue3 | Vue3与Vue2相比的优势、创建项目的方式以及一些常见的Compositioin API

一、Vue3与Vue2相比&#xff0c;有哪些优势呢&#xff1f; 1、性能的提升&#xff1a; 打包大小减少41%初次渲染快55%&#xff0c;更新渲染快133%内存减少54%2、源码的升级 使用Proxy代替defineProperty实现响应式重写虚拟DOM的实现和Tree-Shaking3、新的特性 Composition API&…

[附源码]计算机毕业设计JAVA小型医院药品及门诊管理

[附源码]计算机毕业设计JAVA小型医院药品及门诊管理 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM …

Vue3 - Pinia 状态管理(环境搭建安装及各属性使用教程)详细使用教程

目录初识 Pinia介绍环境搭建getters 属性actions 属性Pinia 模块化Pinia 解构 storePinia 数据持久化SEO初识 Pinia vue 官方文档&#xff1a;https://cn.vuejs.org/guide/scaling-up/state-management.html#pinia Pinia 官方文档&#xff1a;https://pinia.web3doc.top/ pinia…

hiveSql冷门但好用函数 --持续更新

hiveSql常用函数字符串函数to_jsonjson_tupletranslate日期函数next_day字符串函数 to_json 将STRUCT类型的数据转化为json格式字符串&#xff0c;此处需要另外学习一个named_struct()函数:自定义结构化数据的KVnamed_struct(k1,v1,k2,v2…)示例&#xff1a; select named_s…

吉时利2602A数字源表-安泰测试

吉时利Keithley2602A双通道系统数字电源 Keithley/吉时利2602A数字源表数字源表是吉时利新的I-V源测量仪器&#xff0c;可用作台式I-V特征分析工具或者构成多通道I-V测试系统的组成模块。作为台式仪器使用时&#xff0c;2600A系列仪器提供了一个嵌入式TSP Express软件工具&…

函数定义、this指向、闭包等

1、函数的定义和调用 1.1函数的定义方式 1、自定义函数&#xff08;命名函数&#xff09; 2、函数表达式&#xff08;匿名函数&#xff09; 3、利用 new Function (‘参数1’, ‘参数2’, ‘函数体’) 1、自定义函数&#xff08;命名函数&#xff09; function fn() {}2、函…

微服务框架 SpringCloud微服务架构 6 Nacos 配置管理 6.1 Nacos 实现配置管理

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构6 Nacos 配置管理6.1 Nacos 实现配置管理6.1.1 统一配置管理6.1.2 直接开干…

Chapter9.4:线性系统的状态空间分析与综合(下)

此系列属于胡寿松《自动控制原理题海与考研指导》(第三版)习题精选&#xff0c;仅包含部分经典习题&#xff0c;需要完整版习题答案请自行查找&#xff0c;本系列属于知识点巩固部分&#xff0c;搭配如下几个系列进行学习&#xff0c;可用于期末考试和考研复习。 自动控制原理(…

UG NX二次开发(C#)-CAM-获取所有的加工刀具表单

文章目录 1、前言2、加工模块的刀具2.1 进入加工模板2.2 创建加工刀具3、创建一个UI Styler程序3.1 创建一个UI Styler3.2 保存对话框4、创建GetMachiningToolList工程5、添加代码6、保存工程,生成dll7、配置工程8、测试效果1、前言 UG NX软件最擅长的就是生成加工程序,包括…

Express操作MongoDB

一、Express框架通过Mongoose模块操作MongoDB数据库 1、MongoDB数据库&#xff1a; ​ &#xff08;1&#xff09;存放数据的格式&#xff1a;key-value ​ &#xff08;2&#xff09;数据库(database) ——- 集合(collection) ——- 文档(document) ​ &#xff08;3&…

数字逻辑·时序线路设计【原始状态表】

这一篇着重原始状态表 组合线路设计与时序线路设计的区别 组合线路设计方法&#xff1a; 确定输入和输出写真值表写表达式并化简根据题目给出的门或者其他要求进行变换&#xff08;取反&#xff09;画电路图 时序线路设计方法&#xff1a; 确定输入和输出&#xff0c;建立…

[Linux]------初识多线程

文章目录前言一、 Linux线程概念什么是线程线程理解证明C线程库二、页表三、线程的优点四、线程缺点五、线程异常六、线程的用途总结前言 本节重点&#xff01;&#xff01;&#xff01; 了解线程概念&#xff0c;理解线程与进程的区别和联系。学会线程控制&#xff0c;线程创…

因子模型套利定价理论APT的应用

本文是Quantitative Methods and Analysis: Pairs Trading此书的读书笔记。 一、APT&#xff08;套利定价理论&#xff09;应用于计算投资组合的风险 某个投资组合由两个股票A和B组成&#xff0c;它们的暴露系数向量(exposure vector)分别为和。两个股票在投资组合中的比重为…

Turtlebot2简单控制

遥控 遥控前为了让turtlebot接受命令&#xff0c;需要启动 roslaunch turtlebot_bringup minimal.lauch 键盘操作命令&#xff1a; roslaunch turtlebot_teleop keyboard_teleop.launch 简单脚本控制&#xff1a; 首先输入命令 roslaunch turtlebot_bringup minimal.lau…

WebRTC学习笔记七 pion/webrtc

一、Usage用法 1.使用Go Modules Go Modules are mandatory for using Pion WebRTC. So make sure you set export GO111MODULEon, and explicitly specify /v2 or /v3 when importing. 2.常见示例 example applications contains code samples of common things people bu…

Web3.0带来天翻地覆的变化?全面科普!所谓的Web3.0到底是什么?

Web3.0在2021年尾声突然蹿红&#xff0c;在美国国会的听证会里&#xff0c;一句“我们如何确保web3革命发生在美国”引发了大家对于Web3.0的关注&#xff0c;而后马斯克一篇内容为“有人看过web3.0吗? 我没有找到”的推文&#xff0c;将关于Web3.0的讨论推向了高潮。 甚至于这…

零基础入门JavaWeb——CSS相关知识

一、CSS的作用 SS是用于设置HTML页面标签的样式&#xff0c;用于美化HTML页面。 二、CSS的引入方式 2.1 行内样式 在要设置样式的标签中添加style属性&#xff0c;编写css样式&#xff1b;行内样式仅对当前标签生效。 <div style"border: 1px solid red;width: 10…

「MySQL高级篇」SQL优化

大家好&#xff0c;我是Zhan&#xff0c;一名个人练习时长一年半的大二后台练习生&#xff0c;最近在学MySQL高级篇&#xff0c;欢迎各路大佬一起交流讨论 &#x1f449;本篇速览 在前面对索引的的学习中&#xff0c;我们学习到了从MySQL“底层”优化了SQL执行查询的算法&…