死磕Nacos系列:Nacos在我的SpringCloud项目中做了什么?

Nacos服务注册

我们一个SpringCloud项目中集成了Nacos,当项目启动成功后,就可以在Nacos管理界面上看到我们项目的注册信息,还可以看到项目的健康状态等等信息:

image-20231124100734165

那Nacos是什么时候进行了哪些操作的呢?今天我们来一探究竟,Let’s go!

Nacos客户端

如何集成SpringCloud Alibaba Nacos?

在Maven项目中,先引入依赖:

<!-- nacos注册依赖 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>2.2.3.RELEASE</version>
</dependency>

SpringBoot应用中的启动类:

@EnableDiscoveryClient
@SpringBootApplication
public class ServiceApplication {

    public static void main(String[] args) {
        SpringApplication.run(ServiceApplication.class, args);
    }
}

配置文件bootstrap.yaml:

server:
  port: 9001
spring:
  application:
    name: ServiceA
  cloud:
    nacos:
      discovery:
        # NacosServer地址
        server-addr: 127.0.0.1:8848
      # NacosServer鉴权模式下的用户名
      username: nacos
      # NacosServer鉴权模式下的密码
      password: nacos

第一步从哪里下手?

了解过SpringBoot的starter的同学都知道,引入一个starter,我们可以去看这个starter中的spring.factories文件,因为这里会告诉你SpringBoot在启动中,会自动加载这个starter的哪些配置类。

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\
  com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\
  com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\
  com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
  com.alibaba.cloud.nacos.discovery.NacosDiscoveryClientConfiguration,\
  com.alibaba.cloud.nacos.discovery.reactive.NacosReactiveDiscoveryClientConfiguration,\
  com.alibaba.cloud.nacos.discovery.configclient.NacosConfigServerAutoConfiguration,\
  com.alibaba.cloud.nacos.NacosServiceAutoConfiguration
org.springframework.cloud.bootstrap.BootstrapConfiguration=\
  com.alibaba.cloud.nacos.discovery.configclient.NacosDiscoveryClientConfigServiceBootstrapConfiguration

在自动加载的类中,NacosServiceRegistryAutoConfiguration是处理服务实例自动注册的类。

走进服务自动注册

NacosServiceRegistryAutoConfiguration中,创建了NacosServiceRegistryNacosRegistrationNacosAutoServiceRegistration的bean。

其中NacosServiceRegistry继承自SpringCloudCommon下的ServiceRegistry,实现了服务注册、取消注册的能力;

image-20231125120507990

NacosRegistration继承自SpringCloudCommon的Registration,表明它描述的是一个注册信息,其中包含了服务的ip、端口、元信息、访问协议等信息;

image-20231125120601814

NacosAutoServiceRegistration就是利用上面的能力和数据,在合适的时候被调用进行自动注册。

那什么时候才是合适的时候呢?我们可以看到,NacosAutoServiceRegistration继承自AbstractAutoServiceRegistration、实现了ApplicationListener接口,那就说明需要被WebServerInitializedEvent事件驱动。WebServerInitializedEventApplicationContext刷新完成,且Web服务初始化完成后发布的一个事件。

接收到事件通知后,就会进行注册:

protected void register() {
   this.serviceRegistry.register(getRegistration());
}

NacosServiceRegistry是怎样实现注册的?

在Nacos中,ServiceRegistry其实是使用的NamingService的能力完成注册的。

public void register(Registration registration) {

   ....
       
   NamingService namingService = namingService();
   String serviceId = registration.getServiceId();
   String group = nacosDiscoveryProperties.getGroup();

   Instance instance = getNacosInstanceFromRegistration(registration);

   try {
      namingService.registerInstance(serviceId, group, instance);
      .....
   }
   catch (Exception e) {
      ....
   }
}

NamingService提供了服务注册,下线、订阅消息,查询等功能,是很核心的一个能力提供者。

NamingServiceNacosServiceManager负责管理,具体由NamingFactory通过进行创建,具体实现类是NacosNamingService

NacosNamingService在实例化的时候创建了很多组件,如

  • EventDispatcher

  • 一个单线程的线程池+死循环

  • 承接Service信息改变的业务,并通知到各个订阅了此Service的监听器

  • NamingProxy

    • 两个核心线程的定时线程池

    • 每隔30秒拉取远端Server列表

    • 如果token过期,重新登录获取token

  • BeatReactor

    • 定时线程池
    • 核心线程数量可自定义,默认是核心数的一半,最少1个核心线程数
    • 默认每隔5秒向服务端发送心跳
  • HostReactor

    • 提供主动查询更新和被动接受服务信息推送的能力
    • PushReceiver
      • 一个单线程的定时线程池+死循环
      • 接受来自服务端通过udp推送的数据,并向服务端发送ack确认信息

NacosNamingService的注册实例方法中:

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    if (instance.isEphemeral()) {
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

可以看到,如果是临时实例(可通过配置文件配置,默认是临时实例),那就会向线程池中丢一个心跳任务。

再通过NamingProxy进行注册:

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    
    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", namespaceId, serviceName,
            instance);
    
    final Map<String, String> params = new HashMap<String, String>(16);
    params.put(CommonParams.NAMESPACE_ID, namespaceId);
    params.put(CommonParams.SERVICE_NAME, serviceName);
    params.put(CommonParams.GROUP_NAME, groupName);
    params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
    params.put("ip", instance.getIp());
    params.put("port", String.valueOf(instance.getPort()));
    params.put("weight", String.valueOf(instance.getWeight()));
    params.put("enable", String.valueOf(instance.isEnabled()));
    params.put("healthy", String.valueOf(instance.isHealthy()));
    params.put("ephemeral", String.valueOf(instance.isEphemeral()));
    params.put("metadata", JacksonUtils.toJson(instance.getMetadata()));
    
    reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
    
}

这里就是将服务实例信息通过标准的Restful接口和Server进行通信。

Nacos服务端

UtilAndComs.nacosUrlInstance对应的请求路径是:/nacos/v1/ns/instance

在Nacos源码中, 这个请求的处理是在naming模块下的InstanceControllerV2#register中进行的。

@CanDistro
@PostMapping
@Secured(action = ActionTypes.WRITE)
public Result<String> register(InstanceForm instanceForm) throws NacosException {
    // 检查实例的参数
    instanceForm.validate();
    // 检查实例的权重
    checkWeight(instanceForm.getWeight());
    // 构造一个实例对象
    Instance instance = buildInstance(instanceForm);
    // 注册
    instanceServiceV2.registerInstance(instanceForm.getNamespaceId(), buildCompositeServiceName(instanceForm), instance);
    // 发送RegisterInstanceTraceEvent事件
    NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), "",
            false, instanceForm.getNamespaceId(), instanceForm.getGroupName(), instanceForm.getServiceName(),
            instance.getIp(), instance.getPort()));
    return Result.success("ok");
}

上面代码就是检查参数,构造Instance服务实例对象,使用instanceServiceV2类进行注册,再发布RegisterInstanceTraceEvent事件。

接下来进入instanceServiceV2.registerInstance看看:

@Override
public void registerInstance(Service service, Instance instance, String clientId) throws NacosException {
    NamingUtils.checkInstanceIsLegal(instance);

    Service singleton = ServiceManager.getInstance().getSingleton(service);
    if (!singleton.isEphemeral()) {
        throw new NacosRuntimeException(NacosException.INVALID_PARAM,
                String.format("Current service %s is persistent service, can't register ephemeral instance.",
                        singleton.getGroupedServiceName()));
    }
    Client client = clientManager.getClient(clientId);
    if (!clientIsLegal(client, clientId)) {
        return;
    }
    InstancePublishInfo instanceInfo = getPublishInfo(instance);
    client.addServiceInstance(singleton, instanceInfo);
    client.setLastUpdatedTime();
    client.recalculateRevision();
    NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
    NotifyCenter
            .publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}

上述源码其实就三个步骤:

1、将Service的信息维护到ServiceManager中。

2、从ClientManager中获取Client信息。

3、向NotifyCenter发送事件通知。

Nacos Web

在Nacos的后台web系统中,服务列表是通过GET /nacos/v1/ns/catalog/services获取的,我们可以在Nacos的naming模块下找到相应的控制类的处理逻辑:

@Secured(action = ActionTypes.READ)
@GetMapping("/services")
public Object listDetail(@RequestParam(required = false) boolean withInstances,
        @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
        @RequestParam(required = false) int pageNo, @RequestParam(required = false) int pageSize,
        @RequestParam(name = "serviceNameParam", defaultValue = StringUtils.EMPTY) String serviceName,
        @RequestParam(name = "groupNameParam", defaultValue = StringUtils.EMPTY) String groupName,
        @RequestParam(name = "instance", defaultValue = StringUtils.EMPTY) String containedInstance,
        @RequestParam(required = false) boolean hasIpCount) throws NacosException {
    
    if (withInstances) {
        return judgeCatalogService().pageListServiceDetail(namespaceId, groupName, serviceName, pageNo, pageSize);
    }
    return judgeCatalogService()
            .pageListService(namespaceId, groupName, serviceName, pageNo, pageSize, containedInstance, hasIpCount);
}

跟踪源码,最终我们可以确认到最基础的数据是从ServiceManagernamespaceSingletonMaps中获取到的,namespaceSingletonMaps是一个Map,存储了namespaceService的对应关系。

最后,放上一张整个过程的示意图:

image-20231125120205410

总结

今天的文章里面讲解了一个SpringBoot应用是怎样注册到NacosServer中的,以及Nacos管理界面的数据来源。

文中涉及到了SpringCloudCommon的知识,这里可以简单提一下,SpringCloudCommon是SpringCloud的一系列标准,其抽象了服务注册与发现、负载均衡器、熔断等模型,SpringCloudAlibaba只是按照这个标准具体的一个实现,如SpringCloudNetflix就是另一套实现。

在整个Nacos的体系中,还有很多技术是待深入的,比如NamingService中各个组件具体的实现方式,NotifyCenter的实现方式,服务信息的持久化、保证数据一致性的策略等等,有兴趣的小伙伴可以持续关注我的后续文章。

最后,放上一张Nacos架构图,带你敲响Nacos的大门。

image-20231125122534567

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

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

相关文章

【从浅识到熟知Linux】基本指定之find、grep、head和tail

&#x1f388;归属专栏&#xff1a;从浅学到熟知Linux &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;每日一句&#xff1a;一篇又一篇&#xff0c;学写越上头。 文章前言&#xff1a;本文介绍find、grep、head和tail指令用法并给出示例和截图。 文章目录 find基本…

Jquery ajax 进行网络请求,同步阻塞引起的UI线程阻塞 (loading图片不显示 )

jax重新获取数据刷新页面功能&#xff0c;因为ajax属于耗时操作&#xff0c;想在获取数据且加载页面时显示加载遮罩层&#xff0c;结果发现了ajax的好多坑。 ajax 执行http网络请示时时&#xff0c;让遮罩层显示&#xff0c;ajax加载完毕后遮罩层消失。 因为我想让loadChart()…

mysql 更改密码

由于两台设备的mysql数据库的密码不一样&#xff0c;开发时每次连接数据库都需要更改配置文件&#xff0c;所以想修改一下mysql数据库的密码。 mysql 修改密码千万不要直接修改&#xff0c;直接修改的话会出现两种情况&#xff1a; 1&#xff0c;修改成功&#xff0c;无法登录。…

elasticsearch 索引库操作和文档操作

文章目录 索引库操作mapping映射属性索引库的CRUD&#xff08;创建&#xff0c;读取&#xff0c;更新&#xff0c;删除&#xff09;创建索引库和映射基本语法&#xff1a;示例&#xff1a; 查询索引库修改索引库删除索引库 文档操作新增文档查询文档删除文档修改文档全量修改增…

通过互联网代理部署Docker+Kubernetes 1.28.1

一、背景 在公司环境中&#xff0c;我们往往都是无法直接连接外网的&#xff0c;之前写过一篇文章&#xff0c;是通过外网自建的中转机器下载需要的离线包&#xff0c;并在内网搭建一个harbor&#xff0c;通过harbor的方式搭建了一个kubernetes&#xff0c;但是这种方式还是有…

蓝牙运动耳机哪个好?蓝牙运动耳机排行榜前十名

​在运动中&#xff0c;音乐可以激发你的热情和动力&#xff0c;而一款好的运动耳机则可以让你更好地享受音乐。然而&#xff0c;市面上的运动耳机品牌和型号众多&#xff0c;质量参差不齐。所以&#xff0c;今天精选了5款市面上比较优秀的运动耳机给大家参考&#xff0c;是你运…

【鬼鬼鬼iiARPG开发记录】

鬼鬼鬼ARPG开发记录 一、创建项目1、创建3D(URP)项目2、导入新的输入系统&#xff08;input system&#xff09;3、勾选Enter Play Mode Options 二、导入资源1、创建若干文件夹 一、创建项目 1、创建3D(URP)项目 2、导入新的输入系统&#xff08;input system&#xff09; …

PyTorch-ReID重识别算法库与数据集资料汇总

Torchreid 是一个用于深度学习人员重新识别的库&#xff0c;用 PyTorch 编写&#xff0c;为我们的 ICCV’19 项目 Omni-Scale Feature Learning for Person Re-Identification 开发。 PyTorch-ReID的特点是 多GPU训练支持图像和视频 REID端到端培训和评估极其轻松地准备 Rei…

【SpringBoot系列】SpringBoot日志配置

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

电子学会C/C++编程等级考试2021年06月(二级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:数字放大 给定一个整数序列以及放大倍数x,将序列中每个整数放大x倍后输出。 时间限制:1000 内存限制:65536输入 包含三行: 第一行为N,表示整数序列的长度(N ≤ 100); 第二行为N个整数(不超过整型范围),整数之间以一个空格…

Redis集群(新)

1.什么是集群 Redis集群实现了对Redis的水平扩容&#xff0c;可实现并发写操作&#xff0c;启动n个redis节点&#xff0c;将数据分别存储在不同的节点中&#xff0c;每块节点负责不同区域的插槽&#xff0c;所以Redis集群通过分区来提供一定程度的可用性。 Redis集群现采用的是…

Google hacking语法

Google hacking语法 文章目录 Google hacking语法site:inurl:intitle:filetypecacheintext注意 site: 搜索子域 跟域名site:www.baidu.com 定位 跟语言 site: jp inurl: 用于在特定url链接中搜索网站信息 inurl:login intitle: 使用intitle:指令返回页面标题中包含关键…

【pandas】数据透视表【pivot_table】

pivot_table pandas的pivot_table函数是一个非常有用的工具&#xff0c;用于创建一个数据透视表&#xff0c;这是一种用于数据总结和分析的表格形式。 以下是pivot_table的基本语法&#xff1a; pandas.pivot_table(data, valuesNone, indexNone, columnsNone, aggfuncmean,…

vcsa6.7打补丁操作

首先到官网中查找到最新的patch&#xff0c;需要有注册账号才可操作 网址链接&#xff1a;https://customerconnect.vmware.com/patch#search 下载后把iso文件上传到磁盘中&#xff0c;vcsa虚拟机中做光盘iso挂接后&#xff0c; 使用浏览器输入https://ip:5480登录&#xff…

数组基础知识

数组基础&#xff08;不定时更新&#xff09; 数组基础 数组基础 &#xff08;1&#xff09;数组是存放在连续内存空间上的相同类型数据的集合。数组可以方便的通过下标索引的方式获取到下标下对应的数据。数组下标都是从0开始的。数组内存空间的地址是连续的。 &#xff08;…

【unity实战】实现一个放置3d物品建造装修系统(附项目源码)

文章目录 最终效果前言绘制开始场景素材开始放置旋转物体扩展优化1. 绘制地图边界&#xff0c;确保放置物品在指定区域内工作2. 让模型所占面积大小更加准确3. 隐藏白色瓦片指示区域 最终效果其他源码参考完结 最终效果 前言 其实3d物品建造装修系统之前就已经做过了&#xff…

Vue框架学习笔记——事件处理

文章目录 前文提要事件处理的解析过程样例代码如下&#xff1a;效果展示图片&#xff1a;v-on:click"响应函数"v-on:click简写形式响应函数添加响应函数传参占位符"$event"注意事项 前文提要 本人仅做个人学习记录&#xff0c;如有错误&#xff0c;请多包…

1、windows10系统下Qt5.12.0与卸载

一、安装包下载 1、Qt社区下载 https://download.qt.io/archive/qt/5.12/5.12.10/qt-opensource-windows-x86-5.12.10.exe 2、百度网盘下载 链接&#xff1a;百度网盘 请输入提取码 3、Qt官网下载&#xff1a; Try Qt | 开发应用程序和嵌入式系统 | Qt 二、安装提示 下…

android实战项目之二十二---如何快速APP中集成支付宝和微信支付功能

效果图 实现方案 jcenter 集成方式 implementation com.xgr.easypay:EasyPay:2.0.5 // 基类库&#xff0c;必选 implementation com.xgr.easypay:wechatpay:2.0.5 // 微信支付&#xff0c;可选 implementation com.xgr.easypay:alipay:2.0.5 // 支付宝支付&#xff0c;可…

vivado产生报告阅读分析21

其他命令选项 • -of_objects <suggestion objects> &#xff1a; 启用特定建议的报告。在此模式下运行时 &#xff0c; report_qor_suggestions 不会生成新建议。此命令可快速执行 &#xff0c; 读取 RQS 文件后 &#xff0c; 此命令可用于查看其中包 含的建议。其…