六、Nacos源码系列:Nacos健康检查
目录
一、简介
二、健康检查流程
2.1、健康检查
2.2、客户端释放连接事件
2.3、客户端断开连接事件
2.4、小结
2.5、总结图
三、服务剔除
一、简介
Nacos作为注册中心不止提供了服务注册和服务发现的功能,还提供了服务可用性检测的功能,在Nacos 1.x的版本中,临时实例走的是distro协议,客户端向注册中心发送心跳来维持自身的健康(healthy)状态,持久实例则走的是Raft协议存储。
主要有两种检测机制:
- 1)、客户端主动上报机制:主动向Nacos服务端发送心跳,告诉Nacos服务端是否自己还活着。
- 2)、服务器端主动下探机制:Nacos服务端主动向每个Nacos客户端发起探活,如果探活成功,说明客户端还活着,如果探活失败,则服务端将会剔除客户端。
对于Nacos健康检测机制,主要是有两种服务实例类型:
- 临时实例:客户端主动上报机制
- 持久实例:服务端主动下探机制
在1.x版本中,临时实例每隔5秒会主动上报自己的健康状态,发送心跳,如果发送心跳的间隔时间超过15秒,Nacos服务器端会将服务标记为亚健康状态,如果超过30S没有发送心跳,那么服务实例会被从服务列表中剔除。
在Nacos 2.x版本以后,持久实例不变,还是通过服务端主动下探机制,但是临时实例变成通过长连接来判断实例是否健康。
- 长连接: 一个连接上可以连续发送多数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包,在Nacos 2.x之后,使用Grpc协议代替了http协议,长连接会保持客户端和服务端发送的状态,在源码中ConnectionManager 管理所有客户端的长连接。ConnectionManager每3秒检测所有超过20S内没有发生过通讯的客户端,向客户端发起ClientDetectionRequest探测请求,如果客户端在指定时间内成功响应,则检测通过,否则执行unregister方法移除Connection。
如果客户端持续和服务端进行通讯,服务端是不需要主动下探的,只有当客户端没有一直和服务端通信的时候,服务端才会主动下探操作。
二、健康检查流程
2.1、健康检查
在Nacos2.0之后,使用Grpc协议代替了http协议,Grpc是一个长连接的,长连接会保持客户端和服务端发送的状态,在Nacos源码中ConnectionManager 管理所有客户端的长连接。
ConnectionManager每隔3秒检测所有超过20S内没有发生过通讯的客户端,向客户端发起ClientDetectionRequest探测请求,如果客户端在指定时间内成功响应,则检测通过,否则执行unregister方法移除Connection。
我们从ConnectionManager类的源码开始分析:
ConnectionManager内部有一个map用于存放当前所有客户端的长连接信息:
/*** 连接集合* key: ConnectionId* value: Connection*/
Map<String, Connection> connections = new ConcurrentHashMap<>();
当我们启动一个nacos客户端的时候,就会往connections里面保存这个连接信息。
在ConnectionManager类内部,我们发现了存在一个使用@PostConstruct注解标识的方法,说明构造方法执行后就会触发执行start():
/*** 应用启动的时候执行,首次执行延迟1s,运行中周期为3秒执行一次* Start Task:Expel the connection which active Time expire.*/
@PostConstruct
public void start() {// 初始化runtimeConnectionEjector为NacosRuntimeConnectionEjectorinitConnectionEjector();// 开始执行不健康连接的剔除任务RpcScheduledExecutor.COMMON_SERVER_EXECUTOR.scheduleWithFixedDelay(() -> {// 调用com.alibaba.nacos.core.remote.NacosRuntimeConnectionEjector.doEjectruntimeConnectionEjector.doEject();}, 1000L, 3000L, TimeUnit.MILLISECONDS);}
可以看到,start()方法创建了一个定时任务,首次执行延迟1s,后面每隔3s执行一次,实际上就是执行不健康连接的剔除任务。
我们查看runtimeConnectionEjector.doEject()方法:
public void doEject() {try {Loggers.CONNECTION.info("Connection check task start");Map<String, Connection> connections = connectionManager.connections;int totalCount = connections.size();MetricsMonitor.getLongConnectionMonitor().set(totalCount);int currentSdkClientCount = connectionManager.currentSdkClientCount();Loggers.CONNECTION.info("Long connection metrics detail ,Total count ={}, sdkCount={},clusterCount={}",totalCount, currentSdkClientCount, (totalCount - currentSdkClientCount));// 超时的连接集合Set<String> outDatedConnections = new HashSet<>();long now = System.currentTimeMillis();for (Map.Entry<String, Connection> entry : connections.entrySet()) {Connection client = entry.getValue();// client.getMetaInfo().getLastActiveTime(): 客户端最近一次活跃时间// 客户端最近一次活跃时间距离当前时间超过20s的客户端,服务端会发起请求探活,如果失败或者超过指定时间内未响应则剔除服务。if (now - client.getMetaInfo().getLastActiveTime() >= KEEP_ALIVE_TIME) {outDatedConnections.add(client.getMetaInfo().getConnectionId());}}// check out date connectionLoggers.CONNECTION.info("Out dated connection ,size={}", outDatedConnections.size());if (CollectionUtils.isNotEmpty(outDatedConnections)) {// 记录成功探活的客户端连接的集合Set<String> successConnections = new HashSet<>();final CountDownLatch latch = new CountDownLatch(outDatedConnections.size());for (String outDateConnectionId : outDatedConnections) {try {Connection connection = connectionManager.getConnection(outDateConnectionId);if (connection != null) {// 创建一个客户端检测请求ClientDetectionRequest clientDetectionRequest = new ClientDetectionRequest();connection.asyncRequest(clientDetectionRequest, new RequestCallBack() {@Overridepublic Executor getExecutor() {return null;}@Overridepublic long getTimeout() {return 5000L;}@Overridepublic void onResponse(Response response) {latch.countDown();if (response != null && response.isSuccess()) {// 探活成功,更新最近活跃时间,然后加入到探活成功的集合中connection.freshActiveTime();successConnections.add(outDateConnectionId);}}@Overridepublic void onException(Throwable e) {latch.countDown();}});Loggers.CONNECTION.info("[{}]send connection active request ", outDateConnectionId);} else {latch.countDown();}} catch (ConnectionAlreadyClosedException e) {latch.countDown();} catch (Exception e) {Loggers.CONNECTION.error("[{}]Error occurs when check client active detection ,error={}",outDateConnectionId, e);latch.countDown();}}latch.await(5000L, TimeUnit.MILLISECONDS);Loggers.CONNECTION.info("Out dated connection check successCount={}", successConnections.size());for (String outDateConnectionId : outDatedConnections) {// 不在探活成功的集合,说明探活失败,执行注销连接操作if (!successConnections.contains(outDateConnectionId)) {Loggers.CONNECTION.info("[{}]Unregister Out dated connection....", outDateConnectionId);// 注销过期连接connectionManager.unregister(outDateConnectionId);}}}Loggers.CONNECTION.info("Connection check task end");} catch (Throwable e) {Loggers.CONNECTION.error("Error occurs during connection check... ", e);}
}
如上代码,比较容易看懂,总体逻辑就是:
- 1、拿到当前所有的连接;
- 2、循环判断每个连接,判断下最近一次活跃时间距离当前时间,是不是超过20s,如果超过20s,将连接ID加入到一个过期连接集合中放着;
- 3、循环过期连接集合中的每个连接,Nacos服务端主动发起一个探活,如果探活成功,将连接ID加入到探活成功的集合中;
- 4、比较过期连接集合、探活成功集合,两者的差集,就是真正探活失败,需要剔除的那些连接,将会执行注销连接操作;
针对探活失败的那些连接,需要执行注销连接,具体代码如下:
// 注销过期连接
connectionManager.unregister(outDateConnectionId);public synchronized void unregister(String connectionId) {// 根据connectionId从连接集合中移除这个连接// Map<String, Connection> connections = new ConcurrentHashMap<>();Connection remove = this.connections.remove(connectionId);// 移除成功if (remove != null) {String clientIp = remove.getMetaInfo().clientIp;AtomicInteger atomicInteger = connectionForClientIp.get(clientIp);if (atomicInteger != null) {int count = atomicInteger.decrementAndGet();if (count <= 0) {connectionForClientIp.remove(clientIp);}}remove.close();LOGGER.info("[{}]Connection unregistered successfully. ", connectionId);// 通知其它客户端,这个连接断开了clientConnectionEventListenerRegistry.notifyClientDisConnected(remove);}
}
unregister()方法首先根据connectionId从连接集合中移除这个连接,然后通知其它客户端,这个连接断开了。
继续跟踪clientConnectionEventListenerRegistry.notifyClientDisConnected(remove)的源码:
public void notifyClientDisConnected(final Connection connection) {for (ClientConnectionEventListener clientConnectionEventListener : clientConnectionEventListeners) {try {clientConnectionEventListener.clientDisConnected(connection);} catch (Throwable throwable) {Loggers.REMOTE.info("[NotifyClientDisConnected] failed for listener {}",clientConnectionEventListener.getName(), throwable);}}}
ClientConnectionEventListener其实就是客户端连接事件的一些监听器,看下其类图:

ClientConnectionEventListener主要有三个子类,这里我们关注ConnectionBasedClientManager。
我们查看ConnectionBasedClientManager#clientDisConnected()的源码:
public void clientDisConnected(Connection connect) {clientDisconnected(connect.getMetaInfo().getConnectionId());
}public boolean clientDisconnected(String clientId) {Loggers.SRV_LOG.info("Client connection {} disconnect, remove instances and subscribers", clientId);ConnectionBasedClient client = clients.remove(clientId);if (null == client) {return true;}client.release();boolean isResponsible = isResponsibleClient(client);// 发布客户端释放连接事件/*** 具体处理是在:{@link com.alibaba.nacos.naming.core.v2.index.ClientServiceIndexesManager.onEvent}* 主要做了下面几个事情:* 1、从订阅者列表中移除所有服务对这个客户端的引用* 2、从发布者列表中移除所有服务对这个客户端的引用*/NotifyCenter.publishEvent(new ClientOperationEvent.ClientReleaseEvent(client, isResponsible));// 发布客户端断开连接事件/*** 具体处理是在:{@link com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager.onEvent}* 主要做了下面几个事情:* 1、将服务实例元数据添加到过期集合中*/NotifyCenter.publishEvent(new ClientEvent.ClientDisconnectEvent(client, isResponsible));return true;
}
可以看到,关键的逻辑就是发布了两个事件:客户端释放连接事件、客户端断开连接事件。
2.2、客户端释放连接事件
具体处理是在com.alibaba.nacos.naming.core.v2.index.ClientServiceIndexesManager.onEvent:
public void onEvent(Event event) {if (event instanceof ClientOperationEvent.ClientReleaseEvent) {// 处理客户端释放连接事件handleClientDisconnect((ClientOperationEvent.ClientReleaseEvent) event);} else if (event instanceof ClientOperationEvent) {// 处理排除ClientReleaseEvent后的其它客户端操作事件handleClientOperation((ClientOperationEvent) event);}
}private void handleClientDisconnect(ClientOperationEvent.ClientReleaseEvent event) {Client client = event.getClient();for (Service each : client.getAllSubscribeService()) {// 从订阅者列表中移除所有服务对这个客户端的引用// private final ConcurrentMap<Service, Set<String>> subscriberIndexes = new ConcurrentHashMap<>();// key: Service value: 客户端ID集合removeSubscriberIndexes(each, client.getClientId());}DeregisterInstanceReason reason = event.isNative()? DeregisterInstanceReason.NATIVE_DISCONNECTED : DeregisterInstanceReason.SYNCED_DISCONNECTED;long currentTimeMillis = System.currentTimeMillis();for (Service each : client.getAllPublishedService()) {// 从发布者列表中移除所有服务对这个客户端的引用removePublisherIndexes(each, client.getClientId());InstancePublishInfo instance = client.getInstancePublishInfo(each);NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(currentTimeMillis,"", false, reason, each.getNamespace(), each.getGroup(), each.getName(),instance.getIp(), instance.getPort()));}
}
主要做了两件事情:
- 1、从订阅者列表中移除所有服务对这个客户端的引用;
- 2、从发布者列表中移除所有服务对这个客户端的引用;
2.3、客户端断开连接事件
具体处理是在com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager.onEvent:
public void onEvent(Event event) {if (event instanceof MetadataEvent.InstanceMetadataEvent) {// 处理实例元数据事件handleInstanceMetadataEvent((MetadataEvent.InstanceMetadataEvent) event);} else if (event instanceof MetadataEvent.ServiceMetadataEvent) {// 处理服务元数据事件handleServiceMetadataEvent((MetadataEvent.ServiceMetadataEvent) event);} else {// 处理客户端断开连接事件handleClientDisconnectEvent((ClientEvent.ClientDisconnectEvent) event);}
}private void handleClientDisconnectEvent(ClientEvent.ClientDisconnectEvent event) {for (Service each : event.getClient().getAllPublishedService()) {String metadataId = event.getClient().getInstancePublishInfo(each).getMetadataId();if (containInstanceMetadata(each, metadataId)) {// 实例已过期,将实例元数据添加到过期集合中updateExpiredInfo(true, ExpiredMetadataInfo.newExpiredInstanceMetadata(each, metadataId));}}
}
主要做了一件事情:
- 1、判断实例元数据是否存在,存在的话,将它标志已过期,添加到过期集合中;
2.4、小结
以上就是Nacos服务端健康检查的整体流程,总结一下:
- 1、入口在ConnectionManager.start()方法,该方法有注解@PostConstruct;
- 2、start()方法启动了一个定时任务,3s定时调度一次(每次结束后延迟3s);
- 3、判断哪些客户端最近一次活跃时间已经超过20s,如果超过,判断为连接过期,并把过期的client存放到过期集合中;
- 4、Nacos服务端会对过期的client进行一次探活操作,如果失败或者指定时间内还没有响应,直接剔除该客户端;
- 5、剔除客户端的过程,发布了两个事件:客户端释放连接事件、客户端断开连接事件。拿到订阅者列表、发布者列表,移除掉所有服务对这个client的引用,保证服务不会引用到过期的client;
2.5、总结图

三、服务剔除
前面健康检查我们主要分析了ConnectionBasedClientManager这个类,细心的朋友可能会发现ConnectionBasedClientManager的构造方法其实启动了一个定时任务,如下所示:
public ConnectionBasedClientManager() {// 启动了一个定时任务,无延迟,每隔5s执行一次// 具体就是执行ExpiredClientCleaner.run()方法GlobalExecutor.scheduleExpiredClientCleaner(new ExpiredClientCleaner(this), 0, Constants.DEFAULT_HEART_BEAT_INTERVAL,TimeUnit.MILLISECONDS);
}
这个定时任务,每隔5s就会执行一次,具体就是执行ExpiredClientCleaner.run()方法:
private static class ExpiredClientCleaner implements Runnable {private final ConnectionBasedClientManager clientManager;public ExpiredClientCleaner(ConnectionBasedClientManager clientManager) {this.clientManager = clientManager;}@Overridepublic void run() {long currentTime = System.currentTimeMillis();for (String each : clientManager.allClientId()) {// 判断客户端是否超时ConnectionBasedClient client = (ConnectionBasedClient) clientManager.getClient(each);if (null != client && client.isExpire(currentTime)) {// 超时连接处理clientManager.clientDisconnected(each);}}}
}
上面这个clientManager.clientDisconnected(each)超时连接处理,我们在前面已经分析过了,这里不再分析,关键的逻辑就是发布了两个事件:客户端释放连接事件、客户端断开连接事件。
相关文章:
六、Nacos源码系列:Nacos健康检查
目录 一、简介 二、健康检查流程 2.1、健康检查 2.2、客户端释放连接事件 2.3、客户端断开连接事件 2.4、小结 2.5、总结图 三、服务剔除 一、简介 Nacos作为注册中心不止提供了服务注册和服务发现的功能,还提供了服务可用性检测的功能,在Nacos…...
2024美赛C题思路/代码:网球中的动量
美赛直播b站,提前关注:川川菜鸟 美赛辅导预定:美赛服务 去年美赛C题:2023美赛C题 题目翻译 背景 在2023年温布尔登男子单打决赛中,20岁的西班牙新星阿尔卡拉兹击败了36岁的诺瓦克德约科维奇。这是德约科维奇自201…...
ConcurrentHashMap原理详解(太细了)
一、什么是ConcurrentHashMap ConcurrentHashMap和HashMap一样,是一个存放键值对的容器。使用hash算法来获取值的地址,因此时间复杂度是O(1)。查询非常快。 同时,ConcurrentHashMap是线程安全的HashMap。专门用于多线程环境。 二、Concurre…...
EasyExcel根据对应的实体类模板完成多个sheet的写入与读取
1.展示模板一的实体类 import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.write.style.ColumnWidth; import com.alibaba.excel.annotation.write.style.ContentRowHeight; import com.alibaba.excel.annotation.write.style.HeadRowH…...
在企业数字化转型过程中,IT运维发挥着怎样的价值?
IT运维软件在企业数字化转型中发挥着重要的价值。从效率、稳定性、安全性和资源利用率以及数据分析决策支持都有巨大的提升。 提高效率 利用自动化巡检功能,实时或定时进行系统巡检,减少人力巡检的繁琐和低效,避免手动操作的失误,…...
01-工厂模式 ( Factory Pattern )
工厂模式 Factory Pattern 摘要实现范例 工厂模式(Factory Pattern)提供了一种创建对象的最佳方式 工厂模式在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象 工厂模式属于创建型模式 摘要 1. 意图 …...
【LeetCode】每日一题 2024_2_2 石子游戏 VI(排序、贪心)
文章目录 LeetCode?启动!!!题目:石子游戏 VI题目描述代码与解题思路 LeetCode?启动!!! 题目:石子游戏 VI 题目链接:1686. 石子游戏 VI 题目描述…...
一站式在线协作开源办公软件ONLYOFFICE,协作更安全更便捷
1、ONLYOFFICE是什么? ONLYOFFICE是一款功能强大的在线协作办公软件,可以创建编辑Word文档、Excel电子表格,PowerPoint(PPT)演示文稿、Forms表单等多种文件。ONLYOFFICE支持多个平台,无论使用的是 Windows、…...
Java进击框架:Spring-综合(十)
Java进击框架:Spring-综合(十) 前言Rest ClientsWebClientRestTemplateHTTP接口 JMS (Java消息服务)使用Spring JMS发送消息接收消息注释驱动的侦听器端点 JMXEmail任务执行和调度Spring TaskExecutor 抽象Spring TaskScheduler 抽象支持调度…...
2024年第九届信号与图像处理国际会议(ICSIP 2024)
2024第九届信号与图像处理国际会议(ICSIP 2024)将于2024年7月12-14日在中国南京召开。ICSIP每年召开一次,在过去的七年中吸引了1200多名与会者,是展示信号和图像处理领域最新进展的领先国际会议之一。本次将汇集来自亚太国家、北美…...
webassembly003 MINISIT mnist/convert-h5-to-ggml.py
数据结构 # Convert MNIS h5 transformer model to ggml format # # Load the (state_dict) saved model using PyTorch # Iterate over all variables and write them to a binary file. # # For each variable, write the following: # - Number of dimensions (int) # …...
fetch和axios的区别
概念不同 Fetch是一种新的获取资源的接口方式,可以直接使用Axios是一个基于XMLHttpRequest封装的工具包,需要引入才可以使用 传递数据的方式不同 Fetch则是需要放在body属性中,以字符串的方式进行传递Axios是放到data属性里,以对象…...
【unity小技巧】FPS简单的射击换挡瞄准动画控制
文章目录 射击动画控制换弹动画瞄准动画完结 射击动画控制 换弹动画 调用 瞄准动画 问题:瞄准时,但是动画会卡住,不会播放瞄准的待机动画 修改 调用 动画如果太快可以去修改播放速度 播放速度变慢了,可能导致切换待机动画也…...
如何获取时间戳
在JavaScript中,你可以使用Date对象来获取时间戳。以下是一个例子: javascriptvar timestamp new Date().getTime(); console.log(timestamp); 在这个例子中,new Date()创建了一个新的日期对象,.getTime()方法则返回自1970年1月…...
VSCode 设置代理
Open Visual Studio Code, click the settings icon in the lower left corner, and click Settings....
保姆级教程: 零门槛制作AI微信红包封面之入门篇
写在前面 本文旨在低门槛制作微信红包教程,人人均可上手! 操作步骤 AI红包制作平台: https://cover.fdfs.site 第一步: 先登录 alt text 可以使用谷歌,github直接登录,也可以用自己的邮箱注册 第二步: 设置自己的apiKey API-Key可以从平台 ht…...
Redis核心技术与实战【学习笔记】 - 17.Redis 缓存异常:缓存雪崩、击穿、穿透
概述 Redis 的缓存异常问题,除了数据不一致问题外,还会面临其他三个问题,分别是缓存雪崩、缓存击穿、缓存穿透。这三个问题,一旦发生,会导致大量的请求积压到数据库。若并发量很大,就会导致数据库宕机或故…...
Leetcode—2670. 找出不同元素数目差数组【简单】
2024每日刷题(一零七) Leetcode—2670. 找出不同元素数目差数组 哈希表实现代码 class Solution { public:vector<int> distinctDifferenceArray(vector<int>& nums) {unordered_set<int> s;int n nums.size();vector<int&g…...
App ICP备案获取iOS和Android的公钥和证书指纹
依照《工业和信息化部关于开展移动互联网应用程序备案工作的通知》,向iOS和安卓平台提交App时需要先提交ICP备案信息。 iOS平台: 1、下载appuploader工具:Appuploader home -- A tool improve ios develop efficiency such as submit ipa to…...
猿创征文 | 项目整合KafkaStream实现文章热度实时计算
个人简介: > 📦个人主页:赵四司机 > 🏆学习方向:JAVA后端开发 > ⏰往期文章:SpringBoot项目整合微信支付 > 🔔博主推荐网站:牛客网 刷题|面试|找工作神器 > &#…...
Google三星AI眼镜来了,开发者该关注什么
AI 眼镜又回来了,但这次不只是换个硬件外壳AI 眼镜这个话题,最近又被推到了台前。Google 在 I/O 2026 展示了基于 Android XR 的智能眼镜方向,并把三星、Gentle Monster、Warby Parker 等合作方一起摆上台面。按照目前公布的信息,…...
CANN 算子融合技术:Conv-BN-ReLU 与 MatMul-LayerNorm 等融合模式深度解析
CANN 算子融合技术:Conv-BN-ReLU 与 MatMul-LayerNorm 等融合模式深度解析算子融合是提升性能的关键手段。本文深入讲解昇腾支持的算子融合技术、实现原理和应用实践。一、融合技术概述 1.1 为什么要融合 原始: Conv → BN → ReLU → Conv → BN → ReLU融合前内存…...
ubuntu24 主题经验
ubuntu24 使用起来非常令我兴奋,源于他的成熟度、超快的网速。一、主题来源网站 https://www.gnome-look.org/s/Gnome/browse?cat135&page11&ordrating二、主题安装文件夹 & 设置创建文件夹 ~/.themes 下载的主题直接扔到这个文件夹。好处有…...
第2章:文档加载与智能分块——RAG的第一步
本章你将收获:支持PDF(含表格)、Word、Markdown、网页、CSV等10+格式的完整加载代码;五种分块策略的深度对比(固定大小、递归字符、语义、文档结构、按标题);元数据保留与增强的工程方法;处理100页混合格式技术手册的完整实战;以及分块参数调优的最佳实践。 📌 本章…...
AI周报如何成为技术决策的精准导航仪
1. 项目概述:一份真正值得花时间读的AI周报,到底长什么样?我做技术类内容整理和分发已经十一年了,从2014年最早在知乎写“每周机器学习论文速览”,到后来运营三个垂直技术社群、给二十多家企业做AI落地咨询,…...
2026年长沙美缝施工团队哪家强?专业之选等你来揭秘!
在长沙高端住宅、别墅装修领域,美缝施工是提升家居质感的关键环节。面对众多美缝施工团队,业主们常常不知如何选择。今天,我们就来揭秘2026年长沙值得信赖的美缝施工团队——长沙匠心徐师傅美缝团队,看看它有哪些独特的优势。一、…...
农业信息智能化种植系统(10079)
有需要的同学,源代码和配套文档领取,加文章最下方的名片哦 一、项目演示 项目演示视频 二、资料介绍 完整源代码(前后端源代码SQL脚本)配套文档(LWPPT开题报告/任务书)远程调试控屏包运行一键启动项目&…...
Unity哥特UI资源包:SDF字体与Shader Graph工程化实践
1. 为什么哥特UI在游戏开发中长期被低估,又为何现在必须认真对待“哥特UI”这个词,很多Unity开发者第一反应是:不就是黑底、尖角、浮雕字、带玫瑰纹样的按钮吗?配个暗红渐变完事。我2019年接手一个中世纪黑暗奇幻RPG时也这么想——…...
第九届蓝桥杯国赛b组--备战国赛版h
第一题:0换零钞 - 蓝桥云课 模拟 #include <bits/stdc.h> using namespace std; int main() {int a,b,c0;for(a1;a<200;a)//一元钞票{for(b1;b<100;b)//两元钞票{for(c1;c<40;c)//五元钞票{if(ba*10&&(ab*2c*5)200){cout<<abc<&l…...
LLM 认知框架:揭秘时间序列与空间结构,洞悉 AI 未来!
一、简明摘要 本文是一篇概念说明与方法论文章,核心问题是:LLM 到底是什么,它与 AI、AGI、Agent、Skill 有什么关系。全文先区分 AI、AGI、LLM 三个层级,再说明 LLM 的现实形态已经从“文本生成模型”扩展为“模型、上下文、外部知…...
