Spring Cloud Nacos源码讲解(四)- Nacos服务端服务注册源码分析

news2024/11/23 1:22:36

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

服务端调用接口

​ 我们已经知道客户端在注册服务的时候实际上是调用的NamingService.registerInstance这个方法来完成实例的注册,而且在最后我们也告诉了大家实际上从本质上讲服务注册就是调用的对应接口nacos/v1/ns/instance,那咱们现在就在服务端先找到这个接口,然后来看具体服务端的操作。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HqhidJZd-1677029146341)(1561217892717-1418fb9b-7faa-4324-87b9-f1740329f564.jpeg)]

​ 这是从Nacos官网上我们看到的Nacos架构图,其实在这里我们已经就能分析出我们要找的接口应该在NamingService这个服务中,从源码角度来看,其实通过一下这个项目结构图中我们也能清楚的看见naming这个子模块,而且在源码的第一节课就和大家分析过这个naming实际上就是实现服务的注册的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0jP48fo-1677029146343)(20190703005808640.png)]

​ 那我们接着来向下看这个项目中的controller,因为我们知道所有的接口其实都在controller中,从这些Controller中我们就会明显的看到一个InstanceController,所以很明显注册实例一定和它有关

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NF0ihqte-1677029146345)(image-20211019160948545.png)]

​ 所以我们打开InstanceController来深入研究一下,这个时候会发现@RequestMapping注解中的值就是我们访问的注册接口

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QUvapo4d-1677029146346)(image-20211019161614434.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjkJ5rvQ-1677029146346)(image-20211019161726808.png)]

​ 接下来我们再来寻找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/365815.html

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

相关文章

网络工程(三)ensp配置静态路由

配置静态路由 这里选择的路由器是AR2220 因为有三个GE接口 下面说拓扑图 一、定义AR路由ip地址和下一条 AR1system-viewsysname AR1interface g0/0/0ip address 10.0.0.254 8interface g0/0/1ip address 50.0.0.1 8下一条代码[AR1]ip route-static 0.0.0.0 0 50.0.0.2AR2 s…

Linux SID 开发指南

Linux SID 开发指南 1 前言 1.1 编写目的 介绍Linux 内核中基于Sunxi 硬件平台的SID 模块驱动的详细设计&#xff0c;为软件编码和维护提供基 础。 1.2 适用范围 内核版本Linux-5.4, Linux-4.9 的平台。 1.3 相关人员 SID 驱动、Efuse 驱动、Sysinfo 驱动的维护、应用开…

Java 方法超详细整理,适合新手入门

目录 一、什么是方法呢&#xff1f; 二、方法的优点 三、带返回值方法定义 语法&#xff1a; 示例&#xff1a; 四、带返回值方法调用 语法&#xff1a; 示例&#xff1a; 五、结果示例 一、什么是方法呢&#xff1f; Java方法是语句的集合&#xff0c;它们在一起执行…

Android 9.0 仿ios的hotseat效果修改hotseat样式

1.概述 在9.0的系统rom定制化的产品中,在launcher3的定制化需求中,有很多功能需求点需要开发,在对一下ui的定制化的过程中,会参考ios的样式进行定制化,所以最近项目需求 要求仿ios的hotseat的样式来进行产品的定制,开发一款仿ios的hotseat,所以需要对hotseat进行分析,然…

C语言进阶(八)—— 链表

1. 链表基本概念1.1 什么是链表链表是一种常用的数据结构&#xff0c;它通过指针将一些列数据结点&#xff0c;连接成一个数据链。相对于数组&#xff0c;链表具有更好的动态性&#xff08;非顺序存储&#xff09;。数据域用来存储数据&#xff0c;指针域用于建立与下一个结点的…

【CKA】

— k8s basic — 安装版本信息查询 命令行自动补全功能设置 01. Namespaces and Pods 02. Assigning Pods to Nodes 03. Basic Deployments 04. Advanced Deployment 05. Jobs, Init Containers and Cron Jobs 06. Manage DaemonSets 07. Manage Services 08. Manage Ingress …

【技术】雷达液位计设备安装方案

一、设备概述 雷达液位计为我司自主研发&#xff0c;采用FMCW技术&#xff0c;以24G毫米雷达波作为载波信号&#xff0c;该产品测量精度高、功耗低、体积小、重量轻&#xff1b;测量过程不受温度、气压、泥沙、灰尘、河流污染物、水面漂浮物、空气等环境因素的影响&#xff0c…

spring boot项目中i18n和META-INF.spring下的文件的作用

目录标题一、resource下的文件二、i18n下messages_zh_CN.properties三、spring.factories文件四、org.springframework.boot.autoconfigure.AutoConfiguration.imports一、resource下的文件 org.springframework.boot.autoconfigure.AutoConfiguration.imports &#xff1b; - …

微信小程序日记、微信小程序个人空间、个人日记

一.简述 个人比较喜欢微信小程序&#xff0c;因为小程序所追求的用户体验、代码质量、美观的样式&#xff0c;简单方便丰富的api、样式封装等&#xff0c;同时又与普通的前端开发非常相似&#xff0c;让人很容易就上手。 这篇博客介绍的是一款记录个人/家庭日常记录的微信小程…

VsCode开发工具的入门及基本使用

VsCode开发工具的入门及基本使用一、VsCode介绍1.VsCode简介2.VsCode特点二、安装VsCode1.下载VsCode2.安装VsCode3.打开VsCode三、设置VsCode中文1.搜索中文语言插件2.安装中文语言插件四、初识VsCode1.VsCode左侧栏模块2.系统设置功能五、VsCode初始配置1.禁用自动更新2.开启…

MQTT 5协议你知道多少?

一、MQTT 5简介 MQTT协议是当今世界上最流行、接受度最高的物联网协议。自推出以来&#xff0c;MQTT已经成功地连接了各种规模的部署中的无数受限设备。 流行的用例包括从连接汽车、制造系统、物流和军事到企业聊天应用程序和移动应用程序。MQTT协议的广泛使用催生了进一步发…

【离线数仓-5-数据仓库环境准备】

离线数仓-5-数据仓库环境准备离线数仓-5-数据仓库环境准备1.数据仓库运行环境1.Hive环境搭建1.Hive引擎2.Hive on Spark配置2.Yarn环境配置2.数据仓库开发环境3.模拟数据准备离线数仓-5-数据仓库环境准备 1.数据仓库运行环境 数仓之外需要做的事情&#xff1a; 数据安全认证&…

OSCP-课外2(git泄露、SQL注入)

难度 中 主机发现&端口扫描 sudo arp-scan -l sudo nmap -p- 192.168.65.128 sudo nmap -p22,80 -sC -sV 192.168.65.128 首先,发现了.git目录,可能通过代码审计发现漏洞的存在。 其次,发现了一个描述“login.php修改的更安全”,说明以前login.php版本可能存在安全…

活动预告 | 2023 Meet TVM 开年首聚,上海我们来啦!

内容一览&#xff1a;从去年 12 月延期至今的 TVM 线下聚会终于来了&#xff01;首站地点我们选在了上海&#xff0c;并邀请到了 4 位讲师结合自己的工作实践&#xff0c;分享 TVM 相关的开发经验&#xff0c;期待与大家线下相聚~ 关键词&#xff1a;2023 Meet TVM 线下活动 自…

操作系统(day15) -- I/O设备

I/O设备的基本概念与分类 I/O设备就是可以将数据输入到计算机&#xff0c;或者可以接收计算机输出数据的外部设备&#xff0c;属于计算机中的硬件部分。 I/O设备按使用特性可以分为以下类型&#xff1a; 人机交互类设备。用于与计算机用户之间交互的设备&#xff0c;如打印机、…

华为OD机试用Python实现 -【组合出合法最小数】(2023-Q1 新题)

华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/category_12199275.html 华为OD详细说明:https://dream.blog.csdn.net/article/details/128980730 组合出合法最小数…

原子化 CSS 实践

原子化 CSS 实践 jcLee95 的CSDN 博客 邮箱 &#xff1a;291148484163.com CSDN 主页&#xff1a;https://blog.csdn.net/qq_28550263?spm1001.2101.3001.5343 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/129178547 目 录1. 概述 2. 原子化…

Flutter+【三棵树】

定义 在Flutter中和Widgets一起协同工作的还有另外两个伙伴&#xff1a;Elements和RenderObjects&#xff1b;由于它们都是有着树形结构&#xff0c;所以经常会称它们为三棵树。 这三棵树分别是&#xff1a;Widget、Element、RenderObject Widget树&#xff1a;寄存烘托内容…

代码随想录---二叉树的总结和二叉树的定义

二叉树的种类&#xff1a; 满二叉树&#xff1a;树的所有节点都是满&#xff0c;即都有左右孩子。 这棵二叉树为满二叉树&#xff0c;也可以说深度为k&#xff0c;有2^k-1个节点的二叉树。 完全二叉树&#xff1a;完全二叉树的定义如下&#xff1a;在完全二叉树中&#xff0c…

数据结构—堆(完全解析)

数据结构—堆&#xff08;完全解析&#xff09; 数据结构——堆&#xff08;Heap&#xff09;大根堆、小根堆 详解数据结构——堆 堆的基本存储 【从堆的定义到优先队列、堆排序】 10分钟看懂必考的数据结构——堆 【堆/排序】堆排序的两种建堆方法 【算法】排序算法之堆排序 C…