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

一、引言

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

   当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 从注册表当中进行删除。

第一阶段源码分析图:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux 服务器配置 SSH 服务登录失败处理

任务目标 配置 Linux 服务器ssh远程登录失败处理机制&#xff0c;防止黑客爆破服务器密码 操作步骤 备份原配置文件 $ sudo cp /etc/pam.d/sshd /etc/pam.d/sshd.bak $ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak安装 pam_tally2 统计登陆失败次数 # 对于Debia…

UDP网络聊天室(更)

服务器端 #include <header.h> typedef struct node {char name[20];struct sockaddr_in cli_addr;struct node *next; }node,*node_p; typedef struct msg {char type;char name[20];char text[128]; }msg; node_p create_link() {node_p H(node_p)malloc(sizeof(node)…

嵌入式进阶——HID协议

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 USB烧录USB HID协议USB协议组成通讯流程 官方USB HID范例文件说明修改PC端的显示 兼容库函数HID键盘USB调试工具USB 描述符设备描述…

29-ESP32-S3-WIFI篇-00 STA模式扫描全部 AP

ESP32-S3 WIFI_Driver 引言 ESP32-S3是一款集成了Wi-Fi和蓝牙功能的芯片。关于WIFI的部分&#xff0c;其实内容比我想象的要多得多。所以通常来说&#xff0c;如果你想要编写自己的Wi-Fi应用程序&#xff0c;最快捷的方法就是先找一个类似的示例应用&#xff0c;然后将它的相…

输入与输出的魔法:探索Python的内置函数

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、从键盘捕获输入&#xff1a;input()函数的力量 二、打印输出&#xff1a;print()函数的…

简单模拟实现shell(Linux)

目录​​​​​​​ 前言 展示效果 实现代码 前言 该代码模拟了shell的实现&#xff0c;也就是解析类似于“ls -a -l"的命令&#xff0c;当我们启动我们自己写的shell的可执行程序时&#xff0c;我们输入"ls"的命令&#xff0c;也可以展示出在shell中输入&…

50etf期权购是什么意思?

今天带你了解50etf期权购是什么意思&#xff1f;很多刚刚接触50ETF期权的投资者或许不太明白50ETF期权投资是一种什么样的投资&#xff0c;对于50ETF期权投资来说&#xff0c;有认购合约与认沽合约&#xff0c;那么“购”也就是认购的意思。 50etf期权购是什么意思&#xff1f;…

速看!!24上软考【电子商务设计师】真题回顾,含答案解析

2024上半年软考考试已经结束了&#xff0c;为大家整理了网友回忆版的电子商务设计师真题及答案&#xff0c;25-26日两批考试总共60道题。 上半年考试的宝子们可以对答案预估分数&#xff01;准备下半年考的宝子可以提前把握考试知识点和出题方向&#xff0c;说不定会遇到相同考…

当今世界三个最厉害的人物颜廷利:全球最受欢迎的点赞第一人

我们生活在一个历史的拐点&#xff0c;一个要求我们重新构思自我、解读世界以及预见未来的时代。在这个时代&#xff0c;那些能够洞悉社会发展的深层逻辑&#xff0c;并据此提出全新定义的人&#xff0c;将成为引领人类思维、塑造普遍价值观和主导全球发展议程的先锋。因此&…

插件“猫抓”使用方法 - 浏览器下载m3u8视频 - 合并 - 视频检测下载 - 网课下载神器

前言 浏览器下载m3u8视频 - 合并 - 网课下载神器 chrome插件-猫抓 https://chrome.zzzmh.cn/info/jfedfbgedapdagkghmgibemcoggfppbb 步骤&#xff1a; P.s. 推荐大佬的学习视频&#xff01; 《WEB前端大师课》超级棒&#xff01; https://ke.qq.com/course/5892689#term_id…

01_Spring Ioc DI案例,setter方法和构造方法注入(详解) + 思维导图

文章目录 一.概念实操Maven父子工程 二. IOC和DI入门案例【重点】1 IOC入门案例【重点】问题导入1.1 门案例思路分析1.2 实现步骤2.1 DI入门案例思路分析2.2 实现步骤2.3 实现代码2.4 图解演示 三、Bean的基础配置问题导入问题导入1 Bean是如何创建的【理解】2 实例化Bean的三种…

Java导出excel带图片(希望能帮助你们节省时间)

第一天太慌张&#xff0c;下班逃跑&#xff0c;一夜没睡好&#xff0c;第二天决定搞出来。 查了好多博客&#xff0c;感觉都挺繁琐的&#xff0c;好多工具类、引入类找不到。经过一上午的琢磨&#xff0c;终于搞定。记录一下 借鉴了这个博主的文章 需求前端点击导出按钮&#…

HackTheBox-Machines--Lazy

Lazy测试过程 1 信息收集 1.端口扫描 发现 SSH&#xff08;22&#xff09;、HTTP&#xff08;80&#xff09;端口 nmap -sC -sV 10.129.159.512.访问 80 端口 1.页面中存在注册功能&#xff0c;测试注册功能 页面返回登录页面及用户名 使用burpsuite观察注册请求 /register.p…

骑车不戴头盔监测摄像机

骑行是一种健康的出行方式&#xff0c;但是在骑行途中不戴头盔存在安全隐患&#xff0c;容易造成头部受伤。为了规范骑行行为&#xff0c;保障骑行安全&#xff0c;可以考虑使用骑车不戴头盔监测摄像机进行监测和识别。这种摄像机可以通过智能识别技术&#xff0c;实时监测骑自…

Centos7时区设置及手动修改时间

一、修改系统时区 1、查看时区命令 timedatectl 2、设置时区命令 #下面将时区设置为上海时区 timedatectl set-timezone Asia/Shanghai 3、查看时区看一下新时区有没有生效 timedatectl 二、手动修改系统时间 修改系统时间 date -s "2023-12-25 16:05:10" 查…

串口调试助手中文乱码 解决方案

输出乱码 一般&#xff0c;当串口调试助手输出乱码时&#xff0c;可能有以下几个原因&#xff1a; 波特率设置错误&#xff1a;串口通信需要保证发送和接收的设备使用相同的波特率。请检查串口调试助手和目标设备的波特率设置是否一致。 数据位、停止位或校验位设置错误&…

自建视频托管平台:MediaCMS

目录 1 MediaCMS简介1.1 介绍1.2 特性1.3 应用场景 2 安装配置2.1 安装1、安装2、汉化 2.2 一些常见配置 3 简单使用3.1 上传3.2 下载3.3 添加标题或者字幕3.4 通过Tag/Category实现视频/文件分类添加 Tag给任一资源分类 1 MediaCMS简介 1.1 介绍 MediaCMS是一个现代的&#…

Vue项目运行页面禁止缩放【移动端和PC端都禁止缩放】解决方案

Vue项目运行页面禁止缩放【移动端和PC端都禁止缩放】解决方案&#xff0c;有的人手很J,总喜欢放大缩小&#xff0c;从而会导致页面错乱&#xff0c;以下是解决方案&#xff0c;简单有效 效果图PC&#xff1a;滚轮缩放和其他缩放都会禁止 移动端效果图&#xff1a;各种手机平板…

活动会议邀请函制作易企秀源码系统 清爽的画面轻轻滑动自动翻页 带完整的前后端搭建教程

系统概述 在当今数字化时代&#xff0c;活动会议的组织和宣传变得至关重要。为了满足这一需求&#xff0c;活动会议邀请函制作易企秀源码系统应运而生。它不仅为用户提供了一个便捷、高效的工具&#xff0c;还具备一系列令人瞩目的特色功能&#xff0c;为活动会议的成功举办提…

Redis篇 哈希表在redis中的命令

哈希命令 一.哈希表的基本认识二. 哈希表在redis中的命令1.hset,hget2.hdel3.hkeys,hvals4.hexists5.hgetall6.hmget7.hlen8.hincrby和hincrbyfloat 一.哈希表的基本认识 在JAVA数据结构中&#xff0c;我们就已经接触到了哈希表&#xff0c; 在当时&#xff0c;我们主要用到的哈…