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

news2024/11/16 8:48:33

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

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

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

相关文章

数据挖掘 K近邻

什么时候用K近邻&#xff1f; 交叉验证的时候。最常见的交叉验证方法是K折交叉验证&#xff0c;其中数据集被均匀分成K个子集&#xff0c;称为折&#xff0c;然后执行K次训练和测试&#xff0c;每次选择不同的折作为测试集&#xff0c;其余的作为训练集。最后&#xff0c;将K次…

qt5.15.2及6.0以上版本安装

文章目录 下载在线安装器安装打开软件 下载在线安装器 因为从qt5.15开始不支持离线下载安装了&#xff0c;只能通过在线安装的方式进行安装。 下载在线安装下载器&#xff1a; 这个在线安装下载器网上也都是可以找到。 这里是其放到网盘上的下载地址&#xff1a; 链接&#x…

DBT踩坑第二弹

总结下dbt-spark踩到的坑&#xff0c;连接方式采用的是thrift连接 Kerberos认证。考虑到开源组件Kyuubi也是基于Hiveserver2&#xff0c;使用的thrift协议&#xff0c;所以采用Kyuubi执行SparkSQL。 官方文档给出的Thrift方式连接示例真的是简单&#xff0c;但是真是用起来真是…

01_MySQL介绍及安装

#任务背景 一、真实案例 某公司现在有几套不同版本的MySQL数据库&#xff0c;现在大部分的生产和测试环境都已经切换到5.7版本&#xff0c;由于历史原因&#xff0c;有一套测试环境版本为MySQL-5.5。现为了将测试环境版本统一&#xff0c;需要将原来测试环境数据库MySQL-5.5版…

Linux:创建进程 -- fork,到底是什么?

相信大家在初学进程时&#xff0c;对fork函数创建进程一定会有很多的困惑&#xff0c;比如&#xff1a; 1.fork做了什么事情?? 2.为什么fork函数会有两个返回值?3.为什么fork的两个返回值&#xff0c;会给父进程谅回子进程pid&#xff0c;给子进程返回0?4.fork之后:父子进…

哨兵1号回波数据(L0级)包格式解析与成像参数提取

坑爹的格式,具体有多坑往下看就知道了。matlab代码在文末。 先上首字母缩写: 再来回波数据包的格式图 1. 数据包格式 众所周知,解包的第一步是找帧头和帧长,找到第4~5字节,帧长码为“0x3761”,转十进制为14777,然而实际第一帧整帧的长度是14184。。。你要是加6我还能…

机器学习与因果推断的高级实践 | 数学建模

文章目录 因果推断因果推断的前世今生&#xff08;1&#xff09;潜在结果框架&#xff08;Potential Outcome Framework&#xff09;&#xff08;2&#xff09;结构因果模型&#xff08;Structual Causal Model&#xff0c;SCM&#xff09; 身处人工智能爆发式增长时代的机器学…

LeetCode OJ循环队列(C语言)

1.题目的初步分析 我们分析上述题目的时候会发现题目非常的长&#xff0c;不好整理思路&#xff0c;我这里可以大致的将本题的几个核心点说出来&#xff1a; 1.队列的思路 循环队列说来说去不还是队列嘛&#xff0c;那么队列的基本操作增删查改、以及队列的基本结构肯定都是不能…

京东家用电器商品电子说明书在哪里能找到怎么查看产品电子说明书?草柴返利APP如何查询领取京东优惠券拿京东购物返利?

京东商品电子说明书是一种便捷、高效的说明工具&#xff0c;为消费者了解和使用商品提供了重要帮助。京东商品电子说明书是一种以电子文档、图文、视频的形式提供的商品使用说明书。它通常由商家上传至京东平台&#xff0c;以供消费者在购买商品后下载查看。与传统的纸质说明书…

计算机编程零基础编程学什么语言,中文编程工具构件简介软件下载

计算机编程零基础编程学什么语言&#xff0c;中文编程工具构件简介软件下载 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c;象如…

Leetcode—94.二叉树的中序遍历【简单】

2023每日刷题&#xff08;四十&#xff09; Leetcode—94.二叉树的中序遍历 C语言实现代码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** Note: The returned array mus…

Java实现—数据结构 1.初识集合框架

一、什么是集合框架 Java集合框架&#xff0c;又被称为容器&#xff0c;是定义在java.util包下的一组接口interfaces和其实现类classes 其主要表现为将多个元素element置于一个单元中&#xff0c; 集合框架是由若干个类组成的&#xff0c;每个类的背后就是一种数据结构&…

2023.11.25更新关于mac开发APP(flutter)的笔记与整理(实机开发一)

我自己写的笔记很杂&#xff0c;下面的笔记是我在chatgpt4的帮助下完成的&#xff0c;希望可以帮到正在踩坑mac开发APP&#xff08;flutter&#xff09;的小伙伴 目标&#xff1a;通过MAC电脑使用flutter框架开发一款适用于苹果手机的一个APP应用 本博客的阅读顺序是&#xf…

2022年06月 Scratch(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch等级考试(1~4级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 点击绿旗,舞台上的角色会说出? A:2022年5月1日 B:1日5月2022年 C:2022年05月01日 D:05月01日2022年 答案:C 输出为:2022年05月01日。 第2题 观察规律,请问橙色方块应填…

小程序项目:node+vue基于微信小程序的校园盲盒小程序的设计与实现

项目介绍 随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时…

frp V0.52.3 搭建

下载 https://github.com/fatedier/frp/releases/ 此版本暂时没有windows的&#xff0c;想在windows使用请下载v0.52.2 简易搭建 frps.toml的配置文件&#xff0c;以下12000、8500需要在云服务器中的防火墙中开放tcp # bindPort为frps和frpc通信的端口&#xff0c;需要在防…

QT已有项目导入工程时注意事项

文章目录 从qt其他版本上开发的工程导入另一qt版本时 从qt其他版本上开发的工程导入另一qt版本时 这里以之前在qt5.12.2上开发的项目为例&#xff0c;现在到在qt6.5.3上运行。 不能直接导入IDE上&#xff0c;否则会报各种莫名奇妙的错误。 首先要把扩展名位.pro.user文件 删掉…

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

C/C++等级考试(1~8级)全部真题・点这里 第1题:石头剪刀布 石头剪刀布是常见的猜拳游戏。石头胜剪刀,剪刀胜布,布胜石头。如果两个人出拳一样,则不分胜负。 一天,小A和小B正好在玩石头剪刀布。已知他们的出拳都是有周期性规律的,比如:“石头-布-石头-剪刀-石头-布-石头…

[JVM] 京东一面~说一下Java 类加载过程

系统加载 Class 类型的文件主要三步&#xff1a;加载->连接->初始化。连接过程又可分为三步&#xff1a;验证->准备->解析。 通过全限定名来加载生成 class 对象到内存中&#xff0c;然后进行验证这个 class 文件&#xff0c;包括文件格式校验、元数据验证&#xf…

【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步?

【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步&#xff1f; 文章目录 【超强笔记软件】Obsidian如何实现免费无限流量无套路云同步&#xff1f;一、简介软件特色演示&#xff1a; 二、使用免费群晖虚拟机搭建群晖Synology Drive服务&#xff0c;实现局域网同步1 安…