Nacos 入门篇---服务端如何处理客户端的服务注册请求?(三)

news2024/11/17 23:46:27
一、引言

 ok呀,上个章节我们讲了Nacos客户端的服务自动注册,今天我们来看看服务端接收到了客户端的服务注册请求,服务端都做了哪些事情~

二、目录

目录

一、引言

二、目录

三、回顾上节内容:

四、Nacos 服务代码入口分析​​​​​​​

五、Nacos 服务端服务注册源码分析

六、本章小结


三、回顾上节内容:

     上个章节我们从 Nacos 客户端源码开始讲解,从 Nacos Discover 依赖中,发现了spring.factories创建了相关的配置类,其中就包括了注册类NacosServiceRegistryAutoConfiguration ,这个配置类创建了三个bean对象。其中有一个bean对象实现了监听事件的方法。当Spring容器启动的时候,就会发布一个事件。这个bean对象就会监听到,从而执行真正的注册方法,执行的时候会发送一个健康检查延时任务,告诉Nacos服务端我这个服务还活着,健康检查延时任务每5s执行一次。

四、Nacos 服务代码入口分析

主线任务:找到注册中心模块代码的入口

我们打开Nacos 1.4.1 源码项目,看到了这么多模块,是不是有点懵逼,不知道怎么下手~

这个时候,我们就可以从Nacos的架构图来入手了。注册中心在架构图中叫 naming Server,那我们就在项目中找到 naming 的项目模块。


我们打开项目目录结构一看,这不纯纯就是Springboot项目嘛。我们要找接口肯定就在Controller的包下。

  注册实例的接口地址为:/nacos/v1/ns/instance,一猜就是InstanceController类。再看下RequestMapping当中的请求地址,常量拼接起来:v1/ns/instance。正好跟我们要找的请求地址相对应,那代码就是在这个类当中了。

五、Nacos 服务端服务注册源码分析

主线任务:Nacos服务端接受到了实例注册请求,它做了什么事 ?

    我们先看接受请求的代码,很简单。先从request当中获取参数,然后调用serviceManager.registerInstance() 方法。

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {

    // 接受客户端的参数
    // 从request当中获取 namespaceId(命名空间ID)、serviceName(服务名称)、Instance实例
    final String namespaceId = WebUtils
            .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);

    // Instance 里面就包含了客户端的 ip、port 等信息
    final Instance instance = parseInstance(request);

    // 上面代码就是获取参数
    // 主线任务:调用服务注册的实现类
    serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

紧接着看serviceManager类中的registerInstance方法。因为我们第一次看,抓住主线任务代码看,分支代码后面可以再去细看。

public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {

    // 不知道是创建了一个什么服务
    createEmptyService(namespaceId, serviceName, instance.isEphemeral());

    // 根据namespaceId、serviceName获取 Service服务
    Service service = getService(namespaceId, serviceName);

    // service为空就抛出异常
    if (service == null) {
        throw new NacosException(NacosException.INVALID_PARAM,
                "service not found, namespace: " + namespaceId + ", service: " + serviceName);
    }

    // 上面都是分支代码
    // 主线任务:添加服务实例
    addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
}

然后调用了 ServiceManager当中的 addInstance方法,这里key还是比较重要的,这个后面去讲一下。

public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
        throws NacosException {

    // 根据namespaceId、serviceName、ephemeral,获取一个Key,在代码最后put进去
    // 重点:后面再去补充一下这个Key的生成规则
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

    // 根据namespaceId、serviceName,获取 Service服务
    Service service = getService(namespaceId, serviceName);

    // 锁住一个service
    synchronized (service) {
        // 这里提前说一下,ips 上层方法传过来的,是本次实例注册对应的Instance,也就是已开始从Request里面获取的参数信息。
        // 最后会放在instanList里面,为什么这里是List,说明它不仅仅只有一个,还会包含之前已经注册的Instance,放在了一个List里面
        List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);

        // 创建一个 Instances 对象,并且把 instanceList 属性set进去
        Instances instances = new Instances();
        instances.setInstanceList(instanceList);

        // 主线任务:调用了consistencyService.put 方法,把key和Instances对象当作参数传进去
        consistencyService.put(key, instances);
    }
}

然后接着往 consistencyService.put() 方法点击调用时,发现了有好几个类都实现该接口。怎么办 ?

两种办法:

  • 第一种:Debug的方式,一步一步进去看,就知道对应哪个类了。
  • 第二种:看这个属性是怎么注入的。

这里的话,我们采用第二种方法。如下代码,我们能看到这个对象来自consistencyDelegate,注入的时候已经指定对应的Bean了,Bean的名字肯定是唯一的。这里我们用IDEA快捷键跳转,一下子就能找到了。

然后接着调用DelegateConsistencyServiceImpl实现类当中的最后一步put方法了,根据不同的key选择不同对象进行put方法。

@Override
public void put(String key, Record value) throws NacosException {
    // 先通过key选择不同的 Service,然后调用对应 Service 的 put 方法
    mapConsistencyService(key).put(key, value);
}

private ConsistencyService  mapConsistencyService(String key) {
    return KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService;
}

那这个 Record 参数是什么 ?

其实就是 Instances 对象实现了 Record 的接口,Instances 对象当中有个instanceList属性,这个属性包含了之前已经注册的实例和新需要注册的实例。

private List<Instance> instanceList = new ArrayList<>();

那这个 key 是怎么来的 ?生成规则是什么 ?(这里重点说明一下,不然都不知道方法接着该怎么走了)这个key的生成是在前面 addInstance() 的方法当中

// namespaceId:命名空间id , serviceName: 服务名称,ephemeral:是否为临时实例
String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

点进 KeyBuilder.buildInstanceListKey() 方法当中看,是根据 ephemeral 这个参数来生成不同的key。

ephemeral 这个参数看着有点眼熟,之前在讲 Nacos客户端 发送心跳的时候遇到过,根据 ephemeral 这个参数判断是否发送健康心跳。这个参数默认是为:true

那在服务端这个 ephemeral 参数是什么意思呢?我第一次看也有点懵逼,这个时候还是得看官方Open API文档。

public static String buildInstanceListKey(String namespaceId, String serviceName, boolean ephemeral) {
    // 根据 ephemeral 来生成不同的 key
    // ephemeral:是否为临时实例 默认为:true
    return ephemeral ? buildEphemeralInstanceListKey(namespaceId, serviceName)
            : buildPersistentInstanceListKey(namespaceId, serviceName);
}

看到文档上的说明,ephemeral:是否临时实例

那我们就可以得知,Naocs 客户端默认注册的实例就是临时实例。那到底什么是临时实例,这个就和AP、CP架构有关系了,这个后面看集群源码的时候再去说明,现在你只要知道,Nacos 默认注册的实例都是临时实例。

ok,那我们接着看 key的生成规则,既然 ephemeral 为true的话,走的是 buildEphemeralInstanceListKey(namespaceId, serviceName) 方法。

那拼接起来key的格式为:com.alibaba.nacos.naming.iplist.ephemeral. +namespaceId + ## + serviceName

private static String buildEphemeralInstanceListKey(String namespaceId, String serviceName) {
    // com.alibaba.nacos.naming.iplist.ephemeral. +namespaceId + ## + serviceName
    return INSTANCE_LIST_KEY_PREFIX + EPHEMERAL_KEY_PREFIX + namespaceId + NAMESPACE_KEY_CONNECTOR + serviceName;
}

那我们现在知道了 key 的大概生成规则是什么样了~

我们接着看 DelegateConsistencyServiceImpl 当中的mapConsistencyService方法。

那 key 是默认为 com.alibaba.nacos.naming.iplist.ephemeral. +namespaceId + ## + serviceName格式的,所以这里应该返回 ephemeralConsistencyService对象。

private ConsistencyService  mapConsistencyService(String key) {
    // 判断根据不同的key选择不同的对象进行返回
    return KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService;
}

public static boolean matchEphemeralKey(String key) {
    // currently only instance list has ephemeral type:
    return matchEphemeralInstanceListKey(key);
}

// 判断key包含com.alibaba.nacos.naming.iplist.ephemeral开头.就为true,返回 ephemeralConsistencyService 对象
public static boolean matchEphemeralInstanceListKey(String key) {
    return key.startsWith(INSTANCE_LIST_KEY_PREFIX + EPHEMERAL_KEY_PREFIX);
}

那我们就着往 ephemeralConsistencyService 对象当中的put方法看。这个 Record 参数前面已经说过了。直接看核心代码 onPut() 方法。

@Override
public void put(String key, Record value) throws NacosException {
    // 核心代码
    onPut(key, value);

    // 集群节点同步,这个后面文章中讲解
    distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
            globalConfig.getTaskDispatchPeriod() / 2);
}

onPut() 方法 创建Datum对象,把key、和 Instances都放入Datum对象里面去,最后调用了 dataStore.put 方法。点进去,接着往下看,如下图:

public void onPut(String key, Record value) {

    // 这里还是判断刚刚那个 key 前缀,这里是为 true
    if (KeyBuilder.matchEphemeralInstanceListKey(key)) {
        // 创建Datum对象,把key、和 Instances都放入Datum对象里面去
        Datum<Instances> datum = new Datum<>();
        datum.value = (Instances) value;
        datum.key = key;
        datum.timestamp.incrementAndGet();
        // 最后添加到dataStore当中,这个 dataStore就是一个Map对象
        dataStore.put(key, datum);
    }

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

    // 主线任务:添加任务
    notifier.addTask(key, DataOperation.CHANGE);
}

DataStore 里面有个Map方法,put 方法也就是把刚刚创建的 Datum 当作 value 放了进去。key就是前面生成的key,value包含了 key 以及 instances 两部分。

最后我们来看下 addTask 方法,datumKey 还是前面 addInstance 方法生成的key,action 是 DataOperation.CHANGE。最后把把key、action包装成 Pair 对象,放入到阻塞队列当中就结束了。

private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);

/**
 * 向队列中添加新的通知任务。
 * @param datumKey data key
 * @param action   action for data
 */
public void addTask(String datumKey, DataOperation action) {

    if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
        return;
    }
    if (action == DataOperation.CHANGE) {
        services.put(datumKey, StringUtils.EMPTY);
    }

    // 主线任务:taskks 是一个阻塞队列,并且把key、action包装成 Pair 对象,放入队列当中
    tasks.offer(Pair.with(datumKey, action));
}

看完了代码思路不够清晰,这是本章节的分析图,可以根据分析图再过下代码:

六、本章小结

主线任务:Nacos服务端接受到了实例注册请求,它做了什么事 ?

通过源码分析可以得知,先从 request 请求当中接受从客户端传过来的参数。在 addInstance 方法当中生成了一个 key。根据调用注入接口bean得知,调用到了ConsistencyService接口下的DelegateConsistencyServiceImpl实现类当中的put方法。然后又根据不同 key 判断选择调用不同 Service。

最终默认调用到了 EphemeralConsistencyService 接口下的DistroConsistencyServiceImpl当中put方法,又在put方法当中调用了 onPut 方法,在onPut 方法当中,创建了Datum对象,把key、和 Instances都放入Datum对象里面去。放入了DataStore中的Map里。这个步骤有什么用?后面分析源码在进行说明

最后调用了addTask 方法,把key、action包装成 Pair 对象,放入阻塞队列当中。

你以为放入到阻塞队列就完了吗 ?答案:肯定不是,这个后面我们在进行分析,大家可以猜一猜后面都做了什么操作

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

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

相关文章

Linux 网络排查命令

端口相关服务检查 netstat -ntpl|grep [$Port]说明&#xff1a;[$Port]为相应的端口号。 0.0.0.0代表本机上可用的任意地址。比如&#xff0c;0.0.0.0:80表示本机上所有地址的80端口。 tcp 0.0.0.0:80表示在所有的可用接口上监听TCP的80端口 如果返回结果为空&#xff0c;说明…

Java 入门教程||Java 关键字

Java 关键字 Java教程 - Java关键字 Java中的关键字完整列表 关键词是其含义由编程语言定义的词。 Java关键字和保留字&#xff1a; abstract class extends implements null strictfp true assert const false import package super try …

奥尔波特、卡特尔、大五人格的特质流派,预测你是内向还是外向

了解特质&#xff0c;可以预测人的行为&#xff0c;可以预测你的性格是内向还是外向。 特质论并不把人格分为绝对的类型&#xff0c;通常认为存在一些特质维度。人们之间的差异&#xff0c;就在于这些维度上表现程度的不同所形成的不同特质构型。 特质&#xff08;trait&…

Python数据结构与算法——算法(贪心算法、动态规划

贪心算法 介绍&#xff1a;贪心算法又称贪婪算法&#xff0c;是指在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;它所做出的是在某种意义上的局部最优解。 贪心算法并不保证会得到最优解&#xff0c;但…

发誓不在黄金周旅游,可惜管不住自己,看看景区可视化,觉悟了。

2023-10-04 23:29贝格前端工场 景区可视化大屏可以起到以下几个作用&#xff1a; 数据监控和分析&#xff1a;景区可视化大屏可以实时监控景区的各项数据指标&#xff0c;如游客数量、门票销售情况、景区设备运行状态等&#xff0c;通过可视化展示&#xff0c;管理人员可以快…

C/C++基础----指针

指针的定义 在c/c中&#xff0c;有一个特殊的变量指向我们电脑中某个内存地址&#xff0c;进而可以让我们操作这段内存&#xff0c;指的就是指针类型 语法&#xff1a; int a 10; int* p &a;&符号是取出某个变量的内存地址 把这个内存地址赋值给一个变量p&#xff…

分享一个 git stash 的实际使用场景。

当我将新的变更记录提交为 git commit --amend 后&#xff0c;发现这需要修改云端上的提交记录&#xff0c;也就是 vscode 中会出现这张图 于是&#xff0c;我通过 git reset head^ 撤销掉刚刚的提交。 reset 前&#xff1a; reset 后&#xff1a; 但在撤销的同时&#xf…

华院计算参编《金融业人工智能平台技术要求》标准

随着人工智能技术的迅猛发展&#xff0c;金融机构正在从业务场景化向企业智能化演进&#xff0c;金融业对智能化的需求愈加迫切。为引导产业有序发展、规范行业自律、加快金融行业智能化转型&#xff0c;中国信通院依托中国人工智能产业发展联盟&#xff08;AIIA&#xff09;及…

机器学习 | 使用Scikit-Learn实现分层抽样

在本文中&#xff0c;我们将学习如何使用Scikit-Learn实现分层抽样。 什么是分层抽样&#xff1f; 分层抽样是一种抽样方法&#xff0c;首先将总体的单位按某种特征分为若干次级总体&#xff08;层&#xff09;&#xff0c;然后再从每一层内进行单纯随机抽样&#xff0c;组成…

第19讲:自定义类型:结构体

1.结构体类型的声明 2.结构体变量的创建和初始化 3.结构体变量的赋值 4.结构体成员访问操作符 5.结构体内存对齐 1.结构体类型的声明 1.1结构体声明 struct 结构体名称 { 结构体成员... } 举例&#xff1a;描述一个学生&#xff1a; 2结构体变量的创建和初始化 2.1匿…

Linux文本编辑器vim使用和分析—2

目录 1.对vim的简单理解&#xff1a; 2.看待vim的视角&#xff1a; 3.命令模式&#xff1a; 3.1vim被打开后默认的模式&#xff1a; 3.2命令模式切换插入模式&#xff1a; 3.3其他模式回到命令模式&#xff1a; 3.4光标定位&#xff1a; 4.插入模式(编辑模式)&#xff1…

数字乡村创新实践探索农业现代化与农村治理现代化新路径:科技赋能农村全面振兴与农民幸福生活

目录 引言 一、数字乡村与农业现代化 1、智慧农业技术的应用 2、农业产业链的数字化转型 二、数字乡村与农村治理现代化 1、农村信息化水平的提升 2、农村治理模式的创新 三、科技赋能农村全面振兴与农民幸福生活 1、提升农业生产效益与农民收入 2、促进农村产业结构…

【ArcGIS 脚本工具】在ArcPro中实现mdb转gdb

ArcGIS Pro作为主力使用很久了&#xff0c;但是ArcMap也从来没有卸载过。 要问为什么&#xff0c;就是还需要ArcMap来读写mdb数据库&#xff0c;Pro是不支持读写mdb数据库的。 我之前尝试过不借助ArcMap把mdb转成gdb&#xff0c;奈何技术太菜搞不定。 直到我看到了公众号【G…

人工智能轨道交通行业周刊-第77期(2024.4.1-4.14)

本期关键词&#xff1a;货车巡检机器人、铁路安全技防、车辆换长、阿里千问、大模型创业 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界…

现代电商会员管理新玩法——付费会员

现在&#xff0c;无论是线上还是线下&#xff0c;几乎都在做会员管理。会员规则五花八门&#xff0c;不仅有常见的注册会员&#xff0c;还出现了付费会员。付费会员机制&#xff0c;从成为会员的第一步就开始筛选&#xff0c;选出粘性高、要求高、复购高且有一定消费力的用户群…

缓存与数据库的数据一致性解决方案分析

在现代应用中&#xff0c;缓存技术的使用广泛且至关重要&#xff0c;主要是为了提高数据访问速度和优化系统整体性能。缓存通过在内存或更快速的存储系统中存储经常访问的数据副本&#xff0c;使得数据检索变得迅速&#xff0c;从而避免了每次请求都需要从较慢的主存储&#xf…

labview中的同步定时结构

单帧定时循环定时比较精确&#xff0c;最常用的功能还是它的定时循环功能&#xff0c;定时循环允许不连接“循环条件”端子&#xff0c;可以连接定时循环“结构名称”端子&#xff0c;通过定时结构停止函数停止循环。 例子在附件中。

MySQL workbench使用教程(逐渐补充版)

附件&#xff1a; 附1&#xff1a;MySQL下载、安装、配置之Windows 附2&#xff1a;MySQL workbench下载、安装、配置、汉化教程 一、 使用 Workbench 操作数据库 1.MySQL Workbench 初始化界面 2.连接远程 MySQL 数据库 3.创建数据库 切换至schemas标签&#xff0c;右键单…

在家如何查找下载外文文献

查找下载外文文献的数据库大部分都需要使用权限的&#xff0c;那么我们如何在家进入这些数据库查找下载文献资源呢&#xff1f;请看本文的经验分享&#xff1a; 举例1、 一位同学的文献求助&#xff1a;Performance of financial hedging and earnings management under dive…

科研学习|科研软件——如何使用SmartPLS软件进行结构方程建模

SmartPLS是一种用于结构方程建模&#xff08;SEM&#xff09;的软件&#xff0c;它可以用于定量研究&#xff0c;尤其是在商业和社会科学领域中&#xff0c;如市场研究、管理研究、心理学研究等。 一、准备数据 在使用SmartPLS之前&#xff0c;您需要准备一个符合要求的数据集。…