SpringCloud源码学习笔记3——Nacos服务注册源码分析

科技资讯 投稿 6100 0 评论

SpringCloud源码学习笔记3——Nacos服务注册源码分析

一丶基本概念&Nacos架构

1.为什么需要注册中心

    实现服务治理、服务动态扩容,以及调用时能有负载均衡的效果。

2.Nacos 的架构

    Naming Service :注册中心,提供服务注册,注销,管理
  • Config Service:配置中心,Nacos 配置中心为服务配置提供了编辑、存储、分发、变更管理、历史版本管理等功能,并且支持在实例运行中,更改配置。
  • OpenAPI:nacos对外暴露的接口,Provider App(服务提供者就是调用这里的接口,实现将自己注册到nacos,Consumer App(服务消费者也是使用这里的接口拉去配置中心中的服务提供者的信息。

3.nacos数据模型

二丶nacos注册中心简单使用

spring-cloud-starter-alibaba-nacos-discovery,并配置spring.cloud.nacos.discovery.server-addr=nacos服务启动的地址,即可在nacos可视化界面看到:

三丶服务注册源码分析

当我们服务引入spring-cloud-starter-alibaba-nacos-discovery,便可以实现自动进行注册,这是因为在spring.facotries中自动装配了NacosServiceRegistryAutoConfiguration

1.NacosServiceRegistryAutoConfiguration 引入了哪些类

NacosServiceRegistryAutoConfiguration 源码中,发现它注入了一下三个类

1.1.NacosServiceRegistry

    ServiceInstance 表示的是服务发现中的一个实例

    getHost,getIp获取注册实例host,ip等方法,是springcloud定义的规范接口

  • Registration一个标记接口,ServiceRegistry<R>这里面的R泛型就是Registration

  • ServiceRegistry 服务注册,定义如何向注册中心进行注册,和取消注册
    

    register服务注册,deregister服务取消注册等方法,入参是Registration。它是springcloud定义的规范接口。

spring cloud 定义了诸多规范接口,无论是服务注册,还是负载均衡,让其他中间件实现
    NacosServiceRegistry nacos服务注册接口,实现了ServiceRegistry,定义了如何注册,如何取消注册,维护服务状态等。

1.2.NacosRegistration

NacosRegistration 是 Registration的实现类,象征着一个Nacos注册中心的服务,也就是我们自己写的springboot服务

1.3.NacosAutoServiceRegistration

    AutoServiceRegistration一个标记接口,表示当前类是一个自动服务注册类
  • AbstractAutoServiceRegistration 实现了ApplicationListener,监听WebServerInitializedEvent web服务初始化结束事件,在ApplicationListener#onApplicationEvent中进行服务注册
  • NacosAutoServiceRegistration使用NacosServiceRegistryNacosRegistration的注册到nacos注册中心

NacosAutoServiceRegistration 是最核心的类,它负责监听事件,调用NacosServiceRegistry,将服务注册到注册中心。

2.AbstractAutoServiceRegistration 监听事件进行注册

2.1 WebServerInitializedEvent 从何而来

AbstractAutoServiceRegistration想响应WebServerInitializedEvent ,那么WebServerInitializedEvent 是哪儿发出的昵?

WebServerStartStopLifecycle#start方法

WebServerStartStopLifecycle实现了Lifecycle,在spring容器刷新结束的时候,会使用LifecycleProcessor调用所以Lifecycle#start,从而发送ServletWebServerInitializedEvent(WebServerInitializedEvent子类推送事件

Reactive的springboot上下文则是由WebServerStartStopLifecycle推送ReactiveWebServerInitializedEvent事件,原理一样,如下图

2.2 NacosAutoServiceRegistration如何进行服务注册

AbstractAutoServiceRegistration在响应事件后,会调用bind方法,进而调用register进行服务注册,这里就会调用到NacosAutoServiceRegistration#register

NacosServiceRegistry#register(NacosRegistration进行服务注册

3.NacosServiceRegistry 服务注册

NamingService将Instance进行注册

    NamingService,nacos框架中的类,负责服务注册和取消注册
  • Instance,nacos框架中的类,定义一个服务,记录ip,端口等信息
可以看到nacos有自己一套东西,脱离springcloud,也可以使用,这就是松耦合

下面我们看下NamingService是如何进行服务注册的

    ScheduledThreadPoolExecutor,每5秒发送一次心跳,发送心跳即请求nacos注册中心/instance/beat接口

  • NamingProxy 进行服务注册

    /nacos/v1/ns/instance。

4.nacos注册中心如何处理服务注册的请求

  • ServiceManager#registerInstance
    

    addInstance方法中

    namespaceId命名中间id,serviceName服务名称ephemeral是否临时服务构建出一个key,由于我们是一个临时实例,key最终为com.alibaba.nacos.naming.iplist.ephemeral + namespaceId ## + serviceName

    ConsistencyService一致性协议服务#put进行注册,这里和Nacos支持AP,CP架构有关,后续我们分析到一致性协议再补充。

    DelegateConsistencyServiceImpl(一致性协议门面他会根据key中的是临时实例,还是非临时实例,选择协议,最终选择到DistroConsistencyServiceImpl,继续调用put方法

    DistroConsistencyServiceImpl(Distro一致性协议服务)会同步到nacos集群中的其他实例,这部分我们后续分析,我们重点看下onPut,看看nacos服务到底如何注册。

    Notifier的任务队列中。

5.nacos 服务注册表结构

6.nacos注册表结构

/**
 * key 是命名空间
 * value 是 分组名称和Service服务的map
 *
 */
private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>(;

//Service结构如下
//集群和集群对象组成map
private Map<String, Cluster> clusterMap = new HashMap<>(;

//Cluster 中的属性记录所有实例Instance的集合

6.nacos服务注册异步任务队列处理注册任务

上面分析到最终服务注册请求被包装放到Notifier的任务队列中。我们看下任务队列的任务在哪里被拿出来消费。

Notifier实现了Runnable,在DistroConsistencyServiceImpl中使用@PostConstruct将它提交到了调度线程池中。

Notifier#run

Service#onChange,其updateIPs方法会更新实例的ip地址

// 这里 instances 里面就包含了新实例对象
// ephemeral 为 ture,临时实例
public void updateIPs(Collection<Instance> instances, boolean ephemeral {

    // clusterMap 对应集群的Map
    Map<String, List<Instance>> ipMap = new HashMap<>(clusterMap.size(;
    // 把集群名字都放入到ipMap里面,value是一个空的ArrayList
    for (String clusterName : clusterMap.keySet( {
        ipMap.put(clusterName, new ArrayList<>(;
    }

    // 遍历全部的Instance,这个List<Instance> 包含了之前已经注册过的实例,和新注册的实例对象
    // 这里的主要作用就是把相同集群下的 instance 进行分类
    for (Instance instance : instances {
        try {
          
            // 判断客户端传过来的是 Instance 中,是否有设置 ClusterName
            if (StringUtils.isEmpty(instance.getClusterName( {
                // 如果没有,就给ClusterName赋值为 DEFAULT
                instance.setClusterName(UtilsAndCommons.DEFAULT_CLUSTER_NAME;
            }

            // 判断之前是否存在对应的 ClusterName,如果没有则需要创建新的 Cluster 对象
            if (!clusterMap.containsKey(instance.getClusterName( {
                // 创建新的集群对象
                Cluster cluster = new Cluster(instance.getClusterName(, this;
                cluster.init(;
                // 放入到集群 clusterMap 当中
                getClusterMap(.put(instance.getClusterName(, cluster;
            }

            // 通过集群名字,从 ipMap 里面取
            List<Instance> clusterIPs = ipMap.get(instance.getClusterName(;
            // 只有是新创建集群名字,这里才会为空,之前老的集群名字,在方法一开始里面都 value 赋值了 new ArrayList对象
            if (clusterIPs == null {
                clusterIPs = new LinkedList<>(;
                ipMap.put(instance.getClusterName(, clusterIPs;
            }

            // 把对应集群下的instance,添加进去
            clusterIPs.add(instance;
        } catch (Exception e {
        }
    }

    // 分好类之后,针对每一个 ClusterName,写入到注册表中
    for (Map.Entry<String, List<Instance>> entry : ipMap.entrySet( {
        // entryIPs 已经是根据ClusterName分好组的实例列表
        List<Instance> entryIPs = entry.getValue(;
        
        // 对每一个 Cluster 对象修改注册表  ->updateIps
        clusterMap.get(entry.getKey(.updateIps(entryIPs, ephemeral;
    }

}

针对每一个集群分别进行Cluster#updateIps

public void updateIps(List<Instance> ips, boolean ephemeral {

    // 先判断是否是临时实例
    // ephemeralInstances 临时实例
    // persistentInstances 持久化实例
    // 把对应数据先拿出来,放入到 新创建的 toUpdateInstances 集合中
    Set<Instance> toUpdateInstances = ephemeral ? ephemeralInstances : persistentInstances;

    // 先把老的实例列表复制一份,先复制一份新的
    //写时复制,先复制一份
    HashMap<String, Instance> oldIpMap = new HashMap<>(toUpdateInstances.size(;
    for (Instance ip : toUpdateInstances {
        oldIpMap.put(ip.getDatumKey(, ip;
    }

    //省略了同步到其他nacos服务的代码。
    
    // 最后把传入进来的实例列表,重新初始化一个 HaseSet,赋值给toUpdateInstances
    toUpdateInstances = new HashSet<>(ips;
    
    // 判断是否是临时实例
    if (ephemeral {
        // 直接把之前的实例列表替换成新的
        ephemeralInstances = toUpdateInstances;
    } else {
        persistentInstances = toUpdateInstances;
    }
}

编程笔记 » SpringCloud源码学习笔记3——Nacos服务注册源码分析

赞同 (32) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽