Nacos 进阶篇---Nacos服务下线做了哪些事情 ?(八)

news2024/11/17 15:52:48
一、引言

   本章节是第一阶段最后一篇,那么我们今天要学习的源码内容是 “服务下线”.

   当Nacos客户端下线的时候,是要去通知服务端,告诉服务端 “ 我已经下线,不可用了 ”。并且在服务下线时,还要去通知其他客户端服务更新本地缓存列表,避免调用到已经下线的实例。

本章重点:

  • Nacos 客户端是怎么下线通知服务端的 ?
  • Nacos 服务端收到客户端的下线通知,做了什么操作 ?
  • 服务下线时,Nacos 服务端是怎么通知其他客户端更新本地缓存列表的 ?

二、目录  

目录

一、引言

二、目录  

三、客户端服务下线源码分析

四、服务端服务下线源码分析

五、变动事件发布源码分析

六、本章总结

七、第一阶段总结


三、客户端服务下线源码分析

主线任务:Nacos 客户端是怎么下线通知服务端的 ?

首先我们要先找到服务下线的代码入口在哪里 ?

当我们关闭Nacos客户端服务的时候,日志会打印出 [DEREGISTER-SERVICE] :销毁服务的意思,那我们直接根据这个 进行全局搜索,找到代码位置

可以看到这里发起调用 Nacos 服务端删除实例接口,那我们接着往上看,看看这个接口哪里调用了 ?

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

    NAMING_LOGGER
            .info("[DEREGISTER-SERVICE] {} deregistering service {} with instance: {}", namespaceId, serviceName,
                    instance);

    // 组装请求参数
    final Map<String, String> params = new HashMap<String, String>(8);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));

    // 调用Nacos服务端删除接口方法,请求地址:/nacos/v1/ns/instance
    reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.DELETE);
}

调用链路:destroy() -> stop() -> deregister() -> namingService.deregisterInstance(serviceId, group, registration.getHost(),

registration.getPort(), nacosDiscoveryProperties.getClusterName()) -> deregisterInstance(serviceName, groupName, instance); ->

serverProxy.deregisterService(NamingUtils.getGroupedName(serviceName, groupName), instance) -> reqApi(UtilAndComs.nacosUrlInstance,

params, HttpMethod.DELETE);

   最终看到是在 AbstractAutoServiceRegistration 类中的 destroy() 方法中调用了,这个方法还被 @PreDestroy修饰。

  @PreDestroy:当Spring容器销毁的时候,会回调被这些注解修饰的方法

小结:

在 AbstractAutoServiceRegistration 类中 destroy() 方法被@PreDestroy修饰,在Spring容器销毁的时候会去执行这个方法,从而调用 Nacos 服务端的删除实例接口,地址:/nacos/v1/ns/instance

四、服务端服务下线源码分析

主线任务:Nacos 服务端收到客户端的下线通知,做了什么操作 ?

通过请求路径得知,最终是在服务端 InstanceController 类中的 deregister 方法

@CanDistro
@DeleteMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String deregister(HttpServletRequest request) throws Exception {
    // 获取参数
    Instance instance = getIpAddress(request);
    String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);

    Service service = serviceManager.getService(namespaceId, serviceName);
    if (service == null) {
        Loggers.SRV_LOG.warn("remove instance from non-exist service: {}", serviceName);
        return "ok";
    }

    // 调用删除 instance 实例方法
    serviceManager.removeInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    return "ok";
}

可以看到下面整体逻辑方法跟服务注册代码基本一样,不同的点就在于 substractIpAddresses(service, ephemeral, ips); 这个方法, action 参数 一个传的是 add,一个传的是 remover,后面就跟注册服务代码逻辑完全就是一样的了

public void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
        throws NacosException {
    Service service = getService(namespaceId, serviceName);

    synchronized (service) {
        // 删除 instance
        removeInstance(namespaceId, serviceName, ephemeral, service, ips);
    }
}

private void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Service service,
        Instance... ips) throws NacosException {

    // 和注册服务逻辑一样,创建Key
    String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);

    // 在这个 instanceList 当中会移除不需要包含的 instance 实例
    List<Instance> instanceList = substractIpAddresses(service, ephemeral, ips);

    // 包装数据 Instances 对象
    Instances instances = new Instances();
    instances.setInstanceList(instanceList);

    // 后面就和注册服务逻辑完全一样,整体还是利用 异步任务 + 内存队列 的设计,最后包装成任务丢入到阻塞队列当中。
    // 丢入到阻塞队列后,后台开启一条线程,不断从队列中获取任务,最后利 用写使复制的方式,把数据写入到 Nacos 注册表当中!
    consistencyService.put(key, instances);
}

private List<Instance> substractIpAddresses(Service service, boolean ephemeral, Instance... ips)
        throws NacosException {
    // 在 updateIpAddresses 方法中,如果action 为 remove,会在最后返回把对应的 instance 删除
    // 调用 updateIpAddresses 方法,这里 action 传的是 remove (注册服务这里传的是 add)
    return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE, ephemeral, ips);
}

    那有的小伙伴就会好奇了,也没看到 删除 Naocs实例数据的代码,怎么就把服务实例移除了!

     重点还是在 substractIpAddresses 这个方法,在这个方法当中会把不需要 instance 实例列表进行移除,返回的 instanceList 就是最终需要替换的数据。然后就和服务注册一样的代码逻辑,异步任务 + 内存队列的设计,利用写时替换的方式,更新Nacos注册表的数据。

// 在这个 instanceList 当中会移除不需要包含的 instance 实例
List<Instance> instanceList = substractIpAddresses(service, ephemeral, ips);

private List<Instance> substractIpAddresses(Service service, boolean ephemeral, Instance... ips)
        throws NacosException {
    // 在 updateIpAddresses 方法中,如果action 为 remove,会在最后返回把对应的 instance 删除
    // 调用 updateIpAddresses 方法,这里 action 传的是 remove (注册服务这里传的是 add)
    return updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE, ephemeral, ips);
}

小结:

    Nacos 服务端收到客户端服务下线的接口请求后,会把 instance 实例列表进行移除,然后就和服务注册代码逻辑一样,利用写时替换的方式,更新Nacos注册表的数据。

五、变动事件发布源码分析

   通过服务发现的篇章我们可以得知,Nacos的客户端服务是有定时任务去维护本地缓存列表的。

    这样的话,本地缓存列表还是有延时的 ,不能完全跟Nacos注册表数据保持一致 ?

    其实在服务注册、服务下线,更改完Nacos注册表数据,服务端是会发布一个变动事件,然后通过 udp 的方式,去通知每一个客户端服务,从而让客户端感知速度更快

接下来我们就分析一下这段代码,看看如何来实现的?

  在异步onChange方法中,最后调用了updateIPs方法,在这个方法中,有这么一段代码,修改完Nacos注册表数据,就会去 利用 udp 方式来通知客户端。那我们来看下这段代码是怎么实现的 ?

// 针对每一个 clusterName,修改实例列表
for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet()) {
    List<Instance> entryIPs = entry.getValue();
    clusterMap.get(entry.getKey()).updateIps(entryIPs, ephemeral);
}

setLastModifiedMillis(System.currentTimeMillis());
// 利用 udp 方式来通知客户端
getPushService().serviceChanged(this);

在 serviceChanged 方法中,去发布了一个事件 ServiceChangeEvent 方法,那我们具体看 ServiceChangeEvent 方法中的逻辑。

public void serviceChanged(Service service) {
    // merge some change events to reduce the push frequency:
    if (futureMap
            .containsKey(UtilsAndCommons.assembleFullServiceName(service.getNamespaceId(), service.getName()))) {
        return;
    }

    // 发布 服务改变 事件
    this.applicationContext.publishEvent(new ServiceChangeEvent(this, service));
}

在 IDEA 中对这个 ServiceChangeEvent 方法进行全局搜索

在 onApplicationEvent 中,我们就看主要代码,主要用 udp 方式去通知每一个客户端服务!

@Override
public void onApplicationEvent(ServiceChangeEvent event) {

    Future future = GlobalExecutor.scheduleUdpSender(() -> {
        try {
            // 遍历需要通知的 客户端
            for (PushClient client : clients.values()) {
                udpPush(ackEntry);
            }
        } catch (Exception e) {
            Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);

        } finally {
            futureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));
        }

    }, 1000, TimeUnit.MILLISECONDS);
}

从这段代码我们就能看出,在 Nacos 服务端如果注册表中发生了变动,是会主动去通知客户端的,但是协议使用的是:UDP,这种协议比较轻量化,它无需建立连接就可以发送封装的 IP 数据包的方法,这种方式传输其实是不靠谱的。不靠谱也没关系,每一个客户端本地还有一个定时任务会去更新本地实例列表缓存的,所以影响不大。

六、本章总结

  当Spring容器销毁的时候,首先我们知道Nacos客户端服务下线,是会调用服务端删除实例的接口,在这个接口当中,会把 instance 实例列表进行移除,然后就和服务注册代码逻辑一样,利用写时替换的方式,更新Nacos注册表的数据。在Nacos注册数据表变动后,服务端是发布一个事件,然后利用 udp 的方式去通知每一个客户端服务。

七、第一阶段总结

前面已经讲了带上本章节,一共八节,我们来总结下分析过的源码内容:

客户端:

   注册服务:Spring容器启动,Nacos客户端利用事件监听,从而调用Nacos服务端 服务实例注册接口。在调用服务注册之前,客户端会开启一个 心跳健康检查异步任务。

   服务之间调用:在客户端进行服务之间调用时,Nacos整合了Ribbon,从而查询Nacos服务实例列表,来维护本地缓存数据,然后进行负载均衡服务调用。

   服务下线:在Spring容器销毁的时候,会触发Nacos销毁的方法,会去调用服务端服务下线接口,从而完成服务下线流程

服务端:

我们分析几个核心功能:服务注册、服务查询、服务下线、心跳健康。

   服务注册:在服务注册的时候,我们讲了是利用 异步任务+内存队列的设计来完成的,最后是通过 写时复制来往Nacos注册表当中写入数据。

   服务查询:服务查询查询的话,是直接从Nacos注册表当中获取Instance 列表。

   服务下线:在服务下线的时候,会把 instance 实例列表进行移除,利用写时替换的方式,更新Nacos注册表的数据。在Nacos注册数据表变动后,服务端是发布一个事件,然后利用 udp 的方式去通知每一个客户端服务。

   心跳健康:服务端会开启心跳健康检查任务,把 lastBeat 跟当前时间比超过 15s,就会被标识为不健康的实例,把lastBeat 跟当前时间比超过 30s,Nacos 会把该 Instance 从注册表当中进行删除。

第一阶段源码分析图:

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

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

相关文章

uni-app解决表格uni-table样式问题

一、如何让表格文字只显示一行&#xff0c;超出部分用省略号表示 步骤 &#xff1a; 给table设置table-layout:fixed; 列宽由表格宽度和列宽度设定。&#xff08;默认是由单元格内容设定&#xff09;让表格元素继承父元素宽度固定table-layout: inherit;overflow: hidden;超过…

STM32-电灯,仿真

目录 1.配置vscode 2.新创建软件工程 3.仿真 4.源码 5.运行效果 1.配置vscode http://t.csdnimg.cn/BvCLx 安装 C/C Extension Pack 安装 Embedded IDE 安装 Keil MDK 配置路径 2.新创建软件工程 下拉找到对应的 输入项目名字,选择项目所在文件夹即可 3.仿真 一路新…

机器视觉分析在加油站安全中的应用:使用手机检测、打电话行为识别

在加油站等高危场所&#xff0c;禁止使用手机是为了防止潜在的火灾和爆炸风险。手机在使用过程中可能产生电火花&#xff0c;而在加油站这种易燃易爆环境中&#xff0c;任何电火花都可能引发严重的安全事故。因此&#xff0c;加油站禁止使用手机是保障安全生产的重要措施。基于…

【好书分享第十三期】AI数据处理实战108招:ChatGPT+Excel+VBA

文章目录 一、内容介绍二、内页插图三、作者简介四、前言/序言五、目录 一、内容介绍 《AI数据处理实战108招&#xff1a;ChatGPTExcelVBA》通过7个专题内容、108个实用技巧&#xff0c;讲解了如何运用ChatGPT结合办公软件Excel和VBA代码实现AI办公智能化、高效化。随书附赠了…

深入解析多维数组与主对角线元素之和

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;多维数组的奥秘 二、多维数组的基本概念 1. 定义与创建 2. 维度与形…

(文章复现)分布式电源接入配电网承载力评估方法研究

参考文献&#xff1a; [1]郝文斌,孟志高,张勇,等.新型电力系统下多分布式电源接入配电网承载力评估方法研究[J].电力系统保护与控制,2023,51(14):23-33. 1.摘要 随着光伏和风电等多种分布式电源的接入&#xff0c;使得传统配电网的结构及其运行状态发生了较大改变。因此&…

Tableau解包与版本兼容性

Tableau解包与版本兼容性 1、背景描述2、Tableau解包3、Tableau版本兼容性 1、背景描述 有时&#xff0c;在使用Tableau Desktop打开.twbx打包工作簿时&#xff0c;可能会出现如下弹框&#xff1a; 通常考虑以下两种处理情况 2、Tableau解包 解包打包的.twbx工作簿&#xff0c…

会议管理系统(含源码+sql+视频导入教程)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 会议管理系统拥有两种角色 管理员&#xff1a;部门管理、员工管理、会议管理、会议室管理、预订会议、添加员工、注册员工审批等 用户&#xff1a;个人通知中心、预订会议、查看所有会议…

强化学习——学习笔记3

一、强化学习都有哪些分类&#xff1f; 1、基于模型与不基于模型 根据是否具有环境模型&#xff0c;强化学习算法分为两种&#xff1a;基于模型与不基于模型 基于模型的强化学习(Model-based RL)&#xff1a;可以简单的使用动态规划求解&#xff0c;任务可定义为预测和控制&am…

文心智能体平台 | 想象即现实

目录 文心智能体平台介绍平台简介通过平台能做什么平台的优势智能体介绍智能体类型AI 插件介绍 动手创建一个智能体访问平台并进行账号注册根据适合的方式选择智能体类型快速创建智能体智能体个性化模块配置 总结注意事项我的智能体 文心智能体平台介绍 平台简介 文心智能体平…

ArcGIS常规操作-带你创建正确的空间数据库

ArcGIS常规操作-带你创建正确的空间数据库 ArcGIS一词在各行业中出现得越来越多&#xff0c;尤其在国土空间规划中&#xff0c;依赖大量GIS工具和技术的应用&#xff0c;ArcGIS成为了必备的技能之一。今天讲解一下ArcGIS的基础操作&#xff0c;让大家可以快速地上手ArcGIS&…

这有一封“数字赋能 强农兴村”的邀请函,请查收→

2024年6月5日至7日&#xff0c;以“数字赋能 强农兴村”为主题的2024中国&#xff08;南京&#xff09;数字乡村博览会暨第八届中国&#xff08;南京&#xff09;国际智慧农业博览会将在江苏南京举办。珈和科技在农业AI大数据展区的H16展位与您相约&#xff0c;共襄盛会。 私信…

祝贺!阿里云PolarDB斩获数据库国际顶会ICDE 2024工业赛道最佳论文

5月17日消息&#xff0c;在荷兰举行的国际顶级数据库学术会议ICDE 2024上&#xff0c;阿里云斩获工业和应用赛道的“最佳论文奖”&#xff0c;这也是中国企业首次获此殊荣。阿里云PolarDB创新性地解决了数据库Serverless中跨机事务迁移的核心难题&#xff0c;将跨机迁移时间压缩…

消费者组到底是什么?no.15

Kafka的消费者组。 消费者组&#xff0c;即Consumer Group&#xff0c;应该算是Kafka比较有亮点的设计了。那么何谓Consumer Group呢&#xff1f;用一句话概括就是&#xff1a;Consumer Group是Kafka提供的可扩展且具有容错性的消费者机制。既然是一个组&#xff0c;那么组内必…

LLM - 模型下载与 git-lfs 安装

目录 一.引言 二.安装 git lfs 1.使用 apt-get 安装 2.使用 Brew 安装 3.LFS 验证 三.总结 一.引言 在 HuggingFace 上下载模型时提供一个 git clone 的指令&#xff0c;执行后可以下载对应模型的模型文件: 但是本机还没有 git lfs 命令: git: lfs is not a git comman…

从这些原理中,读懂迅软DSE加密系统

加密技术是保护信息安全的系统&#xff0c;通过对原始数据进行加密&#xff0c;使得未经授权的人无法读取这些信息。 一、迅软DSE加密系统干什么用的&#xff1f; ★保护隐私&#xff1a;加密确保个人、机构的敏感信息在传输和存储过程中不被未授权的人访问。 ★防止数据泄露…

从简单到复杂,红酒配餐的层次感与变化

红酒配餐是一种艺术&#xff0c;通过不同层次的搭配&#xff0c;可以呈现出丰富的味觉变化&#xff0c;使每一口都充满惊喜。云仓酒庄雷盛红酒以其卓着的品质和与众不同的口感&#xff0c;为红酒配餐提供了无限可能。从简单到复杂&#xff0c;红酒配餐的层次感与变化如下&#…

CAD石墨烯生成器 V1.0 渊鱼

插件介绍 CAD石墨烯生成器插件可用于在AutoCAD软件内参数化建立石墨烯几何模型。插件建立石墨烯的球棍模型&#xff0c;可控制模型的尺寸、碳原子环的尺寸、原子直径、化学键直径&#xff0c;并可控制模型的起伏形态。插件生成的实体模型可进行修改或绘图渲染&#xff0c;用于…

flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果

flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果 在之前处理类似微博帖子列表及下拉刷新上拉加载效果&#xff0c;刷新使用的是EasyRefresh 一、引入EasyRefresh与likeButton 在工程的pubspec.yaml中引入插件 # 下拉刷新、上拉更多easy_refresh: ^3.3.21pull_to_re…

每天写两道(二)LRU缓存、数组中最大的第k个元素

146.LRU 缓存 . - 力扣&#xff08;LeetCode&#xff09; 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类&#xff1a; LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存…