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/instancereqApi(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) {// 删除 instanceremoveInstance(namespaceId, serviceName, ephemeral, service, ips);}
}private void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Service service,Instance... ips) throws NacosException {// 和注册服务逻辑一样,创建KeyString 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.mzph.cn/news/844242.shtml

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

相关文章

Linux命令那么多,先来一篇文件和目录管理命令!

&#x1f4a1;本文建议大家收藏&#xff01; 文件和目录管理命令 1. ls - 列出目录内容 ls命令是Linux中最常用的命令之一&#xff0c;用于列出目录中的文件和子目录。 ls显示当前目录下的所有文件和目录。 ls -l以长格式列出目录内容&#xff0c;显示文件权限、所有者、大…

如何便捷申请免费SSL证书,实现网站HTTPS安全传输

申请免费SSL证书的教程可以概括为以下几个通用步骤&#xff0c;这里以Lets Encrypt为例&#xff0c;因为它是最受欢迎的免费SSL证书提供商之一&#xff0c;同时也适用于多数其他免费SSL证书提供商&#xff1a; 1.准备工作 确认域名&#xff1a;确保你拥有一个有效的域名&…

CSS学习笔记:响应式布局的原理——媒体查询

什么是响应式布局&#xff1f; 在实际书写代码时&#xff0c;我们不会自己去手写媒体查询来实现响应式布局&#xff0c;我们一般会调用现成的代码库或使用现成的框架&#xff08;但这些代码库或框架的底层原理是媒体查询&#xff0c;所以了解媒体查询也是很有必要的&#xff0…

AB实验人群定向HTE模型1 - Causal Tree

背景 论文给出基于决策树估计实验对不同用户的不同影响。并提出Honest&#xff0c;variance Penalty算法旨在改进CART在tree growth过程中的过拟合问题。 我们举个例子&#xff1a;科研人员想衡量一种新的降血压药对病人的效果&#xff0c;发现服药的患者有些血压降低但有些血…

机架式服务器是什么?

随着科学技术的快速发展&#xff0c;服务器的种类样式也变得多种多样了&#xff0c;其中根据服务器的外观来进行分类的有塔式服务器、刀片式服务器和机架式服务器多种类型&#xff0c;其中对于塔式服务器和刀片式服务器我们还是有一定的了解的&#xff0c;所以今天小编主要来带…

I2C协议详解

文章目录 概念工作模式 原理工作原理工作流程IIC协议的关键特点IIC通信过程 优点与缺点优点缺点 概念 IIC&#xff08;Inter-Integrated Circuit&#xff09;协议&#xff0c;也常被称为TWI&#xff08;Two-Wire Interface&#xff09;协议&#xff0c;是一种用于短距离通信的…

list常用接口模拟实现

文章目录 一、模拟list类的框架二、函数接口实现1、迭代器接口2、常用删除、插入接口3、常用其他的一些函数接口4、默认成员函数 一、模拟list类的框架 1、使用带哨兵的双向链表实现。 2、链表结点&#xff1a; // List的结点类 template<class T> struct ListNode {Li…

Python实现mysql基于配置文件的全自动增量数据备份

前言 在mysql备份或者高可用当中,常见的方式主要有NDBcluster集群,MGR组复制技术,Mycat+mysql分片存储技术(以上三种都可以在mysql专栏查看)以及主从备份。 在上述的几种方式中,所需要的机器及部署配置都是相当繁琐的,集群至少三台起步,对于一些小的备份场景下可能不…

卧式混料机:混合设备的智慧之选

卧式混料机&#xff0c;顾名思义&#xff0c;是一种采用卧式结构的混合设备。它的设计精巧&#xff0c;结构紧凑&#xff0c;不仅占用空间小&#xff0c;而且操作简便&#xff0c;维护方便。与传统的立式混料机相比&#xff0c;卧式混料机在混合效率、混合均匀度以及物料适应性…

DNS设置(linux)

1.配置dns需要现在/etc/sysconfig/network-scripts/目录下的ifcfg-ens33(后面数字也可能是其他的)中配置DNS 2.编辑/etc/resolv.conf文件&#xff0c;将上面网卡中加的dns服务器ip添加到此文件 vi /etc/resolv.conf重启网络配置 service network restart常用的dns的ip 国内…

香港优才计划申请时间要多久?各流程申请周期规划,再晚就来不及了!

香港优才计划申请时间要多久&#xff1f;各流程申请周期规划&#xff0c;再晚就来不及了&#xff01; 2024年是香港优才计划不限配额的最后一年&#xff0c;明年政策如何变化还未可知&#xff0c;但如果明年又设置限额了&#xff0c;那么今年最后的机会一定要抓住了。 在这里…

分享 - 树形dp

树形 d p dp dp 例1 - 基础 链接&#xff1a;树上子链 练手 分析 其实一看题就很显然的树形 d p dp dp子链在这里分为两种情况&#xff0c;如图黑链和红链 思路 d p [ i ] dp[i] dp[i] 表示以 i i i 开头的红链的最大权值易得&#xff1a; d p [ i ] m a x ( d p [ i…

Dice损失函数

Dice损失函数&#xff08;Dice Loss&#xff09;&#xff0c;也称为Dice系数损失或Srensen-Dice系数损失&#xff0c;是一种用于衡量两个集合相似度的指标&#xff0c;广泛应用于图像分割任务中。它的目标是最大化分割结果与真实标签之间的相似度。Dice损失函数基于Dice系数&am…

CE FDA注册相关标准:在线查看 下载

DICOM: View http://med nema.org/ 中文版&#xff1a;https://github.com/dicom-learning-group/dicom-standard-chinese/tree/master/Part01 发布历史&#xff1a;Approved Supplements

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

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

智能客服:论小红书商家杀出重围的正确姿势!

小红书「起飞」密码 洞悉需求&#xff0c;主动应变 面对众多的互联网平台&#xff0c;选择一个合适的平台宣传自家的品牌&#xff0c;也是一门学问&#xff0c;从“遇事不决&#xff0c;小红书”&#xff0c;这一 slogan 就能精准地捕捉了用户搜索行为的新趋势。 在过去的十…

【C++奇妙冒险】拷贝构造函数、运算符重载(赋值重载|const成员|取地址重载|const取地址重载)

文章目录 前言&#x1f6a9;拷贝构造函数&#x1fae7;概念&#x1fae7;特征&#x1fae7;默认生成的拷贝构造&#x1fae7;default关键字&#xff08;浅谈&#xff09; &#x1f6a9;运算符重载&#x1fae7;概念&#x1fae7;运算符重载注意事项&#x1fae7;封装如何保证&a…

如何使用GPT-4o?如何使用 GPT-4o API?

如何使用GPT-4o&#xff1f; GPT-4o 也可以通过 ChatGPT 界面使用 如何使用 GPT-4o API 新的 GPT-4o 模型遵循 OpenAI 现有的聊天完成 API&#xff0c;使其向后兼容且易于使用。 ​ 如何升级GPT4Plus&#xff1f; 升级ChatGPTPLSU4需要一张虚拟卡&#xff0c;点击获取​​​…

JavaScript 中遍历数组的多种方法

在 JavaScript 中,遍历数组有很多种方法。根据不同的场景选择最合适的遍历方式,不仅能提高代码的可读性,还能提升性能。在这篇文章中,我们将详细介绍几种常见的遍历数组的方法及其优缺点。 推荐方法: for-of 循环(ES2015+):简单且支持 async。 for (const element of t…

ClickHouse 分布式部署、分布式表创建及数据迁移指南

文章目录 部署 ClickHouse 集群1.1 环境准备1.2 安装 ClickHouse1.3 配置集群 创建分布式表2.1 创建本地表2.2 创建分布式表2.3 删除分布式表 测试分布式表3.1 插入测试数据。 配置和管理4.1 配置监控4.2 数据备份 数据迁移5.1 导出5.2 导入 部署 ClickHouse 集群 Quantum Ins…