【Nacos】Nacos服务注册与发现 心跳检测机制源码解析
在前两篇文章,介绍了springboot的自动配置原理,而nacos的服务注册就依赖自动配置原理。
Nacos
Nacos核心功能点
服务注册 :Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端口等信息。Nacos Server接收到注册请求后,就会把这些元数据信息存储在一个双层的内存Map中。
服务心跳: 在服务注册后,Nacos Client会维护一个定时心跳来持续通知Nacos Server,说明服务一直处于可用状态,防止被剔除。默认5s发送一次心跳。
服务健康检查: Nacos Server会开启一个定时任务用来检查注册服务实例的健康情况,对于超过15s没有收到客户端心跳的实例会将它 的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复 发送心跳则会重新注册)
服务发现: 服务消费者(Nacos Client)在调用服务提供者的服务时,会发送一个REST请求给Nacos Server,获取上面注册的服务清 单,并且缓存在Nacos Client本地,同时会在Nacos Client本地开启一个定时任务定时拉取服务端最新的注册表信息更新到本地缓存
服务同步: Nacos Server集群之间会互相同步服务实例,用来保证服务信息的一致性。
客户端 服务注册&心跳发送
在客户端中,也就是开发的应用,包含引入有 nacos-discovery
而路径下包含有一个spring.factories
在自动配置的时候,会加载
如下配置类。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\com.alibaba.cloud.nacos.discovery.NacosDiscoveryAutoConfiguration,\com.alibaba.cloud.nacos.ribbon.RibbonNacosAutoConfiguration,\com.alibaba.cloud.nacos.endpoint.NacosDiscoveryEndpointAutoConfiguration,\com.alibaba.cloud.nacos.registry.NacosServiceRegistryAutoConfiguration,\
其中 NacosServiceRegistryAutoConfiguration 中引入了三个类 NacosServiceRegistry
、NacosRegistration
、NacosAutoServiceRegistration
其中 NacosAutoServiceRegistration
继承了 AbstractAutoServiceRegistration
public void onApplicationEvent(WebServerInitializedEvent event) {bind(event);}// 启动this.start();//注册服务register();
组装实例信息,ip 端口 服务权重 集群名字 源信息 以及是否
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.setEnabled(nacosDiscoveryProperties.isInstanceEnabled());instance.setMetadata(registration.getMetadata());instance.setEphemeral(nacosDiscoveryProperties.isEphemeral());return instance;}
// 注册实例namingService.registerInstance(serviceId, group, instance);// 服务名 组名 实例public void registerInstance(String serviceName, String groupName, Instance instance) throws NacosException {// 实例非法校验NamingUtils.checkInstanceIsLegal(instance);// 获取组服务名String groupedServiceName = NamingUtils.getGroupedName(serviceName, groupName);if (instance.isEphemeral()) {// 构建心跳信息BeatInfo beatInfo = beatReactor.buildBeatInfo(groupedServiceName, instance);// 启动一个心跳发送线程beatReactor.addBeatInfo(groupedServiceName, beatInfo);}// 实际就是调用 instance serverProxy.registerService(groupedServiceName, groupName, instance);}// 心跳发送线程executorService.schedule(new BeatTask(beatInfo), beatInfo.getPeriod(), TimeUnit.MILLISECONDS);
发送心跳线程
所谓的客户端心跳,其实就是启动一个线程,然后定时给一个接口发送调用。
class BeatTask implements Runnable {BeatInfo beatInfo;public BeatTask(BeatInfo beatInfo) {this.beatInfo = beatInfo;}@Overridepublic void run() {// 如果停止 直接返回if (beatInfo.isStopped()) {return;}// 获取下次时间long nextTime = beatInfo.getPeriod();// 实际就是调用服务端的一个心跳接口 /instance/beatJsonNode result = serverProxy.sendBeat(beatInfo, BeatReactor.this.lightBeatEnabled);// 如果结束,启动另外一个 开始下次的心跳线程发送executorService.schedule(new BeatTask(beatInfo), nextTime, TimeUnit.MILLISECONDS);}}
nacos 服务注册
对于服务端来说,就是一个API接口
public String register(HttpServletRequest request) throws Exception {// 准备服务实例final Instance instance = parseInstance(request);serviceManager.registerInstance(namespaceId, serviceName, instance);return "ok";}
注册实例其实就是三步、创建服务、获取服务、添加实例
public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {createEmptyService(namespaceId, serviceName, instance.isEphemeral());Service service = getService(namespaceId, serviceName);addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);}
创建服务
public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)throws NacosException {// 命名空间 服务名称Service service = getService(namespaceId, serviceName);if (service == null) {service = new Service();service.setName(serviceName);service.setNamespaceId(namespaceId);service.setGroupName(NamingUtils.getGroupName(serviceName));// now validate the service. if failed, exception will be thrownservice.setLastModifiedMillis(System.currentTimeMillis());service.recalculateChecksum();if (cluster != null) {cluster.setService(service);service.getClusterMap().put(cluster.getName(), cluster);}service.validate();// 这里是重点putServiceAndInit(service);if (!local) {addOrReplaceService(service);}}}private void putServiceAndInit(Service service) throws NacosException {// 添加服务putService(service);// 下面说心跳检测机制service.init();}
这里其实就是底层的注册表的数据结构了,这里使用双检查锁。分别是nameSpace、group、service、实例。
这里简单思考下,为什么要设计这么复杂。一个服务可能对应多个实例。没有问题。一个分组group 可能是在同一个公司有不同的组,比如订单、支付,每个组都有自己的服务。namespace则可以分为dev\test\prod 三个不同的组。
// 注册表 如何提升private final Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap<>();// 这里的机制其实就是 判断当前是否存在该nameSpaceId , 如果不存在的话,则创建一个CSLMpublic void putService(Service service) {if (!serviceMap.containsKey(service.getNamespaceId())) {// 添加锁 locksynchronized (putServiceLock) {if (!serviceMap.containsKey(service.getNamespaceId())) {serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());}}}// 将添加服务添加到nameSpaceId 添加服务serviceMap.get(service.getNamespaceId()).put(service.getName(), service);}
// 添加实例public void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips)throws NacosException {// 构建一个keyString key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);Service service = getService(namespaceId, serviceName);synchronized (service) {List<Instance> instanceList = addIpAddresses(service, ephemeral, ips);Instances instances = new Instances();instances.setInstanceList(instanceList);// 持久化服务consistencyService.put(key, instances);}}// 这里根据判断是否ephemeral 走保存内存还是磁盘持久化 private ConsistencyService mapConsistencyService(String key) {// AP CPreturn KeyBuilder.matchEphemeralKey(key) ? ephemeralConsistencyService : persistentConsistencyService;}
这里其实选择的是 DistroConsistencyServiceImpl
另一个就是 RaftConsistencyServiceImpl
使用raft实现的数据持久化,这里先不介绍。
DistroConsistencyServiceImpl
public void put(String key, Record value) throws NacosException {onPut(key, value);distroProtocol.sync(new DistroKey(key, KeyBuilder.INSTANCE_LIST_KEY_PREFIX), DataOperation.CHANGE,globalConfig.getTaskDispatchPeriod() / 2);}// 这里调用put public void onPut(String key, Record value) {if (KeyBuilder.matchEphemeralInstanceListKey(key)) {Datum<Instances> datum = new Datum<>();datum.value = (Instances) value;datum.key = key;datum.timestamp.incrementAndGet();//将数据保存到内存中dataStore.put(key, datum);}if (!listeners.containsKey(key)) {return;}// 但是这里调用了一个任务notifier.addTask(key, DataOperation.CHANGE);}// 其实就是一个mapprivate Map<String, Datum> dataMap = new ConcurrentHashMap<>(1024);public void put(String key, Datum value) {dataMap.put(key, value);}@PostConstructpublic void init() {// 初始化 构造方法执行的时候 进行处理GlobalExecutor.submitDistroNotifyTask(notifier);}
从源码中可以看到,在类初始化的时候,创建一个任务异步进行执行。 其实就是将当前服务进行异步任务注册,可以提升性能。添加和获取任务。
源码精髓:很多开源框架为了提升操作性能会大量使用这种异步任务及内存队列操作,这些操作本省不需要写入立即返回成功,用这种方式可以提升操作性能很大帮助
public class Notifier implements Runnable {// 保存服务private ConcurrentHashMap<String, String> services = new ConcurrentHashMap<>(10 * 1024);// 阻塞队列 private BlockingQueue<Pair<String, DataOperation>> tasks = new ArrayBlockingQueue<>(1024 * 1024);// 初始化的时候 添加一个任务到阻塞队列中public void addTask(String datumKey, DataOperation action) {if (services.containsKey(datumKey) && action == DataOperation.CHANGE) {return;}if (action == DataOperation.CHANGE) {services.put(datumKey, StringUtils.EMPTY);}tasks.offer(Pair.with(datumKey, action));}@Overridepublic void run() {// 为什么异步设计 : 提升性能// 阻塞队列 ,会线程等待 wait// 并发、反射、网络、IOfor (; ; ) {// 异步处理Pair<String, DataOperation> pair = tasks.take();handle(pair);}}}
心跳检测机制
其实就是服务注册的时候 启动一个线程,然后检查所有实例的心跳检测,对于超过15s没有收到客户端心跳的实例会将它 的healthy属性置为false(客户端服务发现时不会发现),如果某个实例超过30秒没有收到心跳,直接剔除该实例(被剔除的实例如果恢复 发送心跳则会重新注册)
/*** Init service.*/public void init() {// 心跳检查线程HealthCheckReactor.scheduleCheck(clientBeatCheckTask);for (Map.Entry<String, Cluster> entry : clusterMap.entrySet()) {entry.getValue().setService(this);entry.getValue().init();}}// 初始化5S后执行,每5S执行一次public static void scheduleCheck(ClientBeatCheckTask task) {futureMap.putIfAbsent(task.taskKey(), GlobalExecutor.scheduleNamingHealth(task, 5000, 5000, TimeUnit.MILLISECONDS));}// public void run() {List<Instance> instances = service.allIPs(true);for (Instance instance : instances) {// 当前时间 减去 心跳超时时间if (System.currentTimeMillis() - instance.getLastBeat() > instance.getInstanceHeartBeatTimeOut()) {if (instance.isHealthy()) {// 健康状态instance.setHealthy(false);getPushService().serviceChanged(service);ApplicationUtils.publishEvent(new InstanceHeartbeatTimeoutEvent(this, instance));}}}}// then remove obsolete instances:for (Instance instance : instances) {if (System.currentTimeMillis() - instance.getLastBeat() > instance.getIpDeleteTimeout()) {// delete instanceLoggers.SRV_LOG.info("[AUTO-DELETE-IP] service: {}, ip: {}", service.getName(),JacksonUtils.toJson(instance));deleteIp(instance);}}} }
至此,我们就基本上过了一遍,服务的注册 以及心跳检测机制,本篇主要是针对nacos1.4.1 的源码学习,关于后续的服务发现,以及2.X版本的源码 讲解,后续在继续。
相关文章:

【Nacos】Nacos服务注册与发现 心跳检测机制源码解析
在前两篇文章,介绍了springboot的自动配置原理,而nacos的服务注册就依赖自动配置原理。 Nacos Nacos核心功能点 服务注册 :Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务,提供自身的元数据,比如ip地址、端…...
python 66 个冷知识 0720
66个有趣的Python冷知识 一行反转列表 使用切片一行反转列表:reversed_list my_list[::-1] 统计文件单词数量 使用 collections.Counter 统计文件中每个单词的数量:from collections import Counter; with open(file.txt) as f: word_count Counter(f…...

利用PyTorch进行模型量化
利用PyTorch进行模型量化 目录 利用PyTorch进行模型量化 一、模型量化概述 1.为什么需要模型量化? 2.模型量化的挑战 二、使用PyTorch进行模型量化 1.PyTorch的量化优势 2.准备工作 3.选择要量化的模型 4.量化前的准备工作 三、PyTorch的量化工具包 1.介…...

Android 小白菜鸟从入门到精通教程
前言 Android一词最早出现于法国作家利尔亚当(Auguste Villiers de l’Isle-Adam)在1886年发表的科幻小说《未来的夏娃》(L’ve future)中。他将外表像人的机器起名为Android。从初学者的角度出发,通过通俗易懂的语言…...

php相关
php相关 借鉴了小迪安全以及各位大佬的博客,如果一切顺利,会不定期更新。 如果感觉不妥,可以私信删除。 默认有php基础。 文章目录 php相关1. php 缺陷函数1. 与2. MD53. intval()4. preg_match() 2. php特性1. php字符串解析特性2. 杂…...

uniapp上传功能用uni-file-picker实现
文章目录 html代码功能实现css样式代码 html代码 <uni-file-pickerselect"onFileSelected"cancel"onFilePickerCancel"limit"1"class"weightPage-upload-but"file-mediatype"image"></uni-file-picker><imag…...

【PPT笔记】1-3节 | 默认设置/快捷键/合并形状
文章目录 说明笔记1 默认设置1.1 OFFICE版本选择1.1.1 Office某某数字专属系列1.1.2 Office3651.1.3 产品信息怎么看 1.2 默认设置1.2.1 暗夜模式1.2.2 无限撤回1.2.3 自动保存(Office2013版本及以上)1.2.4 图片压缩1.2.5 字体嵌入1.2.6 多格式导出1.2.7…...

Qt中的高分辨率及缩放处理
写在前面 使用Qt开发界面客户端,需要考虑不同分辨率及缩放对UI界面的影响,否则会影响整体的交互使用。 问题 高分辨率/缩放设备上图片/图标模糊 若不考虑高分辨及缩放处理,在高分辨率/缩放设备上,软件中的图片、图标可能会出现…...

电机泵盖机器人打磨去毛刺,选德国进口高精度主轴
机器人打磨去毛刺该如何选择主轴呢?首先我们需要考虑的是工件的材质,电机泵盖通常使用铸铁、不锈钢、合金钢等金属材质,因此这类保持的硬度较高,一般会选择功率、扭矩较大的德国进口高精度主轴Kasite 4060 ER-S。 Kasite 4060 ER-…...
Android init.rc各阶段的定义和功能
Android开机优化系列文档-CSDN博客 Android 14 开机时间优化措施汇总-CSDN博客Android 14 开机时间优化措施-CSDN博客根据systrace报告优化系统时需要关注的指标和优化策略-CSDN博客Android系统上常见的性能优化工具-CSDN博客Android上如何使用perfetto分析systrace-CSDN博客A…...

.net dataexcel 脚本公式 函数源码
示例如: ScriptExec(""sum(1, 2, 3, 4)"") 结果等于10 using Feng.Excel.Builder; using Feng.Excel.Collections; using Feng.Excel.Interfaces; using Feng.Script.CBEexpress; using Feng.Script.Method; using System; using System.Collections.Gen…...

HarmonyOS ArkUi @CustomDialog 和promptAction.openCustomDialog踩坑以及如何选择
CustomDialog 内使用Link,如何正常使用 错误使用方式: 定义一个函数,在函数内使用弹窗,如下面代码showDialog: 这种使用方式,无法在自定义的CustomDialog内使用 Link,进行父子双向绑定&#x…...
Python面试题:详细讲解Python的多线程与多进程编程问题
在 Python 中,多线程和多进程编程是并发编程的两种主要方式,用于提高程序的执行效率和响应性。虽然它们都可以实现并发执行,但它们的工作原理和适用场景有所不同。以下是对 Python 多线程和多进程编程的详细讲解,包括它们的工作原…...

前端Canvas入门——用canvas写五子棋?
前言 五子棋的实现其实不难,因为本身就是一个很小的游戏。 至于画线什么的,其实很简单,都是lineTo(),moveTo()就行了。 难的在于——怎么让棋子落入到指定的格子上,怎么判断连子胜利。 当然啦,这部分是…...
[PaddlePaddle飞桨] PaddleDetection-通用目标检测-小模型部署
PaddleDetection的GitHub项目地址 推荐环境: PaddlePaddle > 2.3.2 OS 64位操作系统 Python 3(3.5.1/3.6/3.7/3.8/3.9/3.10),64位版本 pip/pip3(9.0.1),64位版本 CUDA > 10.2 cuDNN > 7.6pip下载指令: python -m pip i…...

Golang | Leetcode Golang题解之第239题滑动窗口最大值
题目: 题解: func maxSlidingWindow(nums []int, k int) []int {n : len(nums)prefixMax : make([]int, n)suffixMax : make([]int, n)for i, v : range nums {if i%k 0 {prefixMax[i] v} else {prefixMax[i] max(prefixMax[i-1], v)}}for i : n - 1…...
深度解析:在 React 中实现类似 Vue 的 KeepAlive 组件
在前端开发中,Vue 的 keep-alive 组件是一个非常强大的工具,它可以在组件切换时缓存组件的状态,避免重新渲染,从而提升性能。那么,如何在 React 中实现类似的功能呢?本文将带你深入探讨,并通过代…...
2024-7-20 IT新闻
目录 微软全球IT系统故障 中国量子计算产业峰会召开 其他IT相关动态 微软全球IT系统故障 后续处理: 微软和CrowdStrike均迅速响应,发布了相关声明并部署了修复程序。CrowdStrike撤销了有问题的软件更新,以帮助用户恢复系统正常运作。微软也…...

前端组件化开发:以Vue自定义底部操作栏组件为例
摘要 随着前端技术的不断演进,组件化开发逐渐成为提升前端开发效率和代码可维护性的关键手段。本文将通过介绍一款Vue自定义的底部操作栏组件,探讨前端组件化开发的重要性、实践过程及其带来的优势。 一、引言 随着Web应用的日益复杂,传统的…...

11.斑马纹列表 为没有文本的链接设置样式
斑马纹列表 创建一个背景色交替的条纹列表。 使用 :nth-child(odd) 或 :nth-child(even) 伪类选择器,根据元素在一组兄弟元素中的位置,对匹配的元素应用不同的 background-color。 💡 提示:你可以用它对其他 HTML 元素应用不同的样式,如 <div>、<tr>、<p&g…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...

C++:多态机制详解
目录 一. 多态的概念 1.静态多态(编译时多态) 二.动态多态的定义及实现 1.多态的构成条件 2.虚函数 3.虚函数的重写/覆盖 4.虚函数重写的一些其他问题 1).协变 2).析构函数的重写 5.override 和 final关键字 1&#…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...