Nacos(1.4.2)注册中心原理及源码系列(二)- Nacos Client 和 Spring Cloud 集成

1、客户端服务注册

    Nacos在1.x版本的通信方式采用的HTTP协议,2.0版本以后会添加gRPC协议,本文写作采用的版本为1.4.2,所以本文还是基于HTTP协议来分析,服务注册的接口地址为:/nacos/v1/ns/instance,此接口的源码全路径为:com.alibaba.nacos.naming.controllers.InstanceController#register。

@CanDistro
@PostMapping
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public String register(HttpServletRequest request) throws Exception {

    final String namespaceId = WebUtils
        .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
    final String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);
    NamingUtils.checkServiceNameFormat(serviceName);

    final Instance instance = parseInstance(request);
	// 进行注册
    serviceManager.registerInstance(namespaceId, serviceName, instance);
    return "ok";
}

    Nacos提供了多种客户端的集成方式,本文主要剖析 Spring Cloud 的集成方式的源码,如果读者感兴趣其它的集成方式,可以去Nacos官网查看其它的集成方式。

1.添加依赖

// 这些需要先添加Spring Cloud的依赖,我做了省略。。。。

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    <version>${latest.version}</version>
</dependency>

2.配置Nacos server地址

spring:
  application:
    name: nacos-demo
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848 // Nacos server地址

    接下来分析一下Spring Cloud集成Nacos客户端的源码,在spring-cloud-starter-alibaba-nacos-discovery-2.2.2.RELEASE.jar中/META-INF/spring.factories有如下代码:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  -- 省略其它代码。。。。。
  com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\ -- 实现服务注册与发现的核心注册类

    熟悉Spring Boot的同学都知道这个文件,这里是配置AutoConfiguration的地方,在这里只需要关注其中的:NacosServiceRegistryAutoConfiguration,这个类中分别注入了NacosServiceRegistry、NacosRegistration、NacosAutoServiceRegistration三个类,Nacos的客户端主要是靠这几个类来实现服务注册和发现的,首先我们来看一下NacosAutoServiceRegistration的类继承关系,如下:

    细心的同学可能已经发现NacosAutoServiceRegistration的继承的AbstractAutoServiceRegistration类实现了ApplicationListener接口,那么必定在AbstractAutoServiceRegistration类中监听的某个Event,果然在AbstractAutoServiceRegistration类中发现了如下代码:

// 监听了WebServerInitializedEvent事件,WebServerInitializedEvent有一个子类ServletWebServerApplicationContext
public void onApplicationEvent(WebServerInitializedEvent event) {
    bind(event);
}

@Deprecated
public void bind(WebServerInitializedEvent event) {
    // 省略其它代码。。。。。。
    this.start();
}

public void start() {
    // 省略其它代码。。。。。。。
    if (!this.running.get()) {
        // 省略其它代码。。。。。。。
        register();
        // 省略其它代码。。。。。。。
    }
}

     上述源码中发现最终会调用一个register方法,这个方法就是真正向 Nacos Server 注册了当前实例。

// com.alibaba.cloud.nacos.registry.NacosServiceRegistry#register
@Override
public void register(Registration registration) {
	// 省略其它代码。。。。。。。

    try {
        // 真正调用了Nacos Server接口
        namingService.registerInstance(serviceId, group, instance);
        log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
                 instance.getIp(), instance.getPort());
    }
    catch (Exception e) {
        // 省略其它代码。。。。。。。
    }
}

// com.alibaba.nacos.client.naming.NacosNamingService#registerInstance
@Override
public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {
    String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);
    if (instance.isEphemeral()) {
        BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);
        beatReactor.addBeatInfo(groupedServiceName, beatInfo);
    }
    serverProxy.registerService(groupedServiceName, groupName, instance);
}

// com.alibaba.nacos.client.naming.net.NamingProxy#registerService
public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {
    // 省略其它代码。。。。。。。
    
    // 采用HTTP请求调用 /nacos/v1/ns/instance
    reqApi(UtilAndComs.nacosUrlInstance, params, HttpMethod.POST);
}

    从源码中可以看出最终调用了reqApi方法,向 Nacos Server /nacos/v1/ns/instance 接口发送了一个POST请求,把当前实例注册进去,到这里整个客户端的核心注册流程就分析完了。

2、客户端服务发现

    服务发现的接口地址为:/nacos/v1/ns/instance/list,此接口的源码全路径为:com.alibaba.nacos.naming.controllers.InstanceController#list

@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public ObjectNode list(HttpServletRequest request) throws Exception {

    // 省略其它代码。。。。。。。
    // 省略的代码基本上都是在获取request中的参数
    return doSrvIpxt(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant,
                     healthyOnly);
}

    在spring.factories中配置了一个NacosDiscoveryClientConfiguration类,此类向Spring中注入了一个NacosWatch类,这类的类图如下:     从上图可以看出,此类实现了Lifecycle接口,这个接口是Spring设计的生命周期接口,如果实现这个接口,那么就会在Spring加载完所有的Bean并初始化之后就会回调start()方法,在这个方法中完成了服务的拉取并更新到本地缓存,代码如下:

    从源码可以看出最后也是调用了serverProxy.queryList方法,这个方法也是发起了一个HTTP的请求,调用了Nacos Server的/nacos/v1/ns/instance/list接口,进行服务拉取。

    到这里已经从源码级别分析了Spring Cloud的集成了Nacos客户端关于服务拉取的代码,其实代码还是比较简单的,总结来说就是构造出list接口需要的参数,然后发起HTTP请求,进行服务拉取。

    从源码中注意留意一个scheduleUpdateIfAbsent方法的调用,这里提交了一个UpdateTask任务,UpdateTask是一个实现了Runnable接口的类,主要代码如下:

// com.alibaba.nacos.client.naming.core.HostReactor.UpdateTask
@Override
public void run() {
    long delayTime = -1;

    try {
        ServiceInfo serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));

        if (serviceObj == null) {
            // 从Nacos Server拉取service信息并更新到本地
            updateServiceNow(serviceName, clusters);
            delayTime = DEFAULT_DELAY;
            return;
        }

        if (serviceObj.getLastRefTime() <= lastRefTime) {
            // 从Nacos Server拉取service信息并更新到本地
            updateServiceNow(serviceName, clusters);
            serviceObj = serviceInfoMap.get(ServiceInfo.getKey(serviceName, clusters));
        } else {
            // if serviceName already updated by push, we should not override it
            // since the push data may be different from pull through force push
            refreshOnly(serviceName, clusters);
        }

        lastRefTime = serviceObj.getLastRefTime();

        if (!eventDispatcher.isSubscribed(serviceName, clusters) && !futureMap
            .containsKey(ServiceInfo.getKey(serviceName, clusters))) {
            // abort the update task
            NAMING_LOGGER.info("update task is stopped, service:" + serviceName + ", clusters:" + clusters);
            return;
        }

        delayTime = serviceObj.getCacheMillis();

    } catch (Throwable e) {
        NAMING_LOGGER.warn("[NA] failed to update serviceName: " + serviceName, e);
    } finally {
        if (delayTime > 0) {
            // 再次提交一个Task,把自己提交给executor,让其在delayTime时间之后执行(Nacos默认设置为10s)
            executor.schedule(this, delayTime, TimeUnit.MILLISECONDS);
        }
    }

}

     从源码中可以看出,这段代码相当于定时10s(这个时间是从/nacos/v1/ns/instance/list接口里回传回来的)拉取一次服务,这里有个Nacos Server比较巧妙的设计需要提一下,在updateServiceNow方法中可以看到调用服务端/nacos/v1/ns/instance/list接口的时候传入了一个Udp的端口,这个端口的作用是如果Nacos Server感知到Service的变化,就会把变化信息通知给订阅了这个Service信息的客户端。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值