Nacos服务注册与发现中心之服务注册(客户端源码解读)

Nacos服务注册与发现中心之服务注册

  本文只介绍nacos的服务注册与发现中心功能中的服务提供者,在启动时与nacos服务器的交互,使用的是SpringCloud(Hoxton.SR12)集成Nacos1.0,其他集成方式不作介绍。

入口类及具体方法

服务注册的入口类为:AbstractAutoServiceRegistration.java
此类的类图为:
在这里插入图片描述

  • 此类实现了ApplicationListener接口,用于监听WebServerInitializedEvent事件,该事件是web容器在准备完毕后发送的事件,关于此事件的介绍为:
* Event to be published when the {@link WebServer} is ready. Useful for obtaining the
 * local port of a running server.

大概意思为:该事件是在web容器准备好(此处理解为初始化完毕,具体怎么算初始化完毕还没有找到相关定义,但是起码是已经完成了端口的监听)发布,用于获取运行中的服务端的本地端口

  • AbstractAutoServiceRegistration类监听WebServerInitializedEvent事件的代码为:
public void onApplicationEvent(WebServerInitializedEvent event) {
		bind(event);
}
  • AbstractAutoServiceRegistration.bind(WebServerInitializedEvent event) 方法为:
public void bind(WebServerInitializedEvent event) {
		ApplicationContext context = event.getApplicationContext();
		if (context instanceof ConfigurableWebServerApplicationContext) {
			if ("management".equals(((ConfigurableWebServerApplicationContext) context)
					.getServerNamespace())) {
				return;
			}
		}
		this.port.compareAndSet(0, event.getWebServer().getPort());
		this.start();
}

可以看出这个方法主要是设置了port变量的值为web容器监听的端口的值,而具体的逻辑在start()方法上。

  • AbstractAutoServiceRegistration.start() 方法:
public void start() {

		 ...  //去掉不关心的代码
		 
		if (!this.running.get()) {
			this.context.publishEvent(
					new InstancePreRegisteredEvent(this, getRegistration()));
			register();
			if (shouldRegisterManagement()) {
				registerManagement();
			}
			this.context.publishEvent(
					new InstanceRegisteredEvent<>(this, getConfiguration()));
			this.running.compareAndSet(false, true);
		}

	}

此处我们看到最终会调用register()方法进行服务的注册,而接下来就要说到Nacos提供的NacosAutoServiceRegistration类。

NacosAutoServiceRegistration

先来看看NacosAutoServiceRegistration的类图

在这里插入图片描述
可以看到,此类是继承了AbstractAutoServiceRegistration类。翻看代码发现他重写了AbstractAutoServiceRegistration的register()方法:

@Override
	protected void register() {
		if (!this.registration.getNacosDiscoveryProperties().isRegisterEnabled()) {
			log.debug("Registration disabled.");
			return;
		}
		if (this.registration.getPort() < 0) {
			this.registration.setPort(getPort().get());
		}
		super.register();
}

可以看到,在这里做了一些开关性的判断,设置了使用中的端口号,最终会调用AbstractAutoServiceRegistration的register()方法。

  • AbstractAutoServiceRegisration.register()方法:
	/**
	 * Register the local service with the {@link ServiceRegistry}.
	 */
	protected void register() {
		this.serviceRegistry.register(getRegistration());
	}

该方法会调用serviceRegistry变量的register方法,而按照惯例这个serviceRegistry为Nacos提供的NacosServiceRegistry而getRegistration()方法返回的为NacosServiceRegistration。

  • 我们来看NacosServiceRegistry类的register(Registration registration)方法:
@Override
	public void register(Registration registration) {

		if (StringUtils.isEmpty(registration.getServiceId())) {
			log.warn("No service to register for nacos client...");
			return;
		}

		String serviceId = registration.getServiceId();
		
		// 构建服务注册者的信息
		Instance instance = getNacosInstanceFromRegistration(registration);

		try {
			// 此处为注册逻辑
			namingService.registerInstance(serviceId, instance);
			log.info("nacos registry, {} {}:{} register finished", serviceId,
					instance.getIp(), instance.getPort());
		}
		catch (Exception e) {
			log.error("nacos registry, {} register failed...{},", serviceId,
					registration.toString(), e);
		}
	}

此处会先构造一个Instance类: Instance instance = getNacosInstanceFromRegistration(registration);,这个类包括了需要注册的服务的各种信息,我们看看他的代码:

private Instance getNacosInstanceFromRegistration(Registration registration) {
		Instance instance = new Instance();
		instance.setIp(registration.getHost());
		instance.setPort(registration.getPort());
		instance.setWeight(nacosDiscoveryProperties.getWeight());
		instance.setClusterName(nacosDiscoveryProperties.getClusterName());
		instance.setMetadata(registration.getMetadata());
		return instance;
}

然后我们主要看namingService.registerInstance(serviceId, instance);
这一段代码,按照惯例此处的NamingService为NacosNamingService

  • 我们再看NacosNamingService.registerInstance(String serviceName, Instance instance)方法:
    @Override
    public void registerInstance(String serviceName, Instance instance) throws NacosException {
        registerInstance(serviceName, Constants.DEFAULT_GROUP, instance);
    }

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

        if (instance.isEphemeral()) {
            BeatInfo beatInfo = new BeatInfo();
            beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
            beatInfo.setIp(instance.getIp());
            beatInfo.setPort(instance.getPort());
            beatInfo.setCluster(instance.getClusterName());
            beatInfo.setWeight(instance.getWeight());
            beatInfo.setMetadata(instance.getMetadata());
            beatInfo.setScheduled(false);
            long instanceInterval = instance.getInstanceHeartBeatInterval();
            beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);

            beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
        }

        serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
    }

此处可以看到通过判断:instance.isEphemeral(),然后会进行一个心跳的操作,instance.isEphemeral()表示的是该instance是否为临时的instance,nacos服务端根据此标记会有不同的操作,这个后续再补充,我们只要知道这个值默认为true先。
这里补充一下,当nacos服务提供者注册成功后,会有一个心跳机制来向nacos服务端表明我这个服务的存活。

  • 接下来我们来看一下这段代码:long instanceInterval = instance.getInstanceHeartBeatInterval();,这里表示的是心跳的间隔,那么这个间隔是多少呢?我们来看instance.getInstanceHeartBeatInterval()方法:
public long getInstanceHeartBeatInterval() {
        return getMetaDataByKeyWithDefault(PreservedMetadataKeys.HEART_BEAT_INTERVAL, Constants.DEFAULT_HEART_BEAT_INTERVAL);
}

这段代码的意思为:从配置获取信息,获取不到则取默认值,这里我们一般不进行设置,取默认值:

public static final long DEFAULT_HEART_BEAT_INTERVAL = TimeUnit.SECONDS.toMillis(5);

从此处可以看出,默认的心跳间隔时间为5秒,但是这个间隔是会受到nacos服务端的控制,这个后面补充。

  • 接下来我们来看registerInstance方法的:beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);在这里启动心跳,来看看BeatReactor.addBeatInfo方法:
 public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
        NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
        dom2Beat.put(buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort()), beatInfo);
        // 主要看这一段代码
        executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);
        MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}

我们主要看这行代码:executorService.schedule(new BeatTask(beatInfo), 0, TimeUnit.MILLISECONDS);,在这里构建了一个BeatTask类,并且使用线程池跑了个延迟0秒的逻辑(即立即执行),既然是放在线程池跑的类,那么这个类就是实现了Runnable接口的,我们来看这个类的代码:

class BeatTask implements Runnable {

        BeatInfo beatInfo;

        public BeatTask(BeatInfo beatInfo) {
            this.beatInfo = beatInfo;
        }

        @Override
        public void run() {
            if (beatInfo.isStopped()) {
                return;
            }
            long result = serverProxy.sendBeat(beatInfo);
            long nextTime = result > 0 ? result : beatInfo.getPeriod();
            executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
        }
	}

此处,会立即发送一次心跳,从心跳获取到的结果result,并且result不小于0的情况下,将result作为下一次心跳的时间,所以这里就是上文说的nacos服务端可以影响服务注册者心跳时间的机制了我们进一步看long result = serverProxy.sendBeat(beatInfo);这行代码,serverProxy.sendBeat(beatInfo);的代码为:

public long sendBeat(BeatInfo beatInfo) {
        try {
            if (NAMING_LOGGER.isDebugEnabled()) {
                NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());
            }
            Map<String, String> params = new HashMap<String, String>(4);
            params.put("beat", JSON.toJSONString(beatInfo));
            params.put(CommonParams.NAMESPACE_ID, namespaceId);
            params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
            String result = reqAPI(UtilAndComs.NACOS_URL_BASE + "/instance/beat", params, HttpMethod.PUT);
            JSONObject jsonObject = JSON.parseObject(result);

            if (jsonObject != null) {
                return jsonObject.getLong("clientBeatInterval");
            }
        } catch (Exception e) {
            NAMING_LOGGER.error("[CLIENT-BEAT] failed to send beat: " + JSON.toJSONString(beatInfo), e);
        }
        return 0L;
  }

可以看到这里是发送了一个Http的请求,Http请求的uri为:/nacos/v1/ns/instance/beat;接下来,我们去到nacos服务端,查看处理该请求的代码:

@CanDistro
    @RequestMapping(value = "/beat", method = RequestMethod.PUT)
    public JSONObject beat(HttpServletRequest request) throws Exception {

        JSONObject result = new JSONObject();

        result.put("clientBeatInterval", switchDomain.getClientBeatInterval());

       ....

        return result;
    }

由于本文讲解的是Nacos服务注册端的流程,关于Nacos服务端的代码暂时不做详细讲解,因此此处我们把无关代码删除,主要看的是会影响服务注册端下一次心跳时间的switchDomain.getClientBeatInterval(),点击查看该行代码的详情,可以看到它的取值为:

private long clientBeatInterval = TimeUnit.SECONDS.toMillis(5);

可以看到返回的时间也是5秒~

  • 看完心跳机制后,我们回到NacosNamingService.registerInstance()方法,我们来看该方法的最后一行:serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);,该行代码会进行最后的服务注册调用
    来看具体代码:
 public void registerService(String serviceName, String groupName, Instance instance) throws NacosException {

        NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance: {}",
            namespaceId, serviceName, instance);

        final Map<String, String> params = new HashMap<String, String>(8);
        params.put(CommonParams.NAMESPACE_ID, namespaceId);
        params.put(CommonParams.SERVICE_NAME, serviceName);
        params.put(CommonParams.GROUP_NAME, groupName);
        params.put(CommonParams.CLUSTER_NAME, instance.getClusterName());
        params.put("ip", instance.getIp());
        params.put("port", String.valueOf(instance.getPort()));
        params.put("weight", String.valueOf(instance.getWeight()));
        params.put("enable", String.valueOf(instance.isEnabled()));
        params.put("healthy", String.valueOf(instance.isHealthy()));
        params.put("ephemeral", String.valueOf(instance.isEphemeral()));
        params.put("metadata", JSON.toJSONString(instance.getMetadata()));

        reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);

    }

可以看到这里组装了一个Map,往里面塞了服务的ip,port等信息,最终调用一个HTTP请求,向Nacos服务端进行注册
至于Nacos服务端的代码我们后续篇章补充

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值