16 Nacos服务端服务注册源码分析

news2024/11/25 6:34:46

Nacos服务端服务注册源码分析

服务端调用接口

我们已经知道客户端在注册服务的时候实际上是调用的NamingService.registerInstance这个方法来完成实例的注册,而且在最后我们也告诉了大家实际上从本质上讲服务注册就是调用的对应接口nacos/v1/ns/instance,那咱们现在就在服务端先找到这个接口,然后来看具体服务端的操作。
在这里插入图片描述
这是从Nacos官网上我们看到的Nacos架构图,其实在这里我们已经就能分析出我们要找的接口应该在NamingService这个服务中,从源码角度来看,其实通过一下这个项目结构图中我们也能清楚的看见naming这个子模块,这个naming实际上就是实现服务的注册的。
在这里插入图片描述
那我们接着来向下看这个项目中的controller,因为我们知道所有的接口其实都在controller中,从这些Controller中我们就会明显的看到一个InstanceController,所以很明显注册实例一定和它有关
在这里插入图片描述
所以我们打开InstanceController来深入研究一下,这个时候会发现@RequestMapping注解中的值就是我们访问的注册接口
在这里插入图片描述
接下来我们再来寻找RESTful API接口POST请求类型的方法register,在这个方法中实际上就是接受用户请求,把收到的信息进行解析,还原成Instance,然后调用registerInstance方法来完成注册,这个方法才是服务端注册的核心

@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 = HttpRequestInstanceBuilder.newBuilder()
        .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build();
	//注册服务实例
    getInstanceOperator().registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

我们注意一下的这个方法

getInstanceOperator().registerInstance(namespaceId, serviceName, instance);

其中的getInstanceOperator(),就是判断是否采用Grpc协议,很明显这个位置走的是instanceServiceV2

private InstanceOperator getInstanceOperator() {
    return upgradeJudgement.isUseGrpcFeatures() ? instanceServiceV2 : instanceServiceV1;

服务注册

instanceServiceV2.registerInstance

实际上instanceServiceV2就是InstanceOperatorClientImpl,所以我们来看这里面的registerInstance方法.

@Override
public void registerInstance(String namespaceId, String serviceName, Instance instance) {
    //判断是否为瞬时对象(临时客户端)
    boolean ephemeral = instance.isEphemeral();
    //获取客户端ID
    String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), ephemeral);
    //通过客户端ID创建客户端连接
    createIpPortClientIfAbsent(clientId);
    //获取服务
    Service service = getService(namespaceId, serviceName, ephemeral);
    //具体注册服务
    clientOperationService.registerInstance(service, instance, clientId);
}

在这里我们要分析一些细节,其实Nacos2.0以后新增Client模型一个客户端gRPC长连接对应一个Client,每个Client有自己唯一的id(clientId)。Client负责管理一个客户端的服务实例注册Publish和服务订阅Subscribe。我们可以看一下这个模型其实就是一个接口(为了大家看着方便,注释改成了中文)

public interface Client {
    // 客户端id/gRPC的connectionId
    String getClientId();

    // 是否临时客户端
    boolean isEphemeral();
    // 客户端更新时间
    void setLastUpdatedTime();
    long getLastUpdatedTime();

    // 服务实例注册/注销/查询
    boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo);
    InstancePublishInfo removeServiceInstance(Service service);
    InstancePublishInfo getInstancePublishInfo(Service service);
    Collection<Service> getAllPublishedService();

    // 服务订阅/取消订阅/查询订阅
    boolean addServiceSubscriber(Service service, Subscriber subscriber);
    boolean removeServiceSubscriber(Service service);
    Subscriber getSubscriber(Service service);
    Collection<Service> getAllSubscribeService();
    // 生成同步给其他节点的client数据
    ClientSyncData generateSyncData();
    // 是否过期
    boolean isExpire(long currentTime);
    // 释放资源
    void release();
}

EphemeralClientOperationServiceImpl.registerInstance

EphemeralClientOperationServiceImpl实际负责处理服务注册,那我们来看具体方法

@Override
public void registerInstance(Service service, Instance instance, String clientId) {
    //确保Service单例存在
    Service singleton = ServiceManager.getInstance().getSingleton(service);
    //根据客户端id,找到客户端
    Client client = clientManager.getClient(clientId);
    if (!clientIsLegal(client, clientId)) {
        return;
    }
    //客户端Instance模型,转换为服务端Instance模型
    InstancePublishInfo instanceInfo = getPublishInfo(instance);
    //将Instance储存到Client里
    client.addServiceInstance(singleton, instanceInfo);
    client.setLastUpdatedTime();
    //建立Service与ClientId的关系
    NotifyCenter.publishEvent(new ClientOperationEvent.ClientRegisterServiceEvent(singleton, clientId));
    NotifyCenter
        .publishEvent(new MetadataEvent.InstanceMetadataEvent(singleton, instanceInfo.getMetadataId(), false));
}

ServiceManager

Service的容器是ServiceManager,但是在com.alibaba.nacos.naming.core.v2包下,容器中Service都是单例。

public class ServiceManager {
    
    private static final ServiceManager INSTANCE = new ServiceManager();
    //单例Service,可以查看Service的equals和hasCode方法
    private final ConcurrentHashMap<Service, Service> singletonRepository;
    //namespace下的所有service
    private final ConcurrentHashMap<String, Set<Service>> namespaceSingletonMaps;
    .....

​ 所以从这个位置可以看出,当调用这个注册方法的时候ServiceManager负责管理Service单例

//通过Map储存单例的Service
public Service getSingleton(Service service) {
    singletonRepository.putIfAbsent(service, service);
    Service result = singletonRepository.get(service);
    namespaceSingletonMaps.computeIfAbsent(result.getNamespace(), (namespace) -> new ConcurrentHashSet<>());
    namespaceSingletonMaps.get(result.getNamespace()).add(result);
    return result;
}

clientManager

这是一个接口这里我们要看它对应的一个实现类ConnectionBasedClientManager,这个实现类负责管理长连接clientId与Client模型的映射关系

// 根据clientId查询Client
public Client getClient(String clientId) {
    return clients.get(clientId);
}

Client实例AbstractClient

负责存储当前客户端的服务注册表,即Service与Instance的关系。注意对于单个客户端来说,同一个服务只能注册一个实例

@Override
public boolean addServiceInstance(Service service, InstancePublishInfo instancePublishInfo) {
    if (null == publishers.put(service, instancePublishInfo)) {
        MetricsMonitor.incrementInstanceCount();
    }
    NotifyCenter.publishEvent(new ClientEvent.ClientChangedEvent(this));
    Loggers.SRV_LOG.info("Client change for service {}, {}", service, getClientId());
    return true;
}

ClientOperationEvent.ClientRegisterServiceEvent

这里的目的是为了过滤目标服务得到最终Instance列表建立Service与Client的关系,建立Service与Client的关系就是为了加速查询。

​ 发布ClientRegisterServiceEvent事件,ClientServiceIndexesManager监听,ClientServiceIndexesManager维护了两个索引:

  • Service与发布clientId
  • Service与订阅clientId
private final ConcurrentMap<Service, Set<String>> publisherIndexes = new ConcurrentHashMap<>();
    
private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();

private void handleClientOperation(ClientOperationEvent event) {
    Service service = event.getService();
    String clientId = event.getClientId();
    if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) {
        addPublisherIndexes(service, clientId);
    } else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) {
        removePublisherIndexes(service, clientId);
    } else if (event instanceof ClientOperationEvent.ClientSubscribeServiceEvent) {
        addSubscriberIndexes(service, clientId);
    } else if (event instanceof ClientOperationEvent.ClientUnsubscribeServiceEvent) {
        removeSubscriberIndexes(service, clientId);
    }
}

//建立Service与发布Client的关系
private void addPublisherIndexes(Service service, String clientId) {
    publisherIndexes.computeIfAbsent(service, (key) -> new ConcurrentHashSet<>());
    publisherIndexes.get(service).add(clientId);
    NotifyCenter.publishEvent(new ServiceEvent.ServiceChangedEvent(service, true));
}

这个索引关系建立以后,还会触发ServiceChangedEvent,代表服务注册表变更。对于注册表变更紧接着还要做两个事情:1.通知订阅客户端 2.Nacos集群数据同步

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

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

相关文章

浅谈一下mysql8.0与5.7的字符集

修改字符集 修改步骤 在MySQL8.0版本之前&#xff0c;默认字符集为1atin1,utf8字符集指向的是utf8mb3。网站开发人员在数据库设计的时候往往会将编码修改为ut8字符集。如果遗忘修改默认的编码&#xff0c;就会出现乱码的问题。从MySQL8.0开始&#xff0c;数据库的默认编码将改…

《强化学习导论》之6.5 Q-Learning

Q-Learning:Off-Policy TD Control强化学习的早期突破之一是开发了一种称为Q学习的非策略TD控制算法&#xff08;Watkins&#xff0c;1989&#xff09;。其最简单的形式&#xff0c;定义为(6.8)在这种情况下&#xff0c;学习的动作-值函数Q直接近似于最优动作-值函数&#xff0…

【C++PrimerPlus】第三章 处理数据

文章目录前言内容目录3.1 简单变量3.1.2 变量名3.1.2 整形3.1.3 整形short,int,long,long long3.1.4 无符号类型3.1.5 选择整形类型3.1.6 整形字面值3.1.7 C如何确定常量的类型3.1.8 char类型&#xff1a;字符和小整数3.1.9 bool类型3.2 const修饰符3.3浮点数3.3.1 书写浮点数3…

【存储】存储协议

存储协议SCSI协议SCSI协议和存储系统SCSI协议寻址方式iSCSI产生的原因--->基于IP网络的SCSIiSCSI启动器-->目标器模型iSCSI体系结构iSCSI和SCSI、TCP和IP的关系SAS协议为什么要发展SASSAS协议层次结构SAS特点SAS的可扩展性SAS与其他传输技术的比较FCFC协议结构FC拓扑结构…

vueCli实现一个自定义loader

生活只会欺负穷人&#xff0c;爱情也是 webpack 作为前端项目的打包工具&#xff0c;具有很好的学习价值。下面来学习下其中的 Loader Loader可以帮助webpack将不同类型的文件转换为webpack可识别的模块 webpack中Loader使用&#xff1a;https://www.webpackjs.com/loaders/…

用VAE生成图像

用VAE生成图像自编码器AE&#xff0c;auto-encoderVAE讲讲为什么是log_var为什么要用重参数化技巧用VAE生成图像变分自编码器是自编码器的改进版本&#xff0c;自编码器AE是一种无监督学习&#xff0c;但它无法产生新的内容&#xff0c;变分自编码器对其潜在空间进行拓展&#…

二、Redis安装配置(云服务器、vmware本地虚拟机)

一、自己购买服务器 自己购买阿里云、青牛云、腾讯云或华为云服务器&#xff0c; 自带CentoOS或者Ubuntu环境&#xff0c;直接开干 二、Vmware本地虚拟机安装 1、VMWare虚拟机的安装&#xff0c;不讲解&#xff0c;默认懂 2、如何查看自己的linux是32位还是64位 getconf L…

云HIS医院管理系统源码 云HIS系统源码 SaaS模式 springboot开发

▶ SaaS运维平台多医院入驻强大的电子病历模板 &#xff0c;有源码&#xff0c;有演示&#xff01; ▶ 云HIS系统技术框架&#xff1a; 总体框架&#xff1a; SaaS应用&#xff0c;全浏览器访问 前后端分离&#xff0c;多服务协同 服务可拆分&#xff0c;功能易扩展 ▶ 云HI…

初阶C语言——实用调试技巧【详解】

文章目录1. 什么是bug&#xff1f;2. 调试是什么&#xff1f;有多重要&#xff1f;2.1 调试是什么&#xff1f;2.2 调试的基本步骤2.3 Debug和Release的介绍3.学会使用快捷键4.调试的时候查看程序当前信息4.1 查看临时变量的值4.2 查看内存信息4.3 查看调用堆栈4.4 查看汇编信息…

混凝土搅拌站远程监控解决方案

一、项目背景 随着大规模的基础设施建设&#xff0c;对混凝土搅拌设备的需求量日益增加&#xff0c;对其技术指标的要求也日益提高&#xff0c;其技术性能将直接关系到工程的质量和使用寿命。而混凝土生产的质量是在生产过程中形成的&#xff0c;而非最终强度的检测。混凝土生…

10 面向接口编程(上):一切皆服务,服务基于协议

按照面向接口编程的理念&#xff0c;将每个模块看成是一个服务&#xff0c;服务的具体实现我们其实并不关心&#xff0c;我们关心的是服务提供的能力&#xff0c;即接口协议。那么框架主体真正要做的事情是什么呢&#xff1f;其实是&#xff1a;定义好每个模块服务的接口协议&a…

That引导的宾语从句

That引导的宾语从句指的是that为宾语从句的引导词。宾语从句&#xff1a;置于动词、介词等词性后面&#xff0c;在句子中起宾语作用的从句叫做宾语从句。宾语从句分为三类&#xff1a;动词的宾语从句&#xff0c;介词的宾语从句和形容词的宾语从句。 一、that引导的宾语从句(在…

《数据万象带你玩转视图场景》第一期:avif图片压缩详解

前言随着硬件的发展&#xff0c;不管是手机还是专业摄像设备拍出的图片随便可能就有几M&#xff0c;甚至几十M&#xff0c;并且现在我们处于随处可及的信息海洋里&#xff0c;海量的图片带来了存储问题、带宽问题、加载时延问题等等。对图片信息进行有效的压缩处理无疑会极大的…

ARM架构Ubuntu下使用Docker安装MySQL

大家好&#xff0c;我是中国码农摘星人。 欢迎分享/收藏/赞/在看&#xff01; 由于ARM架构的限制&#xff0c;许多软件还没有做到完全适配&#xff0c;CentOS、MySQL等软件安装频繁出错。于是决定做一栏相关软件环境安装的文章。 基础信息 Apple M1 ProUbuntu 22.04 运行 使…

Python 如何安装 MySQLdb ?

人生苦短 我用python Python 标准数据库接口为 Python DB-API&#xff0c; Python DB-API为开发人员提供了数据库应用编程接口。 Python 数据库接口支持非常多的数据库&#xff0c; 你可以选择适合你项目的数据库&#xff1a; GadFlymSQLMySQLPostgreSQLMicrosoft SQL Serve…

来 CSDN 三年,我写了一本Python书

大家好&#xff0c;我是朱小五。转眼间已经来 CSDN 3年了&#xff0c;其中给大家一共分享了252篇Python文章。 但这三年&#xff0c;最大的收获还是写了一本Python书&#xff01; 在这个自动化时代&#xff0c;我们有很多重复无聊的工作要做。想想这些你不再需要一次又一次地做…

站内信箱系统的设计与实现

技术&#xff1a;Java、JSP等摘要&#xff1a;在经济全球化和信息技术成为发展迅速的今时今日&#xff0c;人们通过电子邮件收发进行信息传递已经成为主流。随着互联网和网络办公的发展&#xff0c;电子邮件正在被广泛应用在人们的日常生活中。跟据调查研究统计&#xff0c;在全…

文件系统-

文件系统 是一个面向用户的可视化管理类型的操作系统 其实就是管理硬盘的基本单位扇区&#xff0c;然后将存储数据可视化管理给用户 文件系统包含两个部分 文件的集合和目录结构 对于用户和系统来说文件系统时不一样的 操作系统只解释可执行文件 文件内部结构 文件就是基本…

【JVM】详解Java内存区域和分配

这里写目录标题一、前言二、运行时数据分区2.1程序计数器(PC)2.2 Java虚拟机栈2.3 本地方法栈2.4 Java堆2.5 方法区2.5.1 运行时常量池2.6 直接内存三、HotSpot虚拟机对象探秘3.1 对象的创建3.2 对象的内存布局3.3 对象的访问定位一、前言 C/C需要自行回收和释放已经没用的对象…

2023年 Java 发展趋势

GitHub 语言统计表明&#xff0c;Java在编程语言中排名第二&#xff0c;而在2022年的TIOBE指数中&#xff0c;Java排在第四。 抛开排名&#xff0c;Java是自诞生以来企业使用率最高的编程语言&#xff0c;作为一种编程语言&#xff0c;它比许多竞争对手都有更多的优点&#xf…