Nacos 服务发现(订阅)源码分析(服务端)

前言:

前文我们分析了 Nacos 服务发现(订阅)的流程,从 Nacos Client 端的源码分析了服务发现的过程,服务发现最终还是要调用 Nacos Server 端来获取服务信息,缓存到客户端本地,并且会定时向 Nacos Server 端发送请求,获取服务信息,本篇我们从 Nacos Server 来分析一下服务订阅源码。

Nacos 系列文章传送门:

Nacos 初步认识和 Nacos 部署细节

Nacos 配置管理模型 – 命名空间(Namespace)、配置分组(Group)和配置集ID(Data ID)

Nacos 注册中心和配置中心【实战】

服务启动何时触发 Nacos 的注册流程?

Nacos Client 端服务注册流程源码分析

Nacos Server 端服务注册流程源码分析

Nacos 服务发现(订阅)源码分析(客户端)

InstanceController#list 方法源码解析

前文我们分析到服务的发现(订阅)最终会调用 Nacos Server 端的接口,而这个接口就在 InstanceController 中,根据接口路径我们找到了对应的方法也就是 InstanceController#list 方法,源码解析如下:

//com.alibaba.nacos.naming.controllers.InstanceController#list
@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public ObjectNode list(HttpServletRequest request) throws Exception {//获取 namespaceIdString namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);//获取 serviceNameString serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME);//检查格式NamingUtils.checkServiceNameFormat(serviceName);//agent  java、c、c++、go、nginx、dnsfString agent = WebUtils.getUserAgent(request);//获取集群信息String clusters = WebUtils.optional(request, "clusters", StringUtils.EMPTY);String clientIP = WebUtils.optional(request, "clientIP", StringUtils.EMPTY);//获取 udp  端口int udpPort = Integer.parseInt(WebUtils.optional(request, "udpPort", "0"));//获取环境信息String env = WebUtils.optional(request, "env", StringUtils.EMPTY);boolean isCheck = Boolean.parseBoolean(WebUtils.optional(request, "isCheck", "false"));String app = WebUtils.optional(request, "app", StringUtils.EMPTY);String tenant = WebUtils.optional(request, "tid", StringUtils.EMPTY);boolean healthyOnly = Boolean.parseBoolean(WebUtils.optional(request, "healthyOnly", "false"));//获取服务列表return doSrvIpxt(namespaceId, serviceName, agent, clusters, clientIP, udpPort, env, isCheck, app, tenant,healthyOnly);
}

InstanceController#list 方法本身没有什么难懂逻辑,只是从 request 中获取一些属性后,继续调用了 InstanceController#doSrvIpxt 方法,我们接着往下看。

InstanceController#doSrvIpxt 方法源码解析

InstanceController#doSrvIpxt 方法的源码比较多,大概拆分一下重要步骤,做了如下事情。

  1. 根据 namespaceId, 和 serviceName 获取服务信息。
  2. 判断是有有客户端订阅了服务,如果由客户端订阅了服务,则加入到 UDP 推送列表中,也就是之前我们分析过的 Nacos Server 是如何通知 Nacos Client 服务下线。
  3. 阀值判断,通过各种判断规则得到服务列表(判断详情请看源码分析)。
  4. 封装结果集返回。
//com.alibaba.nacos.naming.controllers.InstanceController#doSrvIpxt
public ObjectNode doSrvIpxt(String namespaceId, String serviceName, String agent, String clusters, String clientIP,int udpPort, String env, boolean isCheck, String app, String tid, boolean healthyOnly) throws Exception {//创建客户端对象ClientInfo clientInfo = new ClientInfo(agent);//创建 ObjectNodeObjectNode result = JacksonUtils.createEmptyJsonNode();//根据  namespaceId serviceName 获取 serviceService service = serviceManager.getService(namespaceId, serviceName);//缓存时间 默认 10 秒long cacheMillis = switchDomain.getDefaultCacheMillis();// now try to enable the pushtry {// udp 端口大于0 且已经开启推送  只有客户端订阅了  udp  端口才会大于0if (udpPort > 0 && pushService.canEnablePush(agent)) {//添加当前客户端 IP、UDP端口到 PushService 中 会作为可推送的目标客户端添加给推送服务组件pushService.addClient(namespaceId, serviceName, clusters, agent, new InetSocketAddress(clientIP, udpPort),pushDataSource, tid, app);//根据服务名 获取缓存时间默认10 秒cacheMillis = switchDomain.getPushCacheMillis(serviceName);}} catch (Exception e) {Loggers.SRV_LOG.error("[NACOS-API] failed to added push client {}, {}:{}", clientInfo, clientIP, udpPort, e);cacheMillis = switchDomain.getDefaultCacheMillis();}//service 为空判断if (service == null) {if (Loggers.SRV_LOG.isDebugEnabled()) {Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);}//service 为空 构造空对象 返回result.put("name", serviceName);result.put("clusters", clusters);result.put("cacheMillis", cacheMillis);result.replace("hosts", JacksonUtils.createEmptyArrayNode());return result;}//检查服务是否禁用 默认是 启用的checkIfDisabled(service);//服务实例 ipsList<Instance> srvedIPs;//服务实例 ipssrvedIPs = service.srvIPs(Arrays.asList(StringUtils.split(clusters, ",")));// filter ips using selector://若选择器不空 则根据选择算法选择可用的intance列表 默认情况下 选择器不做任何过滤if (service.getSelector() != null && StringUtils.isNotBlank(clientIP)) {srvedIPs = service.getSelector().select(clientIP, srvedIPs);}//serviceIps 为空判断if (CollectionUtils.isEmpty(srvedIPs)) {//为空if (Loggers.SRV_LOG.isDebugEnabled()) {Loggers.SRV_LOG.debug("no instance to serve for service: {}", serviceName);}//客户端类型判断if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {result.put("dom", serviceName);} else {result.put("dom", NamingUtils.getServiceName(serviceName));}//构造空对象返回result.put("name", serviceName);result.put("cacheMillis", cacheMillis);result.put("lastRefTime", System.currentTimeMillis());result.put("checksum", service.getChecksum());result.put("useSpecifiedURL", false);result.put("clusters", clusters);result.put("env", env);result.set("hosts", JacksonUtils.createEmptyArrayNode());result.set("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));return result;}//存储健康和不健康的实例//key为true的value中存放的是所有健康的instance//key为false的value存放的是所有不健康的instanceMap<Boolean, List<Instance>> ipMap = new HashMap<>(2);ipMap.put(Boolean.TRUE, new ArrayList<>());ipMap.put(Boolean.FALSE, new ArrayList<>());//服务实例遍历 区分健康和不健康的实例for (Instance ip : srvedIPs) {ipMap.get(ip.isHealthy()).add(ip);}//阀值判断 isCheck 客户端请求中如果没有传值 则默认是 fasleif (isCheck) {//false 标识没有达到保护阀值result.put("reachProtectThreshold", false);}//获取保护阀值double threshold = service.getProtectThreshold();//通过健康实例除以所有实例 来判断是否触发阀值if ((float) ipMap.get(Boolean.TRUE).size() / srvedIPs.size() <= threshold) {//进入这里标识已经出发了保护阀值Loggers.SRV_LOG.warn("protect threshold reached, return all ips, service: {}", serviceName);if (isCheck) {//启动服务保护机制result.put("reachProtectThreshold", true);}//将不健康的实例全部加入到健康的实例中//这样做的好处是可以保证服务不会那么快被打崩溃 即使有部分失败的 但是还是有可用的服务 //不健康的实例存在的目的就是分流 缓解健康服务的压力ipMap.get(Boolean.TRUE).addAll(ipMap.get(Boolean.FALSE));//清空不健康的实例ipMap.get(Boolean.FALSE).clear();}//阀值判断if (isCheck) {result.put("protectThreshold", service.getProtectThreshold());result.put("reachLocalSiteCallThreshold", false);return JacksonUtils.createEmptyJsonNode();}//能够走到这里 标识没有出发 阀值保护ArrayNode hosts = JacksonUtils.createEmptyArrayNode();//遍历实例for (Map.Entry<Boolean, List<Instance>> entry : ipMap.entrySet()) {List<Instance> ips = entry.getValue();//如果只需要健康的实例 那就跳过不健康的实例if (healthyOnly && !entry.getKey()) {continue;}//遍历服务实例for (Instance instance : ips) {// remove disabled instance://移除禁用的实例if (!instance.isEnabled()) {continue;}//创建空对ObjectNode ipObj = JacksonUtils.createEmptyJsonNode();//构建实例对象ipObj.put("ip", instance.getIp());ipObj.put("port", instance.getPort());// deprecated since nacos 1.0.0:ipObj.put("valid", entry.getKey());ipObj.put("healthy", entry.getKey());ipObj.put("marked", instance.isMarked());ipObj.put("instanceId", instance.getInstanceId());ipObj.set("metadata", JacksonUtils.transferToJsonNode(instance.getMetadata()));ipObj.put("enabled", instance.isEnabled());ipObj.put("weight", instance.getWeight());ipObj.put("clusterName", instance.getClusterName());if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {ipObj.put("serviceName", instance.getServiceName());} else {ipObj.put("serviceName", NamingUtils.getServiceName(instance.getServiceName()));}ipObj.put("ephemeral", instance.isEphemeral());hosts.add(ipObj);}}//设置服务实例列表result.replace("hosts", hosts);//客户端类型判断if (clientInfo.type == ClientInfo.ClientType.JAVA&& clientInfo.version.compareTo(VersionUtil.parseVersion("1.0.0")) >= 0) {result.put("dom", serviceName);} else {result.put("dom", NamingUtils.getServiceName(serviceName));}//返回结果result.put("name", serviceName);result.put("cacheMillis", cacheMillis);result.put("lastRefTime", System.currentTimeMillis());result.put("checksum", service.getChecksum());result.put("useSpecifiedURL", false);result.put("clusters", clusters);result.put("env", env);result.replace("metadata", JacksonUtils.transferToJsonNode(service.getMetadata()));return result;
}

PushService#addClient 方法源码解析

我们上面在分析 InstanceController#doSrvIpxt 方法时候,提到如果客户端的订阅了该服务,Nacos 服务端会进行通过 UDP 推送给客户端最新的服务信息,而这个操作就是由 PushService 类实现的,PushService#addClient 方法只是把服务相关信息加入到了推送列表中。

//com.alibaba.nacos.naming.push.PushService#addClient(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.net.InetSocketAddress, com.alibaba.nacos.naming.push.DataSource, java.lang.String, java.lang.String)
public void addClient(String namespaceId, String serviceName, String clusters, String agent,InetSocketAddress socketAddr, DataSource dataSource, String tenant, String app) {//构造 PushClient 对象PushClient client = new PushClient(namespaceId, serviceName, clusters, agent, socketAddr, dataSource, tenant,app);//加入到推送列表中addClient(client);
}//com.alibaba.nacos.naming.push.PushService#addClient(com.alibaba.nacos.naming.push.PushService.PushClient)
public void addClient(PushClient client) {// client is stored by key 'serviceName' because notify event is driven by serviceName change//获取 serviceKeyString serviceKey = UtilsAndCommons.assembleFullServiceName(client.getNamespaceId(), client.getServiceName());//从 客户端 map  中获取 当前 client 对象ConcurrentMap<String, PushClient> clients = clientMap.get(serviceKey);//为空 判断if (clients == null) {//为空 则加入到 客户端 map 中clientMap.putIfAbsent(serviceKey, new ConcurrentHashMap<>(1024));//加入后获取clients = clientMap.get(serviceKey);}//获取之前的 client 对象PushClient oldClient = clients.get(client.toString());if (oldClient != null) {//为空空 刷新 其实是修改 client 最后一次引用时间 可以理解为更新时间oldClient.refresh();} else {//为空 也就是还没有注册这个推送目标客户端  将 client 加入到 clientsPushClient res = clients.putIfAbsent(client.toString(), client);if (res != null) {Loggers.PUSH.warn("client: {} already associated with key {}", res.getAddrStr(), res.toString());}Loggers.PUSH.debug("client: {} added for serviceName: {}", client.getAddrStr(), client.getServiceName());}
}

PushService#onApplicationEvent 方法源码分析

上面我们分析到客户端获取服务信息的时候,服务端会判断是否有客户端订阅了该服务信息,如果有,则会出发推送给客户端,最终会把服务信息封装成一个 PushClient 加入到 clientMap 中,前文我们分析了客户端是如果感知服务下线的,其中也发现了一个 clientMap 的存储结构,而在 PushService#onApplicationEvent 方法会注册一个延时任务并将该 future 放入 futureMap,该延时任务会从 clientMap 获取指定namespaceId、 serviceName 的client 集合,遍历 client 集合,判断 client 是否是 zombie(僵尸) client,如果是的则移除该 client,否则创建 Receiver.AckEntry,然后通过 UDP 的方式推送给 client,执行完毕后会从 futureMap 移除该 future,至此回到了我们前一篇分析的地方,后续就是我们熟悉的流程,不在重复分析了。

//com.alibaba.nacos.naming.push.PushService#onApplicationEvent
public void onApplicationEvent(ServiceChangeEvent event) {//从事件对象中获取到 serviceService service = event.getService();//获取 servicenameString serviceName = service.getName();//获取名称空间idString namespaceId = service.getNamespaceId();//使用延时任务 延时1 秒 通过 UDP 的方式来发送Future future = GlobalExecutor.scheduleUdpSender(() -> {try {Loggers.PUSH.info(serviceName + " is changed, add it to push queue.");//从缓存map中获取当前服务的内层map 内层map中存放着当前服务的所有Nacos Client的//根据 namespaceId 和 serviceName 获取对应的 client 信息ConcurrentMap<String, PushClient> clients = clientMap.get(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));//为空判断 如果为空 就没有必要推送了if (MapUtils.isEmpty(clients)) {return;}//创建缓存 mapMap<String, Object> cache = new HashMap<>(16);//获取当前时间的 纳秒long lastRefTime = System.nanoTime();//遍历所有的 client 信息for (PushClient client : clients.values()) {//是否是僵尸客户端if (client.zombie()) {Loggers.PUSH.debug("client is zombie: " + client.toString());//如果是的话 就移除僵尸客户端clients.remove(client.toString());Loggers.PUSH.debug("client is zombie: " + client.toString());continue;}//ACKReceiver.AckEntry ackEntry;Loggers.PUSH.debug("push serviceName: {} to client: {}", serviceName, client.toString());//获取推送 keyString key = getPushCacheKey(serviceName, client.getIp(), client.getAgent());byte[] compressData = null;Map<String, Object> data = null;//switchDomain.getDefaultPushCacheMillis() 默认是 10秒 因此不会进入 ifif (switchDomain.getDefaultPushCacheMillis() >= 20000 && cache.containsKey(key)) {org.javatuples.Pair pair = (org.javatuples.Pair) cache.get(key);compressData = (byte[]) (pair.getValue0());data = (Map<String, Object>) pair.getValue1();Loggers.PUSH.debug("[PUSH-CACHE] cache hit: {}:{}", serviceName, client.getAddrStr());}//封装 ackEntry  将客户端信息封装到 ackEntry if (compressData != null) {ackEntry = prepareAckEntry(client, compressData, data, lastRefTime);} else {//这里初始化了需要推送的 客户端ackEntry = prepareAckEntry(client, prepareHostsData(client), lastRefTime);if (ackEntry != null) {cache.put(key, new org.javatuples.Pair<>(ackEntry.origin.getData(), ackEntry.data));}}Loggers.PUSH.info("serviceName: {} changed, schedule push for: {}, agent: {}, key: {}",client.getServiceName(), client.getAddrStr(), client.getAgent(),(ackEntry == null ? null : ackEntry.key));//通过 udp 协议向 Nacos 客户端推送数据udpPush(ackEntry);}} catch (Exception e) {Loggers.PUSH.error("[NACOS-PUSH] failed to push serviceName: {} to client, error: {}", serviceName, e);} finally {//移除 futurefutureMap.remove(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName));}}, 1000, TimeUnit.MILLISECONDS);//任务放入 futureMap  表示已经发送了 udp 到客户端的服务实例futureMap.put(UtilsAndCommons.assembleFullServiceName(namespaceId, serviceName), future);}

总结:服务发现(订阅)Nacos 服务端的代码还是比较简答的,而且也有一种一通百通的感觉,分析的过程中,又回到了我们前文分析的代码,一下子就知道是怎么回事了,也更加理解了 Nacos 的设计思想。

欢迎提出建议及对错误的地方指出纠正。

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

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

相关文章

HumanitZ人道主义z用服务器开服教程

1、登录服务器&#xff08;百度莱卡云&#xff09; 进入控制面板后会出现正在安装的界面&#xff0c;安装大约5分钟&#xff08;如长时间处于安装中请联系我们的客服人员&#xff09; 2、修改查询端口 点击网络&#xff0c;两个端口已经创建完成 复制不是首选的端口&#xff…

入门 git

目录 文章简介1. git 是什么2. 为什么要用 git3. git 入门操作4. git 的来源简述&#xff08;拓展&#xff09; 文章简介 本篇文章主要介绍命令行下的 git 的入门操作&#xff08;本地 – 远端仓库的推送拉取&#xff09;&#xff0c;以及简单理解什么是 git&#xff0c; 为什…

无需标注即可训练,自监督学习框架实现大量未标注毫米波雷达数据预训练自动驾驶感知任务

Abstract 由于雷达&#xff08;radar&#xff09;在雾天和恶劣天气下的操作能力&#xff0c;自动驾驶车辆使用雷达进行感知引起了越来越多的研究兴趣。然而&#xff0c;训练雷达模型受到大规模雷达数据注释的成本和难度的阻碍。为了克服这一瓶颈&#xff0c;我们提出了一种自监…

python pandas处理股票量化数据:笔记4

更新日线数据到最新日期&#xff0c;下面是深发展&#xff08;平安银行&#xff09;更新到20240715以后的stock_daily表。因为积分不够&#xff0c;无法下载tushare.pro接口的通用复权行情数据&#xff0c;只能使用旧的日线数据接口pro.daily&#xff0c;下载的数据没有日线复权…

驱动开发系列04-中断处理

目录 一:概述 二:启用中断 三:注册中断处理程序 四:自动检测中断号 五:快中断与慢中断 六:中断处理程序 七:处理参数和返回值 八:禁用中断 九:上半部和下半部 Tasklets Workqueues 十:共享中断 一:概述 虽然有时候只需使用I/O端口就能控制设备了,但大多…

事务失效的几种情况

一、事务管理的基本概念 事务有四个重要特性&#xff0c;称为ACID特性&#xff1a; Atomicity(原子性)&#xff1a;事务中的所有操作要么全部完成&#xff0c;要么全部不完成。Consistency(一致性)&#xff1a;事务完成后&#xff0c;数据要处于一致的状态Isolation(隔离性)&…

如何在电脑上演示手机上APP,远程排查移动端app问题

0序&#xff1a; 对接客户&#xff0c;给领导演示移动端产品&#xff0c;或者远程帮用户排查移动端产品的问题。都需要让别人能够看到自己在操作手机。 会议室可以使用投屏&#xff0c;但需要切换电脑和手机。 排查问题经常都是截图、或者手机上录制视频&#xff0c;十分繁琐…

Windows图形界面(GUI)-DLG-C/C++ - 滑动条(Trackbar)

公开视频 -> 链接点击跳转公开课程博客首页 -> ​​​​​​链接点击跳转博客主页 目录 滑动条(Trackbar) 使用场景 初始控件 控件消息 示例代码 滑动条(Trackbar) 使用场景 音量控制 亮度调节 视频播放进度控制 任何需要用户在特定范围内选择值的场景 初始控…

恶补,先验分布,后验分布 ,似然估计

恶补&#xff0c;打一遍增加印象 先验分布后验分布&#xff0c;似然估计 声明&#xff1a;仅记录个人学习&#xff0c;并无其他用途。 先验分布 后验分布&#xff0c; 似然估计 隔壁小哥的故事&#xff1a; 隔壁小哥要去15公里外的一个公园里玩&#xff0c;小哥可以选择步行…

【BUG】已解决:ModuleNotFoundError: No module named ‘PIL‘

已解决&#xff1a;ModuleNotFoundError: No module named ‘PIL‘ 目录 已解决&#xff1a;ModuleNotFoundError: No module named ‘PIL‘ 【常见模块错误】 错误原因&#xff1a; 解决办法&#xff1a; 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我…

离散型随机变量为何不是左连续?

目录 离散型随机变量 引言 离散型随机变量的定义和性质是什么&#xff1f; 定义 性质 如何证明离散型随机变量的分布函数在每个可能取值处不具有左连续性&#xff1f; 离散型随机变量的阶梯状分布函数是如何影响其左连续性的&#xff1f; 在统计学中&#xff0c;有哪些方…

web前端面向对象面试25题

1 . 简述面向对象&#xff1f;主要特征是什么&#xff1f; 参考回答&#xff1a; 面向对象是一种对现实世界理解和抽象的方法&#xff0c;是计算机编程技术发展到一定阶段后的产物&#xff0c;是一种是软件开发方法面向对象主要有四大特性&#xff1a; 1、抽象 忽略一个主题中…

微信小程序-自定义组件生命周期

一.created 组件实例创建完毕调用。定义在lifetimes对象里。 不能在方法里面更改data对象里面的值&#xff0c;但是可以定义属性值。 lifetimes:{//不能给data设置值created(){this.testaaconsole.log("created") }}二. attached 模板解析完成挂载到页面。 可以更…

Gitee 使用教程1-SSH 公钥设置

一、生成 SSH 公钥 1、打开终端&#xff08;Windows PowerShell 或 Git Bash&#xff09;&#xff0c;通过命令 ssh-keygen 生成 SSH Key&#xff1a; ssh-keygen -t ed25519 -C "Gitee SSH Key" 随后摁三次回车键&#xff08;Enter&#xff09; 2、查看生成的 SSH…

Carousel of Combinations

由圆排列的公式&#xff0c;不难有 C ( n , k ) ( k n ) k ! k C(n,k)(_k^n)\times \frac{k!}{k} C(n,k)(kn​)kk!​ 于是答案为 ∑ i 1 n ∑ j 1 i ( ( j i ) ⋅ ( j − 1 ) ! ) m o d j \sum_{i1}^{n}\sum_{j1}^{i}((_j^i)\cdot (j-1)!)mod\space j ∑i1n​∑j1i​((ji​…

React学习笔记(井字棋游戏)

本教程将引导你逐步实现一个简单的井字棋游戏&#xff0c;并且不需要你对 React 有任何了解。在此过程中你会学习到一些编写 React 程序的基本知识&#xff0c;完全理解它们可以让你对 React 有比较深入的理解。 教程分成以下几个部分&#xff1a; 配置 是一些准备工作。 概…

【Linux服务器Java环境搭建】010在linux中安装Redis,以及对Redis的配置与远程连接

系列文章目录 【Linux服务器Java环境搭建】 前言 好久没有更新博客了&#xff0c;今天下了班回到家&#xff0c;看到电脑桌上尘封已久的《Spring Boot应用开发实战》&#xff0c;翻开目录想起来之前写的系列【Linux服务器Java环境搭建】还未完结&#xff0c;那就继续吧&#…

实现异步天气数据获取与Spring缓存集成

你好呀&#xff0c;我是小邹。 在Web应用中&#xff0c;实时天气数据的获取是一个常见的需求&#xff0c;特别是在需要频繁更新天气信息的场景下&#xff0c;如旅游网站、天气应用或任何需要展示地理位置相关天气的应用。然而&#xff0c;频繁的外部API调用不仅会增加服务器的…

生成式AI的未来:对话的艺术与代理的实践

生成式 AI 的发展方向&#xff0c;是 Chat 还是 Agent&#xff1f; 随着生成式AI技术的不断进步&#xff0c;关于其未来发展方向的讨论也愈发激烈。究竟生成式AI的未来是在对话系统&#xff08;Chat&#xff09;中展现智慧&#xff0c;还是在自主代理&#xff08;Agent&#x…

操作系统知识点详情-任务调度

本文目录 一、名词解释1. 调度器2. 优先级&#xff08;1&#xff09;优先级反转&#xff08;2&#xff09;优先级继承协议&#xff08;3&#xff09;优先天花板 3. 任务状态&#xff1a;新建、就绪、运行、阻塞、终止。4. 任务类型&#xff1a;计算密集型、I/O密集型5. 实时任务…