《跟二师兄学Nacos吧》第1篇 Nacos客户端服务注册源码分析

简介: 《跟二师兄学Nacos吧》第1篇 Nacos客户端服务注册源码分析

开篇构想

在此之前,已经写了十多篇Nacos的文章,感觉Nacos还值得更深入的学习一下。于是萌生了写一个Nacos源码系列专栏的文章。


写作的目标呢,有两个:第一,能够系统的学习Nacos知识;第二,能够基于Nacos学到涉及到的知识点或面;


展现形式呢,也有两个:第一,单篇足够简单且又有价值;第二,发现代码中的新颖之处;


源码版本信息

目前在生产实践中建议大家采用1.4.2版本,但作为技术研究,本系列文章会基于2.0.2版本来仅仅讲解。这是两个跨度比较大的版本,建议大家配合源码进行学习。


关于源码拉取,环境搭建部分就不再赘述。下面就开始本篇文章,讲解Nacos服务注册的客户端部分。


服务注册信息

讲到服务注册,我们先要了解一下Nacos都会将什么信息传递给服务器。直接从Nacos Client项目的NamingTest看起:


Properties properties = new Properties();
properties.put(PropertyKeyConst.SERVER_ADDR, "127.0.0.1:8848");
properties.put(PropertyKeyConst.USERNAME, "nacos");
properties.put(PropertyKeyConst.PASSWORD, "nacos");
Instance instance = new Instance();
instance.setIp("1.1.1.1");
instance.setPort(800);
instance.setWeight(2);
Map<String, String> map = new HashMap<String, String>();
map.put("netType", "external");
map.put("version", "2.0");
instance.setMetadata(map);
NamingService namingService = NacosFactory.createNamingService(properties);
namingService.registerInstance("nacos.test.1", instance);

这是服务注册的核心所有代码。仅从此处的代码分析,可以看出,Nacos注册服务实例时,包含了两大类信息:Nacos Server连接信息和实例信息。


Nacos Server连接信息

Nacos Server连接信息,存储在Properties当中,包含以下信息:


Server地址:Nacos服务器地址,属性的key为serverAddr;

用户名:连接Nacos服务的用户名,属性key为username,默认值为nacos;

密码:连接Nacos服务的密码,属性key为password,默认值为nacos;

实例信息

注册实例信息用Instance对象承载,注册的实例信息又分两部分:实例基础信息和元数据。


实例基础信息包括:


instanceId:实例的唯一ID;

ip:实例IP,提供给消费者进行通信的地址;

port: 端口,提供给消费者访问的端口;

weight:权重,当前实例的权限,浮点类型(默认1.0D);

healthy:健康状况,默认true;

enabled:实例是否准备好接收请求,默认true;

ephemeral:实例是否为瞬时的,默认为true;

clusterName:实例所属的集群名称;

serviceName:实例的服务信息;

Instance类包含了实例的基础信息之外,还包含了用于存储元数据的metadata(描述数据的数据),类型为HashMap。


从Demo中放了两个数据:


netType:顾名思义,网络类型,这里的值为external,也就是外网的意思;

version:版本,Nacos的版本,这里是2.0这个大版本。

除了Demo中这些“自定义”的信息,在Instance类中还定义了一些默认信息,这些信息通过get方法提供:


public long getInstanceHeartBeatInterval() {
    return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
            Constants.DEFAULT_HEART_BEAT_INTERVAL);
}
public long getInstanceHeartBeatTimeOut() {
    return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
            Constants.DEFAULT_HEART_BEAT_TIMEOUT);
}
public long getIpDeleteTimeout() {
    return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
            Constants.DEFAULT_IP_DELETE_TIMEOUT);
}
public String getInstanceIdGenerator() {
    return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
            Constants.DEFAULT_INSTANCE_ID_GENERATOR);
}

上面的get方法在需要元数据默认值时会被用到:


preserved.heart.beat.interval:心跳间隙的key,默认为5s,也就是默认5秒进行一次心跳;

preserved.heart.beat.timeout:心跳超时的key,默认为15s,也就是默认15秒收不到心跳,实例将会标记为不健康;

preserved.ip.delete.timeout:实例IP被删除的key,默认为30s,也就是30秒收不到心跳,实例将会被移除;

preserved.instance.id.generator:实例ID生成器key,默认为simple;

这些都是Nacos默认提供的值,也就是当前实例注册时会告诉Nacos Server说:我的心跳间隙、心跳超时等对应的值是多少,你按照这个值来判断我这个实例是否健康。当然,如果你想让心跳“加速”,出现故障快速被移除,那可以跳短心跳间隙和超时时间。但这也意味着给Nacos服务带来一定的压力。


有了这些信息,我们基本是已经知道注册实例时需要传递什么参数,需要配置什么参数了。


NamingService接口

NamingService接口是Nacos命名服务对外提供的一个统一接口,看对应的源码就可以发现,它提供了大量实例相关的接口方法,比如:


服务实例注册;

服务实例注销;

获取服务实例列表;

获取服务单个实例;

订阅服务事件;

取消订阅服务事件;

获取所有(或指定)服务名称;

获取所有订阅的服务;

获取Nacos服务的状态;

主动关闭服务;

其中部分功能提供了大量的重载方法,应用于不同场景和不同类型实例或服务的筛选。这个就不逐一说明,按照需要或注释进行使用即可。


NamingService的实例化是通过NamingFactory类和上面提到的Nacos服务信息:


public static NamingService createNamingService(Properties properties) throws NacosException {
    try {
        Class<?> driverImplClass = Class.forName("com.alibaba.nacos.client.naming.NacosNamingService");
        Constructor constructor = driverImplClass.getConstructor(Properties.class);
        return (NamingService) constructor.newInstance(properties);
    } catch (Throwable e) {
        throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e);
    }
}

很明显,这里采用了反射的机制来实例化NamingService,接口的具体实现类为NacosNamingService类。


NacosNamingService的实现

在示例代码中使用了NamingService#registerInstance方法来进行服务实例的注册,该方法接收两个参数,服务名称和实例对象。


@Override

public void registerInstance(String serviceName, Instance instance) throws NacosException {

   registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);

}

1

2

3

4

这个方法的最大作用是设置了当前实例的分组信息。我们知道,在Nacos中,通过Namespace、group、Service、Cluster等一层层的将实例进行环境的隔离。在这里设置了默认的分组为“DEFAULT_GROUP”。


紧接着调用的registerInstance方法如下:


@Override

public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {

   NamingUtils.checkInstanceIsLegal(instance);

   clientProxy.registerService(serviceName, groupName, instance);

}

1

2

3

4

5

这个方法实现了两个功能:第一,检查心跳时间设置的对不对,配置的超时时间总不能比心跳间隔还短吧。第二,通过NamingClientProxy这个代理来执行服务注册操作。


反观NacosNamingService构造方法,会发现NamingClientProxy这个代理接口的具体实现是有NamingClientProxyDelegate来完成的。


NamingClientProxyDelegate中实现

NamingClientProxy调用registerService实际上调用的就是NamingClientProxyDelegate的对应方法:


@Override

public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

   getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);

}

1

2

3

4

真正调用注册服务的并不是代理实现类,而是根据当前实例是否为瞬时对象,来选择对应的客户端代理来进行请求的:


private NamingClientProxy getExecuteClientProxy(Instance instance) {

   return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;

}

1

2

3

如果当前实例为瞬时对象,则采用gRPC协议(NamingGrpcClientProxy)进行请求,否则采用http协议(NamingHttpClientProxy)进行请求。默认为瞬时对象,也就是说,2.0版本中默认采用了gRPC协议进行与Nacos服务进行交互。


NamingGrpcClientProxy中实现

关于gRPC协议这部分我们会单独进行讲解,这里暂时不做拓展。主要看其registerService方法实现:


@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName,
            instance);
    namingGrpcConnectionEventListener.cacheInstanceForRedo(serviceName, groupName, instance);
    InstanceRequest request = new InstanceRequest(namespaceId, serviceName, groupName,
            NamingRemoteConstants.REGISTER_INSTANCE, instance);
    requestToServer(request, Response.class);
}

在NamingGrpcClientProxy中做了两件事,一件事是通过事件监听器缓存了当前注册的实例信息用于恢复。缓存的数据结构为ConcurrentMap<String, Instance>,key为“serviceName@@groupName”,value就是前面封装的实例信息。


另外一件事就是封装了参数,基于gRPC协议进行服务的调用和结果的处理。


流程图

下面来看一张流程图,来汇总一下上面讲到的整个业务逻辑:

image.png

小结

关于Nacos源码分析的开篇就写这么多,主要分析了服务注册需要哪些维度的信息、客户端提供的核心服务处理类(NamingService)以及注册通信协议的选择。其中的一些内容还可以细化,比如gRPC协议的实现等,我们后续文章继续进行呈现。

目录
相关文章
|
10月前
|
SpringCloudAlibaba Java Nacos
nacos源码分析-服务注册(客户端)
一直都想写SpringCloudAlibaba的源码分析,终于开始动手第一篇了,如果想要看懂Nacos源码至少要把《SpringBoot自动》配置看了,不然就是看天书。本篇文章呢带大家一起来看一下Nacos-Client 客户端服务注册这一部分的源码。
|
9月前
|
存储 Java Nacos
Nacos服务注册与发现源码剖析
本文通过Nacos源码了解服务注册与发现原理。
118 0
Nacos服务注册与发现源码剖析
|
9月前
|
存储 关系型数据库 MySQL
nacos配置中心的底层原理以及实现
nacos配置中心的底层原理以及实现
189 0
|
10月前
|
存储 缓存 JSON
nacos源码分析-服务注册(服务端)
一篇文章我们了解了《Nacos服务注册》客户端源码,本篇文章我们来看一下服务注册Nacos服务端的源码执行情况。首先需要下载Nacos源码, https://github.com/alibaba/nacos/releases/tag/1.4.3 ,
nacos源码分析-服务注册(服务端)
|
10月前
|
负载均衡 Cloud Native Dubbo
二.SpringCloudAlibaba极简入门-服务注册与发现Nacos
在《Spring Cloud 极简入门》中我们学习了netflix 的 Eureka 组件作为微服务的服务发现。Nacos和Eureka有着相同的能力,甚至更为强大,作为Dubbo 生态系统中重要的注册中心实现。官方对它有如下定义: Nacos致力于帮助您发现,配置和管理微服务。它提供了一组简单有用的功能,使您能够实现动态服务发现,服务配置,服务元数据和流量管理。 Nacos使构建,交付和管理微服务平台变得更容易,更快捷。它是通过微服务或云原生方法支持以服务为中心的现代应用程序体系结构的基础架构。 这里我们看到Nacos不仅是服务发现组件,同时也是一个配置管理组件,也就是说它不仅可以用来取
|
12月前
|
Java Nacos 数据安全/隐私保护
都2023了,为什么选择Nacos,这篇文章让你入门Nacos
2023了,为什么选择Nacos,这篇文章让你入门Nacos.Nacos算是阿里的巅峰之作了 , 集万千优点与一身, 比Eureka更便捷,更优秀,更完美而且Nacos的社区非常的活跃因为它使用简单,易操作,易上手而且比Eureka有着更加优秀的控制台界面.下面来简单认识一下今天的主角Nacos吧
168 0
|
监控 Java 应用服务中间件
手把手教你将Eureka升级Nacos注册中心
由于原有SpringCloud体系版本比较老,最初的注册中心使用的Eureka后期官方无升级方案,配置中心无法在线管理配置,还有实时上下线的问题,因此需要将原有系统的Eureka服务升级Nacos注册心服务。
257 0
|
Nacos 数据安全/隐私保护 开发者
技术点-Nacos 安装和服务注册 | 学习笔记
快速学习技术点-Nacos 安装和服务注册
87 0
技术点-Nacos 安装和服务注册 | 学习笔记
|
存储 负载均衡 关系型数据库
SpringCloud Alibaba——Nacos服务注册与配置中心(三、Nacos持久化配置 & 集群搭建)
SpringCloud Alibaba——Nacos服务注册与配置中心(三、Nacos持久化配置 & 集群搭建)
SpringCloud Alibaba——Nacos服务注册与配置中心(三、Nacos持久化配置 & 集群搭建)
|
存储 Cloud Native Java
Nacos使用和注册部分源码介绍
Nacos致力于帮助您发现、配置和管理微服务。Nacos提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理。Nacos帮助您更敏捷和容易地构建、交付和管理微服务平台。Nacos是构建以“服务”为中心的现代应用架构 (例如微服务范式、云原生范式) 的服务基础设施。 接下来主要介绍Nacos作为注册中心的使用和注册部分的源码解析。
Nacos使用和注册部分源码介绍