服务注册
注:
客户端-Client:对应各个微信服务(如:订单服务、支付服务等)
服务端-Server:指Nacos-Server
客户端(Client)的服务注册
1、测试类入手
我们先从Nacos源码中,Client项目的NamingTest测试类说起
public class NamingTest {
@Test
public void testServiceList() throws Exception {
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的实例化是通过NamingFactory类和上面的Nacos服务信息,
// 从代码中可以看出这里采用了反射机制来实例化NamingService,
// 具体的实现类为NacosNamingService:
NamingService namingService = NacosFactory.createNamingService(properties);
namingService.registerInstance("nacos.test.1", instance);
ThreadUtils.sleep(5000L);
List<Instance> list = namingService.getAllInstances("nacos.test.1");
System.out.println(list);
ThreadUtils.sleep(30000L);
// ExpressionSelector expressionSelector = new ExpressionSelector();
// expressionSelector.setExpression("INSTANCE.metadata.registerSource = 'dubbo'");
// ListView<String> serviceList = namingService.getServicesOfServer(1, 10, expressionSelector);
}
}
它模仿了一个真实的服务注册进Nacos的过程,包括NacosServer连接、实例的创建、实例属性的赋值、注册实例,所以在这个其中包含了服务注册的核心代码,仅从此处的代码分析,可以看出,Nacos注册服务实例时,包含了两大类信息:Nacos Server连接信息和实例信息。
Nacos Server连接信息
Server地址:Nacos服务器地址,属性的key为serverAddr;
用户名:连接Nacos服务的用户名,属性key为username,默认值为nacos;
密码:连接Nacos服务的密码,属性key为password,默认值为nacos;
Instance 实例信息
注册实例信息用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这个大版本。
在Instance类中还定义了一些默认信息:
- 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;
2、从启动微服务项目分析
创建一个服务项目,我们要让某一个服务注册到Nacos中,我们首先要引入一个依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>2.1.0</version>
</dependency>
通过SpingBoot的自动装配(首先找到)来加载EnableAutoConfiguration对应的类,其中跟服务注册有关的就是NacosServiceRegistryAutoConfiguration这个类
进入这个类,发现其中有一个与自动注册相关的Bean,NacosAutoServiceRegistration,这个类就是注册的核心
它的继承关系图:
NacosAutoServiceRegistration继承了AbstractAutoServiceRegistration,而AbstractAutoServiceRegistration类实现了ApplicationListener接口,一般实现ApplicationListener接口的类型都会实现一个方法onApplicationEvent(),这个方法会在项目启动的时候触发
然后在start()方法中调用register()方法来注册服务
现在我们已经知道了真实服务注册的入口和具体调用那个方法来注册,那我们再来分析一下register()方法,发现是NacosServiceRegistry类的register()方法
@Override
public void register(Registration registration) {
if (StringUtils.isEmpty(registration.getServiceId())) {
log.warn("No service to register for nacos client...");
return;
}
NamingService namingService = namingService();
String serviceId = registration.getServiceId();
String group = nacosDiscoveryProperties.getGroup();
// 创建实例
Instance instance = getNacosInstanceFromRegistration(registration);
try {
// namingService 向服务端注册此服务
namingService.registerInstance(serviceId, group, instance);
log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
instance.getIp(), instance.getPort());
}
catch (Exception e) {
log.error("nacos registry, {} register failed...{},", serviceId,
registration.toString(), e);
// rethrow a RuntimeException if the registration is failed.
// issue : https://github.com/alibaba/spring-cloud-alibaba/issues/1132
rethrowRuntimeException(e);
}
}
其中这行代码与测试类中的向服务端发起服务注册一致,我们接下来要分析namingService是如何向服务端发起的注册
namingService.registerInstance(serviceId, group, instance);
3、从测试类分析NamingService的实现(源码为Nacos-2.1.0版本)
代码中使用了NamingService的registerInstance方法来进行服务实例的注册,该方法接收三个参数,服务名称、分组名、实例对象;
Nacos是通过Namespace、group、Service、Cluster等一层层的将实例进行环境服务的隔离。
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
NamingUtils.checkInstanceIsLegal(instance);
clientProxy.registerService(serviceName, groupName, instance);
}
通过clientProxy我们发现NamingClientProxy这个代理接口的具体实现是有NamingClientProxyDelegate来完成的,这个可以从NacosNamingService构造方法中来看出
通过上面的代码了解到,NamingClientProxy调用registerService实际上调用的就是NamingClientProxyDelegate的对应方法:
@Override
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
getExecuteClientProxy(instance).registerService(serviceName, groupName, instance);
}
真正调用注册服务的并不是代理实现类,而是根据当前实例是否为瞬时对象,来选择对应的客户端代理来进行请求的:
如果当前实例为瞬时对象,则采用gRPC协议(NamingGrpcClientProxy)进行请求,
否则采用http协议(NamingHttpClientProxy)进行请求。
默认为瞬时对象,也就是说,2.0版本中默认采用了gRPC协议进行与Nacos服务进行交互。
private NamingClientProxy getExecuteClientProxy(Instance instance) {
return instance.isEphemeral() ? grpcClientProxy : httpClientProxy;
}
进入NamingGrpcClientProxy中registerService()方法的实现分析:其实就是做了两件事
1.缓存当前注册的实例信息用于恢复,缓存的数据结构为ConcurrentMap<String, Instance>,key为“serviceName@@groupName”,value就是前面封装的实例信息。
2.另外一件事就是封装了参数,基于gRPC进行服务的调用和结果的处理。
4、gRPC服务注册请求
进入doRegisterService()方法:
5、http服务注册请求
进入NamingHttpClientProxy类的registerService()方法