微服务学习-SpringCloud -Nacos (服务注册源码学习)

news2024/11/27 13:41:57

文章目录

  • 源码版本及下载
  • 服务注册核心流程图(看不清请双击打开大图)
  • 源码详解
    • 客户端注册源码
    • 服务端注册源码

源码版本及下载

此次源码版本为1.4.1,2.x版本在服务请求时使用了grpc的方式,所以先以1.4.1版本学习,后续再看2.x版本。
源码下载地址:: link
打开页面后,下拉到最下面,下载nacos-1.4.1.zip,解压导入idea即可。

服务注册核心流程图(看不清请双击打开大图)

请添加图片描述
此图主要是对核心注册流程进行了大概梳理,可以在后续详细看源码时配合互补。

源码详解

客户端注册源码

在微服务使用nacos需要在pom文件中引入依赖:

<dependency>
	<groupId>com.alibaba.cloud</groupId>
	<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
	<version>2.2.5.RELEASE</version>
</dependency>
  • 开始分析客户端源码
    SpringCloud这一套组件都是基于SpringBoot开发的,那么SpringBoot引入其它组件的方式是什么呢?
    自动装配
    所以第一步,我们先找它的在自动装配类。
    在这里插入图片描述
    打开文件:
    在这里插入图片描述
    由类名推断服务注册的自动装配类为:
    com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration
    我们打开此类:
    在这里插入图片描述
    对应我画的流程图,我们重点关注第一个和第三个bean。
    先看NacosAutoServiceRegistration。
    当Springboot启动时,会发布事件。我们来看NacosAutoServiceRegistration,继承了AbstractAutoServiceRegistration,
    在这里插入图片描述
    在看AbstractAutoServiceRegistration类,我们发现它实现了ApplicationListener。
    在这里插入图片描述
    当我们实现了ApplicationListener时,我们就需要实现它的onApplicationEvent方法。
  public void onApplicationEvent(WebServerInitializedEvent event) {
        this.bind(event);
    }

    /** @deprecated */
    @Deprecated
    public void bind(WebServerInitializedEvent event) {
        ApplicationContext context = event.getApplicationContext();
        if (!(context instanceof ConfigurableWebServerApplicationContext) || !"management".equals(((ConfigurableWebServerApplicationContext)context).getServerNamespace())) {
            this.port.compareAndSet(0, event.getWebServer().getPort());
            this.start();
        }
    }

onApplicationEvent监听了WebServerInitializedEvent事件。调用this.start()方法。
在这里插入图片描述
进入start方法,忽略分支内容,直接看主线,也就是this.register().
在这里插入图片描述
走到这个方法,可以在这边打断点,debug看代码。
往下走,进入namingService.registerInstance(serviceId, group, instance)方法。

    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
       // 心跳检测
        NamingUtils.checkInstanceIsLegal(instance);
        String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
        if (instance.isEphemeral()) {
            BeatInfo beatInfo = this.beatReactor.buildBeatInfo(groupedServiceName, instance);
            this.beatReactor.addBeatInfo(groupedServiceName, beatInfo);
        }
		// 进入注册核心方法
        this.serverProxy.registerService(groupedServiceName, groupName, instance);
    }
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        LogUtils.NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}", new Object[]{this.namespaceId, serviceName, instance});
        //  配置参数写入map中
        Map<String, String> params = new HashMap(16);
        params.put("namespaceId", this.namespaceId);
        params.put("serviceName", serviceName);
        params.put("groupName", groupName);
        params.put("clusterName", 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()));
        // 通过http请求发生注册信息
        this.reqApi(UtilAndComs.nacosUrlInstance, params, "POST");
    }

UtilAndComs.nacosUrlInstance=/nacos/v1/ns/instance
这个地址就是服务注册的地址,我们来看官网API:
官网API
在这里插入图片描述
具体参数可以看官网。
再往下,简单看看主要代码:

HttpRestResult<String> restResult = this.nacosRestTemplate.exchangeForm(url, header, Query.newInstance().initParams(params), body, method, String.class);
this.execute(url, httpMethod, requestHttpEntity, responseType);
response = this.requestClient().execute(uri, httpMethod, requestEntity);

底层是调用了JdkHttpClientRequest方法发送请求。请添加图片描述
客户端主要注册源码就结束了。

服务端注册源码

服务端就是第一步下载并导入的源码。
找到服务注册方法类:
在这里插入图片描述
找到对应的注册的方法

    /**
    * Register new instance.
    *
    * @param request http request
    * @return 'ok' if success
    * @throws Exception any error during register
    */
   @CanDistro
   @PostMapping
   @Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
   public String register(HttpServletRequest request) throws Exception {
       
       final String namespaceId = WebUtils
               .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
       final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
       NamingUtils.checkServiceNameFormat(serviceName);
       
       final Instance instance = parseInstance(request);
       // 服务注册核心方法
       serviceManager.registerInstance(namespaceId, serviceName, instance);
       return "ok";
   }

看注释注册一个新的实例。
往下走。

  // 以AP模式创建注册实例
   public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        // 创建空的服务实例
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());
        
        Service service = getService(namespaceId, serviceName);
        
        if (service == null) {
            throw new NacosException(NacosException.INVALID_PARAM,
                    "service not found, namespace: " + namespaceId + ", service: " + serviceName);
        }
        // 添加实例
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

注意,此处是以AP模式创建的实例,是临时实例。
nacos注册实例分为AP和CP,我们看到服务注册时传入的参数有一个参数控制我们注册的实例是AP还是CP,就是这个参数:
在这里插入图片描述
此参数默认为true,也就是默认创建临时实例。后面会重点讲一下AP和CP架构。
我们先加入createEmptyService方法。

public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
        createServiceIfAbsent(namespaceId, serviceName, local, null);
    }
  public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {
        Service service = getService(namespaceId, serviceName);
        if (service == null) {
            
            Loggers.SRV_LOG.info("creating empty service {}:{}", namespaceId, serviceName);
            service = new Service();
            service.setName(serviceName);
            service.setNamespaceId(namespaceId);
            service.setGroupName(NamingUtils.getGroupName(serviceName));
            // now validate the service. if failed, exception will be thrown
            service.setLastModifiedMillis(System.currentTimeMillis());
            service.recalculateChecksum();
            if (cluster != null) {
                cluster.setService(service);
                service.getClusterMap().put(cluster.getName(), cluster);
            }
            service.validate();
            
            putServiceAndInit(service);
            if (!local) {
                addOrReplaceService(service);
            }
        }
    }
  • 先看下这个方法
Service service = getService(namespaceId, serviceName);

主要是用来从注册表中通过namespace获取对应的服务,如果没有获取到,返回null;如果有,返回对应的service。
看一下对应的getService方法:

  public Service getService(String namespaceId, String serviceName) {
        if (serviceMap.get(namespaceId) == null) {
            return null;
        }
        return chooseServiceMap(namespaceId).get(serviceName);
    }
    
    public boolean containService(String namespaceId, String serviceName) {
        return getService(namespaceId, serviceName) != null;
    }

然后我们来看一下注册表的结构,也就是serviceMap。

private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();

其实主要是这样一个结构:
Map<namespace, Map<group::serviceName, Service>>
在我们平时使用中,大概是这样一个结构:
请添加图片描述

  • 再看下下面的这个主要方法,点击进入:
putServiceAndInit(service);
 private void putServiceAndInit(Service service) throws NacosException {
 		// 将服务信息写入serviceMap
        putService(service);
        // 初始化服务
        service.init();
        // 监听数据变化
        consistencyService
                .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), true), service);
        consistencyService
                .listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), false), service);
        Loggers.SRV_LOG.info("[NEW-SERVICE] {}", service.toJson());
    }

将service数据写入serviceMap后,创建临时实例的方法就结束了,现在我们代码回到创建临时实例createEmptyService处继续向下走,然后再判断一次注册表中是否有service,此次没有的话就会抛出异常。
再往下走,进入核心方法:

 addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
  public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)
            throws NacosException {
        
        String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
        
        Service service = getService(namespaceId, serviceName);
        
        synchronized (service) {
           // 比较并获取新的实例列表,这个列表中包含了此次新增的实例
            List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);
            
            Instances instances = new Instances();
            instances.setInstanceList(instanceList);
            
            consistencyService.put(key, instances);
        }
    }

先看一下此方法:

String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
 public static String buildInstanceListKey(String namespaceId, String serviceName, boolean ephemeral) {
        return ephemeral ? buildEphemeralInstanceListKey(namespaceId, serviceName)
                : buildPersistentInstanceListKey(namespaceId, serviceName);
    }

ephemeral前面我们了解过,是否是临时实例,默认传true,所以一般返回 buildEphemeralInstanceListKey(namespaceId, serviceName)这个结果,所以这里主要是区分nacos是AP还是CP架构的地方。
再往下走:

consistencyService.put(key, instances);
 public void put(String key, Record value) throws NacosException {
        onPut(key, value);
        distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,
                globalConfig.getTaskDispatchPeriod() / 2);
    }
 public void onPut(String key, Record value) {
        // 健壮性校验
        ...
        // 核心方法,将任务添加到阻塞队列
        notifier.addTask(key, DataOperation.CHANGE);
    }
  public void addTask(String datumKey, DataOperation action) {
            
            if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {
                return;
            }
            if (action == DataOperation.CHANGE) {
                services.put(datumKey, StringUtils.EMPTY);
            }
            // 放入阻塞队列中,如果队列中无数据时会阻塞
            tasks.offer(Pair.with(datumKey, action));
        }

放入阻塞队列后,注册基本结束。
nacos会启用一个线程,一直循环将阻塞队列中的客户端的注册信息拿出来实现真正的注册。
下面看下源码:
在这里插入图片描述
实现了Runnable接口,所以我们主要看一下run方法:
在这里插入图片描述
可以看到一个线程一直循环取数据,当队列为空时,阻塞线程。

private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);
// 进行实际的注册任务
handle(pair);

服务注册基本流程已经结束。

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

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

相关文章

uni-app--》如何实现网上购物小程序(中上)?

&#x1f3cd;️作者简介&#xff1a;大家好&#xff0c;我是亦世凡华、渴望知识储备自己的一名在校大学生 &#x1f6f5;个人主页&#xff1a;亦世凡华、 &#x1f6fa;系列专栏&#xff1a;uni-app &#x1f6b2;座右铭&#xff1a;人生亦可燃烧&#xff0c;亦可腐败&#xf…

Jenkins终极部署详细版

&#xff08;一&#xff09;首先你需要配置好虚拟机的JDK环境和Maven环境 1、配置JDK环境 &#xff08;1&#xff09;上传安装包&#xff0c;然后解压 &#xff08;2&#xff09;修改Linux环境变量 具体参考&#xff1a; https://blog.csdn.net/u010227042/article/details/1…

腾讯云轻量应用服务器可以修改镜像,但有限制!

腾讯云轻量应用服务器镜像可以更换或修改吗&#xff1f;可以&#xff01;镜像可以修改&#xff0c;镜像是指轻量服务器的预装操作系统&#xff0c;轻量服务器创建成功后镜像也是可以更换的&#xff0c;如下图&#xff1a; 腾讯云轻量应用服务器镜像可以修改 目录 轻量服务器修…

自适应模糊PID控制算法

一、自适应模糊PID控制 自适应模糊PID控制将模糊控制与传统PID控制相结合&#xff0c;将两种控制方式进行结合&#xff0c;取长补短&#xff0c;对传统的算法进行优化&#xff0c;形成一种新的控制算法&#xff0c;自适应模糊PID控制可以用于很多场景&#xff0c;比如温度控制&…

stm32下载代码到单片机上需要调节BOOT为什么模式

一、BOOT模式选择图解 二、BOOT模式介绍 所谓启动&#xff0c;一般来说就是指下好程序后&#xff0c;重启芯片时&#xff0c;SYSCLK的第4个上升沿&#xff0c;BOOT引脚的值将被锁存。用户可以通过设置BOOT1和BOOT0引脚的状态&#xff0c;来选择在复位后的启动模式。 A. Mai…

【读书笔记】《MySQL技术NM InnoDB存储引擎》第一章 MySQL体系结构和存储引擎

文章目录第一章 MySQL体系结构和存储引擎前言1.1 定义数据库和实例1.2MySQL体系结构1.3MySQL存储引擎1.3.1InnoDB存储引擎1.3.2MyISAM存储引擎1.3.3NDB存储引擎1.3.4 Memory存储引擎1.3.5其他存储引擎1.4各存储引擎之间的比较1.5连接MySQL1.5.1 TCP/IP1.5.2命名管道和共享内存1…

np.concatenate函数和np.append函数用于数组拼接

一&#xff1a;np.concatenate() 函数介绍&#xff1a;np.concatenate((a, b), axis0)参数意思&#xff1a;a和b都为数组&#xff0c;axis可以选择大小&#xff0c;axis0 按照行拼接。axis1 按照列拼接。 对于一维数组&#xff0c;情况如下&#xff1a; import numpy as np a…

客户关系管理系统的设计与实现(论文+源码)_kaic

摘 要 近些年来&#xff0c;由于信息科技的不断进步&#xff0c;网络也越来越深入到了各行各业中&#xff0c;信息量呈现的方式各种各样。我们所处的时代社会不管在经济体制、方式&#xff0c;或是在居民消费构成上都产生了巨大的变化&#xff0c;然而现代科技不仅仅为人们生…

fastadmin弹窗添加二级类别

在程序开发中,经常遇上有一、二级表格情况,例如ask和answer,一个ask中,就有很多个answer,如果在后台中分两个列表很容易实现,但很不直观,现通过代码,实现在ask列表中,每个item添加一个查看answer按钮,点击该按钮弹窗显示对应的answer列表,在该弹窗中实现增删改查操作…

什么是MVCC?MVCC解决了什么问题?MVCC的实现原理?

1.什么是MVCC&#xff1f; MVCC全称是【Multi-Version ConCurrency Control】&#xff0c;即多版本控制协议。 多版本控制&#xff08;Multiversion Concurrency Control&#xff09;: 指的是一种提高并发的技术。最早的数据库系统&#xff0c;只有读读之间可以并发&#xff…

LNMP架构部署

目录一、安装 Nginx 服务1、安装依赖包2、创建运行用户3、编译安装4、优化路径5、添加 Nginx 系统服务二、安装 MySQL 服务1、安装Mysql环境依赖包2、创建运行用户3、编译安装4、修改mysql 配置文件5、更改mysql安装目录和配置文件的属主属组6、设置路径环境变量7、初始化数据库…

15.网络爬虫—selenium验证码破解

网络爬虫—selenium验证码破解一selenium验证码破解二破解平台打码平台超级鹰文识别基于人工智能的定制化识别平台 —图灵三英文数字验证码破解selenium破解验证码快捷登录古诗文网四滑动验证码破解selenium滑动验证码破解网易网盾测试案例五总结六后记前言&#xff1a; &#…

鲁祥老师吉他课学习笔记

鲁祥老师吉他课学习笔记 导语 参考教材&#xff1a; 《吉他入门经典教程》李国标 《弹指之间》潘尚文 《吉他教本》好连得出版社 《吉他教程》杰瑞吉他学校 《伯克利现代吉他教程》 《吉他考级教程》英国RSL其中的原声吉他和电吉他教程 《一个月电吉他新手养成计划》宫胁俊郎 …

C51 - LCD12864

LCD128641> 项目概述2> LCD12864参数2.1> LCD硬件原理框图2.2> 工作原理2.2> 6800接口引脚功能2.3> 6800接口时序3> 硬件设计4> 程序设计4.1> 初始化4.2 > 清屏4.3> 显示ASCII码4.4> 显示图片5> 复盘总结1> 项目概述 51单片机驱动LC…

用不了chatgpt,试试Claude-Claude注册教程

Claude是一款人工智能聊天机器人,由 Anthropic 公司开发。说到Anthropic公司就有意思了&#xff0c;Anthropic成立于2021年&#xff0c;其联合创始人Dario Amodei曾经担任OpenAI 研究副总裁&#xff0c;后来因为对OpenAI变成了CloseAI&#xff0c;心存不满&#xff0c;因此就自…

App 抓包提示网络异常怎么破?

背景 当你测试App的时候&#xff0c;想要通过Fiddler/Charles等工具抓包看下https请求的数据情况&#xff0c;发现大部分的App都提示网络异常/无数据等等信息。以“贝壳找房”为例&#xff1a; Fiddler中看到的请求是这样的&#xff1a; 你可能开始找证书的问题&#xff1a;是…

【MySQL】(2)数据类型

文章目录数据类型分类数值类型文本、二进制类型日期和时间类型String 类型数据类型分类 MySQL 支持多种数据类型&#xff0c;大致可分为数值类型&#xff0c;文本、二进制类型&#xff0c;时间日期&#xff0c;String类型。 数值类型 类型字节有符号范围无符号范围TINYINT1-1…

01 |「ChatGPT」简介

前言 ChatGPT 科普。 文章目录 前言一、ChatGPT 介绍1. ChatGPT 是什么2. ChatGPT 有哪些应用二、相关文献一、ChatGPT 介绍 登录网址:https://chat.openai.com/auth/login 1. ChatGPT 是什么 ChatGPT 是一个大型语言模型,由 OpenAl 公司训练,并基于 GPT-3.5 架构构建;它可…

java设计模式(2)单例模式、工厂模式、原型模式、建造者模式

用pr设计的图片&#xff0c;当封面不错 单例模式 单例对象的类必须保证只有一个实例存在 饿汉式单例 饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用&#xff0c;以后不再改变&#xff0c;所以天生是线程安全的 //饿汉式单例类. public class Singleton {//构…

tmall.item.sizemapping.template.create( 新增天猫商品尺码表模板 )

&#xffe5;开放平台免费API必须用户授权 新增天猫商品尺码表模板 男鞋、女鞋、运动鞋、户外鞋类目&#xff0c;尺码表维度为&#xff1a; 脚长&#xff08;cm&#xff09; 必选 内衣-文胸类目&#xff0c;尺码表维度为&#xff1a; 上胸围&#xff08;cm&#xff09; 必选 …