Nacos 服务注册概述及客户端注册实例源码分析(一)(上)

简介: Nacos 服务注册概述及客户端注册实例源码分析(一)(上)

Nacos 服务注册与发现概述

Nacos 核心功能点

服务注册: Nacos Client 会通过发送 REST 请求的方式向 Nacos Server 注册自己的服务,提供自身的元数据,比如 IP 地址、端口等信息,Nacos Server 接收到注册请求以后,就会把这些元数据信息存储在一个双层的内存 Map 中

服务心跳: 在服务注册后,Nacos Client 会维护一个定时心跳来持续通知 Nacos Server,说明服务一直处于可用状态,防止被剔除,默认 5s 发送一次心跳

服务健康检查: Nacos Server 会开启一个定时任务来检查注册服务实例的健康情况,对于超过 15s 没有收到客户端心跳会将它的 healthy 属性设置为 false「客户端服务发现时不会发现」,如果某个实例超过 30s 没有收到心跳,直接剔除该实例「被剔除的实例如果恢复发送心跳则会重新注册」

服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个 REST 请求给 Nacos Server,获取上面注册的服务清单,并且缓存在 Nacos Client 本地,同时会在 Nacos Client 本地开启一个定时任务来定时拉取服务端最新的注册表信息更新到本地缓存中

服务同步: Nacos Server 集群之间会互相同步服务实例,来保证服务信息的一致性

Nacos 服务端及客户端模块图

如上图,核心模块集中在 nacos-console、name-naming、nacos-config 中

Nacos 客户端服务注册源码入口分析

Nacos GitHub 地址

Nacos 源码,本文在 nacos-2.1.1 版本进行分析

服务注册信息

从 nacos-client 模块中开始说起,说起客户端就必然涉及到服务注册,先了解一下 Nacos 客户端会传递什么信息给到服务端侧,我们直接从 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 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);
    }
}

其实这就是客户端注册的一个 Test 类,它模仿了一个真实的服务注册进了 Nacos 的过程,包括 NacosServer 连接、实例的创建、实例属性的赋值、注册实例,所以在这个其中包括了服务注册的核心代码;仅从此处的代码分析,可以看出,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 方法提供:

// 心跳的间隔时间默认值 5s
public long getInstanceHeartBeatInterval() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL,
                                     Constants.DEFAULT_HEART_BEAT_INTERVAL);
}
// 心跳超时时间默认值 15s
public long getInstanceHeartBeatTimeOut() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_TIMEOUT,
                                     Constants.DEFAULT_HEART_BEAT_TIMEOUT);
}
// IP 删除超时时间默认值 30s
public long getIpDeleteTimeout() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.IP_DELETE_TIMEOUT,
                                     Constants.DEFAULT_IP_DELETE_TIMEOUT);
}
// 实例 ID 生成器默认值:simple
public String getInstanceIdGenerator() {
  return getMetaDataByKeyWithDefault(PreservedMetadataKeys.INSTANCE_ID_GENERATOR,
                                     Constants.DEFAULT_INSTANCE_ID_GENERATOR);
}

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

  • preserved.heart.beat.interval:心跳间隔 Key,默认值为 5s,也就是默认 5s 进行一次心跳
  • preserved.heart.beat.timeout:心跳超时 Key,默认值为 15s,也就是默认 15s 收不到心跳,实例将会标记为不健康
  • preserved.ip.delete.timeout:实例 IP 被删除 Key,默认值为 30s,也就是 30s 收不到心跳,实例将会被移除
  • preserved.instance.id.generator:实例 ID 生成器 Key,默认值为 simple

这些都是 Nacos 提供的默认值,也就是当前实例注册时会告知 Nacos Server 说:我的心跳间隔、心跳超时等对应的值是多少,按照这个值来判断我这个实例是否健康

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

NamingService 接口

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

  • 注册服务实例,提供了多个不同参数的重载方法,可以指定组名、集群名
// 注册服务实例,指定 IP、Port
void registerInstance(String serviceName, String ip, int port) throws NacosException
  • 注销服务实例
void deregisterInstance(String serviceName, String ip, int port) throws NacosException
  • 获取全部的服务实例
List<Instance> getAllInstances(String serviceName) throws NacosException
  • 获取健康的服务实例
List<Instance> selectInstances(String serviceName, boolean healthy) throws NacosException
  • 获取集群中健康的服务实例
List<I
  • 使用负载均衡策略选择一个健康的服务实例
Instance selectOneHealthyInstance(String serviceName) throws NacosException
  • 订阅服务事件
void subscribe(String serviceName, EventListener listener) throws NacosException
  • 取消订阅服务事件
void unsubscribe(String serviceName, EventListener listener) throws NacosException
  • 获取所有(或指定)服务名称
ListView<String> getServicesOfServer(int pageNo, int pageSize, ...) throws NacosException
  • 获取所有订阅的服务
List<ServiceInfo> getSubscribeServices() throws NacosException
  • 获取 Nacos 服务状态
String getServerStatus()
  • 主动关闭服务
void shutDown() throws NacosException

这些方法中提供了大量的重载方法,应用于不同场景、不同类型实例或服务的筛选,所以我们只需要在不同的情况下使用不同的方法即可

NamingService 实例化是通过 NamingFactory 类和上面的 Nacos 服务信息,从以下代码中可以看出这里采用了反射机制来实例化 NamingService,具体的实现类为 NacosNamingService:

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);
  }
}

NacosNamingService 实现

在示例代码中使用了 NamingService#registerInstance 方法来进行服务实例的注册,该方法接收两个参数:服务名称和实例对象;这个方法的最大作用是设置了当前实例的分组信息;在 Nacos 中,通过 Namespace、Group、Service、Cluster 等一层层的将实例进行环境的隔离;在这里设置了默认的分组名:DEFAULT_GROUP

public void registerInstance(String serviceName, Instance instance) throws NacosException {
  registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
}

紧接着调用的 registerInstance 方法如下,这个方法做了两件事情:

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

1、检查心跳时间设置的是否正确(心跳默认值是 5s)

public static void checkInstanceIsLegal(Instance instance) throws NacosException {
  // 实例的心跳间隔必须小于 "心跳超时" 和 "ip删除超时"
  if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval()
      || instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) {
    throw new NacosException(NacosException.INVALID_PARAM,
                             "Instance 'heart beat interval' must less than 'heart beat timeout' and 'ip delete timeout'.");
  }
  // 实例的集群名称不满足条件:只支持数字和字母
  if (!StringUtils.isEmpty(instance.getClusterName()) && !CLUSTER_NAME_PATTERN.matcher(instance.getClusterName()).matches()) {
    throw new NacosException(NacosException.INVALID_PARAM,String.format("Instance 'clusterName' should be characters with only 0-9a-zA-Z-. (current: %s)",
                                           instance.getClusterName()));
  }
}

2、通过 NamingClientProxy 代理类来执行服务注册操作

通过 clientProxy 属性可以发现 NamingClientProxy 这个代理接口的具体实现是由 NamingClientProxyDelegate 来完成的,这个可以直接从 NacosNamingService 构造方法看出,在 init 方法中进行初始化操作:

public NacosNamingService(Properties properties) throws NacosException {
  init(properties);
}
private void init(Properties properties) throws NacosException {
  ValidatorUtils.checkInitParam(properties);
  this.namespace = InitUtils.initNamespaceForNaming(properties);
  InitUtils.initSerialization();
  InitUtils.initWebRootContext(properties);
  initLogName(properties);
  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, properties);
  this.clientProxy = new NamingClientProxyDelegate(this.namespace, serviceInfoHolder, properties, changeNotifier);
}

目录
相关文章
|
5天前
|
SpringCloudAlibaba 负载均衡 Java
【微服务 SpringCloudAlibaba】实用篇 · Nacos注册中心
【微服务 SpringCloudAlibaba】实用篇 · Nacos注册中心
20 3
|
5天前
|
安全 Linux Nacos
如何使用公网地址远程访问内网Nacos UI界面查看注册服务
如何使用公网地址远程访问内网Nacos UI界面查看注册服务
25 0
|
5天前
|
负载均衡 Cloud Native Java
Nacos 注册中心(2023旧笔记)
Nacos 注册中心(2023旧笔记)
20 0
|
5天前
|
Dubbo Java 应用服务中间件
深度剖析:Dubbo使用Nacos注册中心的坑
2020年笔者在做微服务部件升级时,Dubbo的注册中心从Zookeeper切换到Nacos碰到个问题,最近刷Github又有网友提到类似的问题,就在这篇文章里做个梳理和总结。
深度剖析:Dubbo使用Nacos注册中心的坑
|
5天前
|
SpringCloudAlibaba Java Nacos
SpringCloud Alibaba微服务 -- Nacos使用以及注册中心和配置中心的应用(保姆级)
SpringCloud Alibaba微服务 -- Nacos使用以及注册中心和配置中心的应用(保姆级)
|
5天前
|
Dubbo 关系型数据库 MySQL
nacos常见问题之命名空间配置数据上线修改如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
104 1
|
5天前
|
存储 运维 监控
NACOS 配置中心和注册中心是分两个集群部署还是放在一个集群中
【2月更文挑战第33天】NACOS 配置中心和注册中心是分两个集群部署还是放在一个集群中
92 2
|
5天前
|
SpringCloudAlibaba 应用服务中间件 Nacos
【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心(下)
【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心
18 0
|
5天前
|
JSON SpringCloudAlibaba Java
【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心(上)
【微服务 SpringCloudAlibaba】实用篇 · Nacos配置中心
22 1
|
5天前
|
Nacos
nacos 配置页面的模糊查询
nacos 配置页面的模糊查询

热门文章

最新文章