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

news2024/11/28 8:31:28

前言

本节内容我们主要介绍一下中间键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客户端服务注册的机制:nacos客户端使用springboot的event事件发布机制实现服务的注册。通过NacosAutoServiceRegistration类继承ApplicationListener监听器,当springboot容器启动后,会派发监听事件,触发监听器,从而执行nacos客户端的服务注册功能。

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

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

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

② 调用main方法启动服务

 ③刷新spring容器上下文

 ④发布容器监听器事件

 ⑤调用bind方法绑定事件

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

  • 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<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());
		//通过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<String, String> params, List<String> 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<String, String> params = new HashMap<String, String>(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客户端服务注册原理说明及源码解读到这里就结束了,我们下期见。。。。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/572902.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

golang常见导致panic的场景

1、越界 常见有数组越界和字符串越界 2、空指针引用 直接引用空指针结构体的字段会引发panic&#xff0c;但调用成员方法里如果没引用结构体的字段不会引发panic 3、断言失败 4、map操作错误 map未初始化&#xff0c;可读不可写。 map的value如果是结构体指针&#xf…

G0第21章 :gin框架介绍、RESTful API、Gin渲染

G0第21章 &#xff1a;gin框架 01 内容介绍 web本质 Web是基于HTTP协议进行交互的应用网络Web就是通过使用浏览器/APP访问的各种资源 package mainimport ("fmt""net/http" )func sayHello(w http.ResponseWriter, r *http.Request){_, _ fmt.Fprintln(…

MKS SERVO4257D 闭环步进电机_系列1 产品简介

第1部分 产品概述 1.1 产品介绍 MKS SERVO 28D/35D/42D/57D 系列闭环步进电机是创客基地为满足市场需求而自主研发的一款产品。具备脉冲接口&#xff0c;RS485接口和CAN接口&#xff0c;内置高效FOC矢量算法&#xff0c;采用高精度编码器&#xff0c;通过位置反馈&#xff0c;有…

《深入理解计算机系统》读书笔记1.1-1.5

1.1信息就是位上下文 只由ASCLL字符构成的文件称为文本文件&#xff0c;所有其他文件都称为二进制文件。 系统中的所有的信息都由一串比特表示。区分不同数据对象的唯一方法是读到这些数据对象时的上下文。 1.2程序被其他程序翻译成不同的格式 预编译&#xff0c;编译&#xf…

EasyCVR视频融合平台设备分组共享功能的使用介绍

EasyCVR视频融合平台基于云边端一体化架构&#xff0c;具有强大的数据接入、处理及分发能力&#xff0c;平台支持海量视频汇聚管理&#xff0c;可支持多协议、多类型的设备接入&#xff0c;并能对设备进行分级、分组管理&#xff0c;并支持权限、角色分配&#xff0c;属于功能全…

PFC落石模拟

Landslide/Rockfall simulation 山体滑坡/落石模拟 https://www.youtube.com/watch?vWSa3909qYmI 模拟的目的在于通过导入团块的对象文件产生团块的二进制输出。 具体措施&#xff1a; (i) 使用导入的几何体形成团块模板 (ii) 使用Taghavi(2011)定义的BubblePack算法来生成卵石…

算法|4.归并排序及应用

算法|4.归并排序及应用 1.归并排序算法 题意&#xff1a;归并排序的递归和非递归实现 解题思路&#xff1a; ​ 递归实现&#xff1a; 预处理&#xff1a;数组为空或者长度小于2的直接返回调用子过程子过程终止条件LR分解成[L,mid]&#xff0c;[mid1,R] &#xff0c;子数组…

九章云极DataCanvas公司诚邀您共享AI基础软件前沿技术盛宴

“杭州通用人工智能论坛暨AIIA人工智能产业发展大会”将于2023年5月30日-31日在杭州举办。本次人工智能产业发展大会由中国信息通信研究院、中国人工智能产业发展联盟主办&#xff0c;杭州城西科创大走廊管委会、杭州市经济和信息化局、杭州未来科技城管理委员会、人工智能关键…

企业级信息系统开发——初探JdbcTemplate操作

文章目录 一、创建数据库与表1、创建数据库2、创建用户表3、用户表添加记录 二、打开Spring项目三、添加数据库相关依赖四、创建用户实体类五、创建用户数据访问接口六、创建用户数据访问接口实现类七、创建用户服务类八、创建数据库配置属性文件九、创建Spring配置文件十、创建…

Springboot +spring security,解决跨域问题

一.简介 这篇文章主要是解释什么是跨域&#xff0c;在Spring中如何解决跨域&#xff0c;引入Spring Security后Spring解决跨域的方式失效&#xff0c;Spring Security 如何解决跨域的问题。 二.什么是跨域 跨域的概率&#xff1a; 浏览器不能执行其他网站的脚本&#xff0c…

jsp页面调试

现象: 访问jsp页面, 页面为空, 网络请求显示失败, 控制台打印错误net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 分析: 错误描述&#xff1a;编码模块不完整&#xff0c;返回浏览器的流不完整 可能得原因: 1、网络是否稳定 2、服务器端是否有对响应数据做限制&#xff0c;比如…

【App自动化测试】(十七)遍历测试工具——Android Maxim

目录 1. Android Maxim介绍2. Android Maxim使用方法3.Android Maxim运行命令4.Android Maxim的策略5.实例演示——Windows系统&#xff0c;使用AVD模拟器&#xff0c;系统 Android6.0 1. Android Maxim介绍 Android Maxim是基于遍历规则的高性能Android Monkey&#xff0c;适…

基于SpringBoot+Vue的毕业生信息招聘平台设计与实现

博主介绍&#xff1a; 大家好&#xff0c;我是一名在Java圈混迹十余年的程序员&#xff0c;精通Java编程语言&#xff0c;同时也熟练掌握微信小程序、Python和Android等技术&#xff0c;能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架下…

Elasticsearch常用接口使用说明以及postman调用调试

查看集群状态 接口url&#xff1a;http://xxxx:9200/_cat 查看所有索引 http://xxxx:9200/_cat/indices?v 创建索引 http://xxxx:9200/test-20230526?pretty 返回值 { "acknowledged": true, "shards_acknowledged": true, "index": &quo…

Opencv-C++笔记 (2) : opencv的矩阵操作

文章目录 创建与初始化1.1 数据类型1.2 基本方法1.3 初始化方法 矩阵加减法矩阵乘法矩阵转置矩阵求逆矩阵非零元素个数矩阵均值与标准差矩阵全局极值及位置GEMM 通用矩阵乘法Transform 对数组每一个元素执行矩阵变换MulTransposed 计算数组和数组的转置的乘积Trace 返回矩阵的迹…

WIN10:Cognos10.2_x32安装

一、Cognos BI Server 10.2 32Bit 二、Cognos Transformer 10.2 三、Cognos Framework Manager 10.2 四、环境 1、如果使用Cognos自带的Tomcat web容器&#xff0c;将E:\common\Cognos\c10\webcontent下的所有文件拷贝到E:\common\Cognos\c10\webapps\p2pd 下面.(一般我们就使…

redis高级篇 缓存双写一致性之更新策略

闲聊 缓存通用查询3部曲 redis 中数据&#xff0c;返回redis 中的数据redis 中没有&#xff0c;查询数据库并返回完成第二部的同时&#xff0c;将数据库查询结果写到redis,redis和数据库数据一致. 谈谈双写一致性的理解 1.如果redis 中有数据&#xff1a;需要和数据库中的相…

什么是可视化开发平台?拥有什么优势?

随着科技的进步和发展&#xff0c;可视化开发平台拥有广阔的市场前景&#xff0c;在提升企业办公企业效率、做好数据管理等方面具有自身的特色和优势。在办公自动化发展的年代&#xff0c;低代码开发平台是助力企业实现提质增效办公效率的得力助手&#xff0c;其可视化、易操作…

Windows操作系统存储管理——实存管理和虚存管理

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天总结一下Windows操作系统存储管理——实存管理和虚存管理。 存储器管理的对象是主存&#xff08;内存&#xff09;。重点是要知道实存和虚存的管理&#xff0c;而虚存管理重点是逻辑地址和物理地址间的转…

桥梁结构健康监测解决方案

城市桥梁担负着城市的交通和运输网络的重要角色&#xff0c;是城市生命线的重要组成部分。然而&#xff0c;随着时间的推移和日益增长的负荷&#xff0c;桥梁可能会受到各种因素的损害&#xff0c;如自然灾害、疲劳、腐蚀等。因此&#xff0c;桥梁结构健康监测变得至关重要&…