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

news/2024/12/27 16:21:59/

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

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

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

grpc server

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

    @PostConstructpublic 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()的代码:

    @Overridepublic 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为clientNameprivate 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是哪一个实现呢?

    @Overridepublic 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);//终于,我们找到了NamingGrpcClientProxythis.grpcClientProxy = new NamingGrpcClientProxy(namespace, securityProxy, serverListManager, properties,serviceInfoHolder);}

回过头来再次看NacosNamingServieregisterInstance方法。

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

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

别着急,谜底马上揭开😎

    @Overridepublic 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.ppmy.cn/news/1113195.html

相关文章

解决 SQLyog 连接 MySQL8.0+ 报错:错误号码2058

文章目录 一、问题现象二、原因分析三、解决方案1. 方案1&#xff1a;更新SQLyog版本2. 方案2&#xff1a;修改用户的授权插件3. 方案3&#xff1a;修复my.cnf 或 my.ini配置文件 四、最后总结 本文将总结如何解决 SQLyog 连接 MySQL8.0 时报错&#xff1a;错误号码2058 一、问…

C++ 动态库与静态库的区别?

一、生成方式 静态库生成 g -c add.cc -o add.o g -c del.cc -o del.o ar rcs libapi.a del.o add.o g -static main.cc -o static main -L./ -lapi -l./ ./static main动态库生成 g -c add.cc -o add.o g -c del.cc -o del.o g -shared -fPIC -o libapi.so del.o add.o g m…

9.10数字逻辑

基础内容 module 模块名&#xff08;【端口列表】&#xff09; 端口信号声明 信号数据类型有wire,reg 信号位宽 模块把输入的input转化为output 数据类型默认为wire&#xff0c;wire表电路间的连线 assign赋值目标必须是wire&#xff0c;始终激活&#xff0c;连续赋值语…

Linux入门教程||Linux 文件与目录管理

我们知道Linux的目录结构为树状结构&#xff0c;最顶级的目录为根目录 /。 其他目录通过挂载可以将它们添加到树中&#xff0c;通过解除挂载可以移除它们。 在开始本教程前我们需要先知道什么是绝对路径与相对路径。 绝对路径&#xff1a; 路径的写法&#xff0c;由根目录 /…

C++中使用嵌套循环计算斐波纳契数列

C中使用嵌套循环计算斐波纳契数列 最先研究这个数列的人是意大利人斐波那契&#xff0c;Leonardo Fibonacci&#xff0c;他在描述兔子生长的数目时用上了这数列: 第一个月初有一对刚诞生的兔子&#xff1b; 第二个月还是只有这一对&#xff1b; 第三个月初它们可以生育&#…

LeetCode-热题100-笔记-day27

2. 二叉树的层序遍历https://leetcode.cn/problems/binary-tree-level-order-traversal/ 给你二叉树的根节点 root &#xff0c;返回其节点值的 层序遍历 。 &#xff08;即逐层地&#xff0c;从左到右访问所有节点&#xff09;。 示例 1&#xff1a; 输入&#xff1a;root [3…

(vue2)商品列表

mytag标签组件封装 1创建组件&#xff1b; 2实现功能&#xff1a; 双击显示&#xff0c;自动聚焦&#xff1a;输入框注册双击dblclick事件&#xff0c;封装v-focus全局指令 失去焦点&#xff0c;隐藏输入框&#xff1a;绑定失去焦点事件blur &#xff08;当元素失去焦点时触…

基于SpingBoot+vue的乡村公益助老平台

创建一个具有两类使用者的乡村公益助老平台&#xff0c;其中的使用者分别为系统管理员和普通的义工&#xff08;护员&#xff09;。旨在为养老院信息管理提供软件产品技术支持&#xff0c;人员管理、药品食品管理和出行管理等功能满足管理人员在信息管理的基础需求&#xff0c;…