【Nacos】Nacos服务注册服务端源码分析(一)

news2024/11/21 2:39:34

上篇简单看了下Nacos客户端在服务注册时做了什么。
本篇开始分析Nacos在服务注册时,服务端的相关逻辑。

建议先阅读这篇文章:支持 gRPC 长链接,深度解读 Nacos 2.0 架构设计及新模型

回顾一下,上篇我们看了Nacos在服务注册时,客户端的相关源码。Nacos2.X通过grpc支持了长链接,那么客户端发起调用,肯定就有一个grpc的服务端在接收请求。那么就从这个grpc的相关代码看起~

grpc server

abstract class BaseRpcServernacos-core中一个抽象类,有一个@PostConstruct 修饰的start方法。

    @PostConstruct
    public void start() throws Exception {
        String serverName = getClass().getSimpleName();
        String tlsConfig = JacksonUtils.toJson(grpcServerConfig);
        Loggers.REMOTE.info("Nacos {} Rpc server starting at port {} and tls config:{}", serverName, getServicePort(), tlsConfig);
        
        //启动grpc服务端
        startServer();
    
        Loggers.REMOTE.info("Nacos {} Rpc server started at port {} and tls config:{}", serverName, getServicePort(), tlsConfig);
        
        //钩子函数:处理退出信号
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            Loggers.REMOTE.info("Nacos {} Rpc server stopping", serverName);
            try {
                BaseRpcServer.this.stopServer();
                Loggers.REMOTE.info("Nacos {} Rpc server stopped successfully...", serverName);
            } catch (Exception e) {
                Loggers.REMOTE.error("Nacos {} Rpc server stopped fail...", serverName, e);
            }
        }));
    }

这个startServer()是一个抽象方法,我们看下其实现。

    /**
     * Start sever.
     *
     * @throws Exception exception throw if start server fail.
     */
    public abstract void startServer() throws Exception;

追踪代码发现,这个方法是当前类BaseRpcServer的子类BaseGrpcServer实现的

public abstract class BaseGrpcServer extends BaseRpcServer

看下startServer()的代码:

    @Override
    public void startServer() throws Exception {
        final MutableHandlerRegistry handlerRegistry = new MutableHandlerRegistry();
        
        //注册服务
        addServices(handlerRegistry, new GrpcConnectionInterceptor());
        
        NettyServerBuilder builder = NettyServerBuilder.forPort(getServicePort()).executor(getRpcExecutor());

        if (grpcServerConfig.getEnableTls()) {
            if (grpcServerConfig.getCompatibility()) {
                builder.protocolNegotiator(new OptionalTlsProtocolNegotiator(getSslContextBuilder()));
            } else {
                builder.sslContext(getSslContextBuilder());
            }
        }

        server = builder.maxInboundMessageSize(getMaxInboundMessageSize()).fallbackHandlerRegistry(handlerRegistry)
                .compressorRegistry(CompressorRegistry.getDefaultInstance())
                .decompressorRegistry(DecompressorRegistry.getDefaultInstance())
                .addTransportFilter(new AddressTransportFilter(connectionManager))
                .keepAliveTime(getKeepAliveTime(), TimeUnit.MILLISECONDS)
                .keepAliveTimeout(getKeepAliveTimeout(), TimeUnit.MILLISECONDS)
                .permitKeepAliveTime(getPermitKeepAliveTime(), TimeUnit.MILLISECONDS)
                .build();

		//启动grpc的server服务
        server.start();
    }

研读框架源码时,先不要陷入细节当中,第一遍梳理清楚整个框架的即可,关于grpc-server就先看到这里。

上文中我们提到了抽象类BaseRpcServer,简单分析下这个类。

BaseRpcServer

在这里插入图片描述
BaseRpcServerBaseGrpcServer 都是抽象类,GrpcClusterServerGrpcSdkServer都是抽象实现类,并且这两个实现类都有@Service注解标注,那么就意味着这两个类会被注册为spring bean 。
上文我们提过BaseRpcServer.start()有一个@PostConstruct注解,那么也就意味着具体调用时使用了GrpcClusterServerGrpcSdkServer的任何一个类,都会去调用BaseRpcServer.start()方法去启动grpc-server

GrpcClusterServerGrpcSdkServer的区别

从名字上可以看出,一个是Cluster服务调用,一个是SDK调用。
那么客户端注册使用的是哪个Server?
BaseRpcServer中定义了一个获取端口偏移量的方法:

    /**
     * the increase offset of nacos server port for rpc server port.
     *
     * @return delta port offset of main port.
     */
    public abstract int rpcPortOffset();

GrpcSdkServer对此给出的实现是返回一个常量定义:

public static final Integer SDK_GRPC_PORT_DEFAULT_OFFSET = 1000;

GrpcClusterServer给出的常量中定义的端口是CLUSTER_GRPC_PORT_DEFAULT_OFFSET = 1001

即然定义了端口,那么这个常量在创建这两个server的时候肯定会用到,我们通过这个常量去寻找到调用方,自然而然也就找到了server创建的逻辑,进而找到这两个server使用上的区别。

通过常量Constants.SDK_GRPC_PORT_DEFAULT_OFFSET发现一个新的类GrpcSdkClientrpcPortOffset()方法使用了这个常量。
在这里插入图片描述
在这里插入图片描述

通过观察构造方法的调用,我们找到了RpcClient的创建工厂RpcClientFactory

	//本地缓存,存储client,key为clientName
	private static final Map<String, RpcClient> CLIENT_MAP = new ConcurrentHashMap<>();

    public static RpcClient createClient(String clientName, ConnectionType connectionType, Integer threadPoolCoreSize,
                                         Integer threadPoolMaxSize, Map<String, String> labels, RpcClientTlsConfig tlsConfig) {

        if (!ConnectionType.GRPC.equals(connectionType)) {
            throw new UnsupportedOperationException("unsupported connection type :" + connectionType.getType());
        }
        
        //如果当前clientName不存在,那么执行GrpcSdkClient的创建逻辑
        return CLIENT_MAP.computeIfAbsent(clientName, clientNameInner -> {
            LOGGER.info("[RpcClientFactory] create a new rpc client of " + clientName);
            try {
                return new GrpcSdkClient(clientNameInner, threadPoolCoreSize, threadPoolMaxSize, labels, tlsConfig);
            } catch (Throwable throwable) {
                LOGGER.error("Error to init GrpcSdkClient for client name :" + clientName, throwable);
                throw throwable;
            }
            
        });
    }

那么再看下上边源码中createClient的调用方是谁,查看代码得知分别有两个调用方,ClientWorkerNamingGrpcClientProxy,第二个调用方是不是有点熟悉?
对的,我们在上篇文章看客户端注册服务的代码时,看到过如下代码:

@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}

private NamingClientProxy getExecuteClientProxy(Instance instance) {
    return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}

那么这个grpcClientProxy是哪一个实现呢?

    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        NamingUtils.checkInstanceIsLegal(instance);
        clientProxy.registerService(serviceName, groupName, instance);
    }

再次观察创建服务实例的代码,我们可以看到在实际的注册服务时,使用了一个客户端代理clientProxy来做处理,我们来看下这个代理的是怎么创建的。

    private NamingClientProxy clientProxy;

    private void init(Properties properties) throws NacosException {
        final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(properties);
        
        ValidatorUtils.checkInitParam(nacosClientProperties);
        this.namespace = InitUtils.initNamespaceForNaming(nacosClientProperties);
        InitUtils.initSerialization();
        InitUtils.initWebRootContext(nacosClientProperties);
        initLogName(nacosClientProperties);
    
        this.notifierEventScope = UUID.randomUUID().toString();
        this.changeNotifier = new InstancesChangeNotifier(this.notifierEventScope);
        NotifyCenter.registerToPublisher(InstancesChangeEvent.class, 16384);
        NotifyCenter.registerSubscriber(changeNotifier);
        this.serviceInfoHolder = new ServiceInfoHolder(namespace, this.notifierEventScope, nacosClientProperties);
		//一目了然
        this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, nacosClientProperties, changeNotifier);
    }

this.clientProxy = new NamingClientProxyDelegate,可以看到这个代理实际上是一个创建了一个委托类,那继续看下这个委托类的构造方法。

    public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfoHolder, NacosClientProperties properties,
            InstancesChangeNotifier changeNotifier) throws NacosException {
        this.serviceInfoUpdateService = new ServiceInfoUpdateService(properties, serviceInfoHolder, this,
                changeNotifier);
        this.serverListManager = new ServerListManager(properties, namespace);
        this.serviceInfoHolder = serviceInfoHolder;
        this.securityProxy = new SecurityProxy(this.serverListManager.getServerList(),
                NamingHttpClientManager.getInstance().getNacosRestTemplate());
        initSecurityProxy(properties);
        this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties);
        //终于,我们找到了NamingGrpcClientProxy
        this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties,
                serviceInfoHolder);
    }

回过头来再次看NacosNamingServieregisterInstance方法。

    @Override
    public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
        NamingUtils.checkInstanceIsLegal(instance);
        clientProxy.registerService(serviceName, groupName, instance);
    }

我们已经知道了clientProxyNamingClientProxyDelegate,那么就看下它是如何实现registerService即可。

别着急,谜底马上揭开😎

    @Override
    public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
        getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
    }

    private NamingClientProxy getExecuteClientProxy(Instance instance) {
        return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
    }

instance.封装了客户端要注册的服务,ephemeral默认为true。所以默认会选择grpcClientProxy去注册服务。

this.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties,
                serviceInfoHolder);

回答我们自己提出的问题:GrpcClusterServerGrpcSdkServer的区别?
后者是客户端注册使用的,那么前者肯定就是服务内部调用使用的了。

总结

梳理关键类和方法

  • 客户端
    • NacosDiscoveryAutoRegister
      • 是一个ApplicationListener,监听WebServerInitializedEvent事件后,注册当前实例
    • NacosNamingService
      • private void init(Properties properties) throws NacosException
        • 初始化时,定义了创建客户端的代理委托类clientProxyNamingClientProxyDelegate
      • registerInstance
        • 使用clientProxy注册服务
    • NamingClientProxyDelegate
      • 构造方法注入: grpcClientProxy = new NamingGrpcClientProxy
      • instance.isEphemeral() ? grpcClientProxy : httpClientProxy
    • NamingGrpcClientProxy
      • registerService
      • requestToServer
        • this.rpcClient.request(request) 这里的rpcClient其实就是GrpcSdkClient
  • 服务端
    • RpcClientFactory
      • createClient :返回GrpcSdkClient
    • GrpcSdkClient
      • 未完待续…

本篇很大篇幅上讲了服务注册时,服务端grpc-server相关的一些逻辑。从中可以看到服务端使用了很多委托类、代理类来抽象、封装相关业务逻辑,所以刚开始看框架源码如果一头雾水的时候,不要着急。抓大放小,先建立整体认知后,再回过头来深入细节。

下篇从源码上深入服务端在注册服务时的业务细节。

下班!🕶️

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

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

相关文章

四种常用的自动化测试框架

一直想仔细研究框架&#xff0c;写个流水账似的测试程序不难&#xff0c;写个低维护成本的测试框架就很难了&#xff0c;所以研究多种测试框架还是很有必要的&#xff0c;知道孰优孰劣&#xff0c;才能在开始编写框架的时候打好基础&#xff0c;今天读到了KiKi Zhao的翻译文章&…

reverse_iterator 逆序迭代器

在上一篇 C迭代器 iterator&#xff08;与逆序迭代器&#xff09; 中&#xff0c;我们看到了迭代器的基本用法。 现在我们看看一个奇怪的现象 意料之外的减法运算 无符号数的 0 减 1 变成了什么&#xff1f; &#xff1f;已知 size_t 一般为 unsigned long long 类型。 #…

区块链(4):区块链去中心化

1 区块链白皮书中的公有链&#xff0c;私有链&#xff0c;联盟链概念介绍 区块链系统根据应用场景和设计体系的不同&#xff0c;一般分为公有链、联盟 链和专有链(私有链)。其中: 公有链的各个节点可以自由加入和退出网络&#xff0c;并参加链上数据的读 写&#xff0c;运行时…

SpringBoot+MyBatis flex实现简单增删改查

一&#xff1a;创建SpringBoot项目 SpringBoot版本选择2.7.15 勾选相关的选项&#xff0c;并点击Create 项目创建完成 二.pom文件添加相关的依赖 <dependencies><dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starte…

排序——直接插入排序折半插入排序

文章目录 前言一、排序的基本概念1. 排序的定义2. 排序的分类1) 稳定排序2) 不稳定排序 二、插入排序1. 直接插入排序1&#xff09;直接插入排序算法分析 2. 直接插入排序代码3. 直接插入排序时间复杂度4. 折半插入排序5. 折半插入排序代码 总结 前言 排序的基本概念数据结构稳…

从数字化到智能化再到智慧化,智慧公厕让城市基础配套更“聪明”

随着科技的迅猛发展&#xff0c;城市生活方式与配置设施的方方面&#xff0c;面也在不断的改变和升级。智慧公厕作为城市基础配套设施的一部分&#xff0c;从数字化到智能化再到智慧化&#xff0c;正逐渐展现出其独特的魅力和优势。实现了公共厕所建设、使用与管理方式的全面变…

【AI】机器学习——支持向量机(非线性及分析)

5. 支持向量机(线性SVM) 文章目录 5.4 非线性可分SVM5.4.1 非线性可分问题处理思路核技巧核函数特点 核函数作用于SVM 5.4.2 正定核函数由 K ( x , z ) K(x,z) K(x,z) 构造 H \mathcal{H} H 空间步骤 常用核函数 5.5 SVM参数求解算法5.6 SVM与线性模型关系 5.4 非线性可分SVM …

奇偶数之和

任务描述 本关任务&#xff1a;请编一个函数fun(int *a,int n,int *odd,int *even)&#xff0c;函数的功能是分别求出数组a中所有奇数之和以及所有偶数之和。形参n给出数组中数据的个数&#xff1b;利用指针odd返回奇数之和&#xff0c;利用指针even返回偶数之和。 例如&#…

UE5_OpenCV库的加载方式

UE5使用opencv库要在系统中添加opencv的环境变量 在项目文件夹下新建ThirdParty,将OpenCV中的bin、opencv文件夹copy到ThirdParty中 打开项目,找到source目录下的build.cs文件 修改build.cs内容,添加头文件路径,dll路径,lib路径 // Copyright Epic Games, Inc. All Right…

leetcode21

题目&#xff1a; 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例 1&#xff1a; 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4]示例 2&#xff1a; 输入&#xff1a;l1 [], l2 [] 输…

腾讯mini项目-【指标监控服务重构】2023-07-21

今日已办 当在Docker容器中运行程序时&#xff0c;可能会遇到使用os.Getpid()函数时出现异常的情况。这是因为Docker容器中的进程隔离机制与宿主机器不同&#xff0c;容器内部的进程可能无法访问宿主机器的进程信息。 要解决这个问题&#xff0c;可以尝试&#xff1a; 使用do…

Linux磁盘管理:最佳实践

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

每日一博 - 防范彩虹表攻击_数据库存储密码的秘密武器

文章目录 概述图解小结 概述 加盐&#xff08;salting&#xff09;是一种安全存储数据库中密码并验证其真实性的常见方法&#xff0c;它的主要目的是增加密码的安全性&#xff0c;以防止常见的密码攻击&#xff0c;如彩虹表攻击。以下是关于如何使用加盐技术的简要介绍&#x…

计算机二级python基础题刷题笔记(三)

hello&#xff0c;看到三的小伙伴们你们已经超过30%的对手啦&#xff01;接下来也要加油呀 代码没有最好&#xff0c;只有更好&#xff0c;如果你有更好的想法答案欢迎在评论区里发表呀 1、将程序里定义好的std列表里的姓名和成绩与已经定义好的模板拼成一段话&#xff0c;显示…

我的微信公众号开通啦,来关注我吧

我的微信公众号开通啦&#xff0c;承蒙各位粉丝的厚爱&#xff0c;请大家动动手指扫码关注吧 公众号主要以Android开发技术文章为主&#xff0c;活跃度高&#xff0c;紧跟技术前沿&#xff0c;内容深度而全面&#xff0c;重要的是 私信必回&#xff01;私信必回&#xff01;私…

利用github托管个人网站

如何制作个人学术主页&#xff1f; - 知乎去年年底刚刚基于开源项目al-folio重新做了自己的个人主页。注意&#xff0c;这个开源项目很好&#xff0c;但是有个尴尬的问题…https://www.zhihu.com/question/281476526/answer/2360738062 your site可能会出现的慢一点&#xff0…

qsort函数详细讲解以及利用冒泡排序模拟实现qsort函数

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言程序设计————KTV C语言小游戏 C语言进阶 C语言刷题 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂。 目录 1.qsort函数 1.1qsort函数的参数 …

Java笔记:Arthas-Java线上异常排查

一、安装 arthas在github上有个page&#xff0c;地址是https://alibaba.github.io/arthas/。 安装的方式有好几种&#xff1a; 1.直接下载一个可以启动的jar包然后用java -jar的方式启动 2.用官方提供的as.sh脚本一键安装 3.用rpm的方式安装 本篇介绍第一种方式&#xff0c;因…

无涯教程-JavaScript - EXP函数

描述 EXP函数返回e升至数字的幂。常数e等于自然对数的底数2.71828182845904。 语法 EXP (number)争论 Argument描述Required/OptionalNumberThe exponent applied to the base e.Required Notes 要计算其他碱基的幂,请使用幂运算符(^) EXP是LN的倒数,LN是数字的自然对数…

基于SSM的汽车租赁后台管理系统

基于SSM的汽车租赁后台管理系统 介绍 包括登录、首页、客户管理、车辆管理、汽车出租、出租单管理、汽车入库、检查单管理、系统管理等功能&#xff0c;适合二次开发课程设计、毕业设计等 软件架构 SSM 运行环境 数据库 mysql 安装教程输入链接说明 端口&#xff1a;3306…