(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读

前言

本节内容我们主要介绍一下中间键nacos的客户端服务注册原理及其源码解读,便于我们理解nacos作为服务注册中心的具体实现。在springcloud的微服务体系中,nacos客户端的注册是通过使用spring的监听机制ApplicationListener实现的。学习本节内容,需要我们清楚springboot的启动过程。

正文

  • nacos客户端服务注册原理说明

①在注册服务之前,开启一个服务线程,每隔5秒钟向nacos服务器上传一次心跳信息,访问地址为:/instance/beat,如果服务器还没有创建服务实例,那么先创建一个服务实例,并且更新服务最新的心跳时间,如果已经创建实例,则直接返回。
②通过轮询的方式访问nacos的服务地址(服务地址可能有多个),通过访问地址/nacos/v1/ns/instance去注册服务,直到注册成功,失败则抛出异常。
③服务端创建实例完成后,会创建一个定时任务来检查这个实例是否健康,如果心跳机制超过15秒,标记服务为不健康,超过30秒,这个实例会直接被删除。

  • nacos客户端服务注册的机制

①在spring-cloud-alibaba-nacos-discovery-2.2.0.RELEASE.jar包的spring.factories配置文件中,存在nacos客户端注册的Bean(NacosServiceRegistryAutoConfiguration),该Bean中实现了nacos客户端注册的核心功能。

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第1张图片

②nacos客户端服务注册的机制:nacos客户端使用springboot的event事件发布机制实现服务的注册。通过NacosAutoServiceRegistration类继承ApplicationListener监听器,当springboot容器启动后,会派发监听事件,触发监听器,从而执行nacos客户端的服务注册功能。

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第2张图片

③在NacosAutoServiceRegistration类中,通过调用register()方法实现服务的注册

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第3张图片

  • nacos客户端服务注册的整体流程

①在NacosAutoServiceRegistration类的register()方法方法处打断点,启动服务,分析其服务注册原理

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第4张图片

② 调用main方法启动服务

 ③刷新spring容器上下文

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第5张图片

 ④发布容器监听器事件

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第6张图片

 ⑤调用bind方法绑定事件

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第7张图片

⑥调用start方法 ,开始执行注册服务的方法,并发布该事件,完成服务的最终注册

(一)微服务中间键工作原理——nacos客户端服务注册原理说明及源码解读_第8张图片

  • nacos客户端服务注册的核心流程 

①NacosServiceRegistry类的register()方法实现了服务注册的核心流程

public void register(Registration registration) {
	//1.判断初始化服务是否存在
	if (StringUtils.isEmpty(registration.getServiceId())) {
		log.warn("No service to register for nacos client...");
		return;
	}

	String serviceId = registration.getServiceId();
	String group = nacosDiscoveryProperties.getGroup();
	//2.根据nacos客户端配置信息封装服务实例对象
	Instance instance = getNacosInstanceFromRegistration(registration);

	try {
		//3.实现真正的服务注册逻辑
		namingService.registerInstance(serviceId, group, instance);
		log.info("nacos registry, {} {} {}:{} register finished", group, serviceId,
				instance.getIp(), instance.getPort());
	}
	catch (Exception e) {
		4.服务注册失败的处理
		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);
	}
}

②NacosNamingService类中的registerInstance(String serviceName, String groupName, Instance instance)方法实现具体的注册逻辑:封装心跳包数据BeatInfo,使用addBeatInfo

()方法发送心跳包数据,通过registerService()方法实现服务注册

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

	if (instance.isEphemeral()) {
		//1.设置心跳包参数
		BeatInfo beatInfo = new BeatInfo();
		//服务名称
		beatInfo.setServiceName(NamingUtils.getGroupedName(serviceName, groupName));
		//ip地址
		beatInfo.setIp(instance.getIp());
		//端口号
		beatInfo.setPort(instance.getPort());
		//集群名称
		beatInfo.setCluster(instance.getClusterName());
		//服务的访问权重
		beatInfo.setWeight(instance.getWeight());
		//服务的meta信息,包括自定义信息
		beatInfo.setMetadata(instance.getMetadata());
		beatInfo.setScheduled(false);
		//获取心跳包的发送间隔时间
		long instanceInterval = instance.getInstanceHeartBeatInterval();
		//设置心跳包时间,如果没有配置,则获取默认的时间
		beatInfo.setPeriod(instanceInterval == 0 ? DEFAULT_HEART_BEAT_INTERVAL : instanceInterval);
	    //2.发送服务心跳包
		beatReactor.addBeatInfo(NamingUtils.getGroupedName(serviceName, groupName), beatInfo);
	}
	//3.通过http轮询方式注册服务
	serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);
}

③在BeatReactor类中的addBeatInfo方法实现心跳包的发送,通过线程池实现心跳包的发送逻辑,period是发送心跳包的间隔。

public void addBeatInfo(String serviceName, BeatInfo beatInfo) {
	NAMING_LOGGER.info("[BEAT] adding beat: {} to beat map.", beatInfo);
	String key = buildKey(serviceName, beatInfo.getIp(), beatInfo.getPort());
	BeatInfo existBeat = null;
	//fix #1733
	if ((existBeat = dom2Beat.remove(key)) != null) {
		existBeat.setStopped(true);
	}
	dom2Beat.put(key, beatInfo);
	//通过线程池执行心跳包的发送
	executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
	MetricsMonitor.getDom2BeatSizeMonitor().set(dom2Beat.size());
}


④BeatReactor类中的BeatTask任务类的线程run方法执行发送心跳包的逻辑,首先验证心跳包是否是停止状态,如果不是,开始发送心跳包,根据心跳包返回的时间计算下一次心跳包的发送时间nextTime,调用线程池执行下一次心跳包的发送。

public void run() {
	//1.如果心跳包是停止状态,直接退出
	if (beatInfo.isStopped()) {
		return;
	}
	//2.向nacos服务端发送心跳包
	long result = serverProxy.sendBeat(beatInfo);
	//3.获取下一个心跳包的发送时间
	long nextTime = result > 0 ? result : beatInfo.getPeriod();
	//4.发送下一个心跳包
	executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);
}

⑤在NamingProxy类中,调用sendBeat方法,通过RPC远程调用,发送心跳包到/instance/beat地址

public long sendBeat(BeatInfo beatInfo) {
	try {
		if (NAMING_LOGGER.isDebugEnabled()) {
			NAMING_LOGGER.debug("[BEAT] {} sending beat to server: {}", namespaceId, beatInfo.toString());
		}
		//构建心跳包请求参数
		Map params = new HashMap(4);
		params.put("beat", JSON.toJSONString(beatInfo));
		params.put(CommonParams.NAMESPACE_ID, namespaceId);
		params.put(CommonParams.SERVICE_NAME, beatInfo.getServiceName());
		//通过RPC远程调用发送心跳包
		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;
}

⑥调用NamingProxy类中的reqAPI方法执行远程RPC调用,callServer方法通过HttpClient客户端工具发送http请求。心跳包通过轮询的方式发送到每一台nacos服务器上。

public String reqAPI(String api, Map params, List servers, String method) {

	params.put(CommonParams.NAMESPACE_ID, getNamespaceId());

	if (CollectionUtils.isEmpty(servers) && StringUtils.isEmpty(nacosDomain)) {
		throw new IllegalArgumentException("no server available");
	}

	Exception exception = new Exception();
	//验证nacos服务器是否存在,存在则向nacos服务器发送心跳请求
	if (servers != null && !servers.isEmpty()) {
		//产生一个随机数,获取随机的一台nacos服务地址
		Random random = new Random(System.currentTimeMillis());
		int index = random.nextInt(servers.size());
		//轮询向nacos服务器发送心跳请求
		for (int i = 0; i < servers.size(); i++) {
			String server = servers.get(index);
			try {
			    //发送心跳请求,并返回结果
				return callServer(api, params, server, method);
			} catch (NacosException e) {
				exception = e;
				NAMING_LOGGER.error("request {} failed.", server, e);
			} catch (Exception e) {
				exception = e;
				NAMING_LOGGER.error("request {} failed.", server, e);
			}

			index = (index + 1) % servers.size();
		}

		throw new IllegalStateException("failed to req API:" + api + " after all servers(" + servers + ") tried: "
			+ exception.getMessage());
	}

	for (int i = 0; i < UtilAndComs.REQUEST_DOMAIN_RETRY_COUNT; i++) {
		try {
			return callServer(api, params, nacosDomain);
		} catch (Exception e) {
			exception = e;
			NAMING_LOGGER.error("[NA] req api:" + api + " failed, server(" + nacosDomain, e);
		}
	}

	throw new IllegalStateException("failed to req API:/api/" + api + " after all servers(" + servers + ") tried: "
		+ exception.getMessage());

}

⑦NacosNamingService类中的registerInstance方法中代码段serverProxy.registerService(NamingUtils.getGroupedName(serviceName, groupName), groupName, instance);实现服务的注册。具体的注册逻辑如下:封装客户端的基本信息,通过RPC远程调用nacos服务端,注册客户端服务。接口的调用方式和心跳包的方式一致,也是通过轮询调用。

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 params = new HashMap(9);
        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()));
		//内部通过轮询的方式,使用PRC远程服务调用,注册nacos客户端到服务器
        reqAPI(UtilAndComs.NACOS_URL_INSTANCE, params, HttpMethod.POST);

}

结语 

关于nacos客户端服务注册原理说明及源码解读到这里就结束了,我们下期见。。。。。。

你可能感兴趣的:(中间键,微服务,中间件,微服务)