news 2026/10/16 0:43:40

多智能体系统通信风暴与死锁治理:生产级降级容灾实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多智能体系统通信风暴与死锁治理:生产级降级容灾实战

1. 先还原一次真实的故障现场

凌晨两点十七分,运维群跳出一条告警:消息队列积压量从三千条在五分钟内飙到三千七百万条。紧接着第二条告警弹出:订单履约Agent和库存Agent互相超时,可用性掉到87%。这不是第一次了,之前几次都是靠重启节点硬扛过去,但每次扛完隔一两周又复发。真正让我下定决心做一次彻底治理的,是那次我完整复盘了从故障发生到人工恢复的全过程——通信风暴和死锁根本不是两个独立的问题,它们在同一套机制里互相放大。

这篇文章想聊的东西很具体:多智能体系统在真实生产环境里,通信风暴和死锁是怎么共生的,生产级降级与容灾方案应该怎么设计、怎么落地、怎么验证。适合正在搭建MAS架构的团队参考,也适合系统里跑着不少智能化Agent组件、但总在“重启恢复”的后端工程师阅读。直接说结论:如果只是加限流、加超时,治不了这个病,必须把降级和容灾当成和Agent通信框架平级的一等公民来设计。

1.1 一条完整的事故时间线

那次的故障从22:10开始。一个库存数据源的变更同步出现延迟,持续了大概40秒。22:11,库存Agent检测到数据不一致,启动补偿线程,向关联的3个下游Agent发出“重算”请求。22:12,这三个Agent各自又向自己的关联Agent扩散重算请求,消息量从每秒几百条涨到每秒上万条,而且大多数是在重复计算同一批数据。22:14,消息积压触发告警。

22:16,真正的死锁出现了。订单履约Agent在等待库存Agent返回预占结果,而库存Agent的补偿任务已经被消息淹没,预占请求排在三千多万条消息后面。库存Agent同时在等待它自己的子补偿任务完成——它既是被等待方,也是等待方。此时系统里没有任何锁,但一组Agent彻底停滞了。22:20,部分超时请求开始自动重试,重试消息再次叠加到队列尾部。22:25,人工介入:停掉补偿线程,清空队列,服务才恢复。

这条时间线有两个值得注意的细节。第一,风暴先于死锁出现,但死锁一旦形成,风暴会更猛烈,这是一个正反馈回路。第二,所有Agent的代码逻辑没改、配置没改,只是下游数据源抖动了一下。问题不是某个Agent写错了,而是整个系统的通信机制在异常输入下缺少约束。

1.2 风暴和死锁为什么总是一起出现

单独看通信风暴,本质是消息产生速率远大于消费速率。单独看死锁,本质是一组Agent互相等待、谁也无法推进。但在MAS里,这两件事天然耦合:死锁导致超时,超时导致重试,重试产生消息洪峰;消息洪峰导致处理延迟上升,延迟上升又扩大了超时范围,更多Agent进入等待状态,等待图上的环越来越多。

所以治理方案不能只治一边。只防风暴,死锁会把积压重新拉起来;只解死锁,风暴会让新消息继续堵塞通道。生产级方案必须同时包含两类动作:一类是“节流”(降级),一类是“解环”(容灾)。后面我按这个思路展开。

2. 多智能体的通信模型与风暴成因拆解

2.1 MAS通信架构的三种形态

不同架构下,风暴的形态不一样,降级方案的落点也不一样。这里列出三种我在生产环境里实际见过的形态:

  • 全互联(Peer-to-Peer):每个Agent之间都有消息通道,链路数是n(n-1)/2。Agent数量超过20个之后,广播一条消息的成本就很高了。优点是单跳直达,缺点是任何一条链路的抖动都可能扩散到全网。
  • 星型(仲裁者模型):所有Agent的消息都经过一个协调者转发,管控能力强,但协调者天然是风暴中心。一旦协调者处理不过来,所有Agent都会进入等待,此时降级的关键是绕开协调者的直连通道。
  • 分层级联(Hierarchical):业务Agent把消息汇聚到区域Agent,区域Agent再汇聚到全局Agent,这是大型系统最常见的形态,也是治理难度最高的,因为风暴可能在任意一层爆发,跨层消息还很难追踪。

明确系统属于自己的哪一种拓扑,才知道风暴的爆点在哪个环节。我建议在消息总线上做一次拓扑梳理,把Agent之间的调用关系完整画出来,而且要随版本演化定期更新。很多团队的架构图存在Wiki里,跟线上实际跑的早就不一致了,等到排查故障时才发现调用关系根本不是想的那样。

2.2 三个最常见的风暴起点

根据我接触过的生产事故,通信风暴的起点基本可以归为三类。

补偿放大。这是最常见的一种。Agent检测到状态不一致之后发起补偿,补偿请求本身又触发下游的状态重新计算,下游再把重算信号传递给更下游。如果补偿逻辑没有幂等控制和去重,消息量会指数级膨胀。前面那个案例里,一条“重算”消息最终扩散成了三千多万条,就是典型的补偿放大。这里有个更隐蔽的问题:补偿请求往往会带上“最近一次快照”之类的上下文,如果快照本身也在频繁更新,那每一条补偿消息的构造都会触发一次新的读操作,进一步抬高消息生成成本。

协调者抖动。MAS里的协调者(比如共识模块)一旦短暂失去心跳,就会触发选举。选举本身会产生一批控制消息。如果网络抖动恢复后旧协调者还在正常发送消息,新旧两套控制流同时存在,控制消息会翻倍甚至更多。这类问题在Raft类协议里叫选举风暴,在MAS里同样成立。我遇到过一种变体:协调者为了“防止自己失联”,设置了非常激进的心跳重试,每200毫秒一次。当网络出现一次300毫秒的抖动时,协调者就疯狂补发了大量心跳和快照同步请求,直接把broker打挂。

重试波峰撞击。前两种可以归为业务逻辑问题,这种纯粹是配置失误。很多团队把超时和重试间隔设成同一个固定值,比如超时3秒、5秒后重试。当上游抖动1秒时,几百个Agent几乎同时进入重试窗口,形成非常尖锐的流量脉冲。明明每秒新增只有几百条,重试脉冲一来就是每秒几十万条。用大白话说,大家平时各行其道,一旦路况变差,所有车都挤在同一个出口排队,那就是死局。

2.3 为什么传统的限流和熔断在MAS里不够用

在普通微服务架构里,限流熔断已经很成熟。但直接搬到MAS上,会遇到几个实际问题。

第一,MAS的调用图是网状的,不是线状的。熔断一个节点,可能导致上下游同时发起重试,反而放大风暴。第二,静态配额不可行,因为不同时刻占用资源的是不同Agent,你无法静态预判谁会成为瓶颈。第三,MAS里消息的“价值”是动态的。一条“下单”消息和一条“日志同步”消息在数量上可能一样,但业务意义天差地别。限流器按数量比例放行,很可能把小概率的高价值消息也限掉。而高价值消息被限流后又会触发补偿,越限越乱。

所以MAS的降级必须是一种结构化降级——不是简单丢弃消息,而是根据消息类型、Agent角色、业务链路优先级做分级处理。这也是我下面要重点展开降级分级的原因。

3. 语义死锁:比抢锁更难发现的停滞原因

3.1 死锁的四个必要条件在MAS里的映射

教科书里死锁的四个必要条件是互斥、持有并等待、不可剥夺、循环等待。这四个条件在MAS里依然成立,但要换一套理解方式:

  • 互斥:某个状态或资源同一时刻只能被一个Agent操作。比如库存预占记录不能被两个Agent同时改写。
  • 持有并等待:Agent已经持有了一部分中间结果(比如预占单),还在等待其它Agent的确认。
  • 不可剥夺:已经发出的“预占成功”承诺无法强制撤销,除非显式发回滚消息。
  • 循环等待:A等待B,B等待C,C等待A。

在传统死锁里,这四个条件通常对应着锁资源。在MAS里,最重要的是“持有并等待”和“循环等待”——即使代码里没有任何锁,只要Agent之间互相等待业务确认,死锁一样会发生。我习惯把这种叫做“语义死锁”,因为它锁的不是数据结构,而是业务状态。

一个具体场景:订单履约Agent为某个订单向库存Agent发起预占请求。库存Agent收到请求后,为了做预占校验,需要先对库存数据做一次重算,而重算结果依赖订单履约Agent刚提交的订单状态。于是库存Agent开始等待订单状态更新,订单履约Agent则在等待预占结果。两个Agent都没有锁,但谁也无法推进。如果其它Agent被牵连进来,这个环会越拉越大,最终表现为整个子系统的吞吐归零。

3.2 用等待图检测环形依赖的落地写法

死锁检测的落地方式是维护一张等待图(Wait-for Graph)。每个节点是一个Agent,每条有向边表示“等待对方完成某件事”。当图中出现环,就说明存在死锁。下面是一个简化实现:

from collections import defaultdict, deque class WaitGraph: def __init__(self): self.edges = defaultdict(set) def add_wait(self, waiter, holder): self.edges[waiter].add(holder) def remove_wait(self, waiter, holder): self.edges[waiter].discard(holder) def detect_cycle(self): nodes = set(self.edges.keys()) for targets in self.edges.values(): nodes.update(targets) if not nodes: return False indegree = {node: 0 for node in nodes} for waiter, holders in self.edges.items(): for holder in holders: indegree[holder] += 1 queue = deque([node for node in nodes if indegree[node] == 0]) visited = 0 while queue: node = queue.popleft() visited += 1 for holder in list(self.edges[node]): indegree[holder] -= 1 if indegree[holder] == 0: queue.append(holder) return visited < len(nodes)

这个检测思路是拓扑排序:不断移除入度为0的节点,如果最后还有节点剩下,说明存在环。全量扫描的复杂度是O(V+E),Agent少的时候每几秒扫一次没问题;但Agent到几百个之后,建议做增量检测——只在边新增或删除时做局部DFS,避免每轮全量重建图。

我在生产里的一个经验:等待图的边不要只记录“正在等待”,还要记录等待了多久。如果一条边存在超过N秒就告警。很多“环”在正常业务里是临时存在的,出现一下又消失是正常的;只有持续时间超过阈值的环,才值得触发降级。这个N我一般设成业务SLA的三分之一,比如SLA是3秒,那N取1秒。

3.3 超时重试如何放大死锁

等待图上的环一旦出现,超时和重试会让问题迅速恶化。假设A等待B,B等待A。A的超时时间到了之后,A会放弃本次请求并重试。重试消息再次发给B,B本来就在忙,新请求又提高了B的负载。B响应更慢,A的再次超时来得更早。每重试一轮,环上的负载就增加一轮。

这个正反馈过程在指标上表现为:消息积压先上升,然后处理延迟P99急剧恶化,紧接着死锁环上所有Agent的等待时间全部超过阈值,系统进入一种“每个Agent都在忙、但整体没有产出”的假死状态。这就是风暴和死锁共生的原因:任何一方单独修复都很快被另一方重新拖下水。

我在实际排查时有个判断标准:如果消息积压和等待图上的环同时出现,先解环再节流。等环解开了,风暴才压得住;如果先节流,环上的Agent还是会互相等待,积压确实会降,但业务依然停滞。

4. 生产级降级:分级、熔断与隔离的落地

4.1 动手设计前先回答三个问题

降级方案设计前,我建议团队先回答三个问题:降什么、降多少、怎么恢复。答不出来就动手,大概率会在故障现场变成“关掉所有Agent”这种一刀切操作。

  • 降什么:明确哪些消息类型、哪些Agent行为是可以牺牲的。比如“同步心跳消息不能牺牲”、“补偿重算消息可以牺牲”。
  • 降多少:定义降级等级,每一级对应一套可执行的动作,而不是一个模糊的“降低负载”。
  • 怎么恢复:必须有自动恢复条件或一键恢复开关,恢复时还要考虑闪断对上游的影响。

这三个问题在架构设计阶段就要有结论。如果等到故障发生才在运维群里讨论,决策成本会高得离谱。我见过一个团队在故障现场争论“要不要把补偿线程全停了”吵了四十分钟,等吵完系统已经彻底不可用了。

4.2 降级等级:从L0到L4的动作定义

我用一套五级降级体系,每一级切的东西不一样:

等级业务状态典型动作恢复条件
L0全部正常不动作-
L1局部波动丢弃低优先级消息、压缩指标上报频率队列积压回落、P99恢复
L2协商失败关闭动态协商,使用预设规则表决策连续N次规则表结果与协商一致
L3链路过载Agent独立运行,只保留关键数据同步人工评估后切回
L4保护模式冻结一切协同行为,仅保留监控与人工通道人工介入

每一级的核心逻辑不是“丢东西”,而是“把不必要的协同降到最低”。比如L2关闭动态协商之后,Agent之间不再实时交换决策依据,改为查一张预置规则表。这张规则表是平时在L0状态下由协商过程预计算的,所以L2下依然能做出大体合理的决策,只是缺少实时性。

这里有个容易被忽略的点:降级动作本身要可观测。你从L2降到L3,这个动作必须产生审计日志,并在监控面板上显示当前全局等级。否则故障结束后复盘,根本说不清楚系统在哪一步做了什么事。这个看似简单的要求,在实际项目里实施起来并不容易——很多团队把降级逻辑塞在Agent内部,没有统一出口。

4.3 降级状态机的实现思路

降级管理器需要定时评估系统指标,决定当前等级。一个简化实现:

class DegradeManager: LEVELS = ["L0", "L1", "L2", "L3", "L4"] def __init__(self, thresholds): self.level = "L0" self.thresholds = thresholds def evaluate(self, metrics): score = 0 if metrics["queue_depth"] > self.thresholds["queue_depth"]: score += 2 if metrics["p99_latency_ms"] > self.thresholds["p99_latency"]: score += 2 if metrics["error_ratio"] > self.thresholds["error_ratio"]: score += 3 if metrics["wait_cycle_count"] > 0: score += 5 if score >= 10: self.level = "L4" elif score >= 7: self.level = "L3" elif score >= 4: self.level = "L2" elif score >= 2: self.level = "L1" else: self.level = "L0" return self.level

这里的权重是一个经验值,需要根据业务调整。我的原则是:等待图上的环是否出现权重最高,因为它比消息积压更能反映“业务真的卡住了”;错误率次之;CPU和内存不参与评分,因为它们是最迟钝的指标,等它们爆表时往往已经晚了。

还要注意降级状态机的滞后性:指标好转后不能立刻升级回L0,应该加一个稳定期。比如在L4待满120秒且指标持续低于阈值,才允许升到L3。避免“降级、恢复、再降级”的抖动。这个稳定期的长度取决于你的业务容忍度,我一般是5到10分钟。

4.4 熔断器和隔离:保护单个Agent不被全局拖死

降级是全局动作,熔断是局部动作。每个Agent都应该包一层熔断器,避免一个坏节点拖垮所有消费者。熔断器的三态转换可以套用Reliability4j的成熟模型:

CircuitBreakerConfig config = CircuitBreakerConfig.custom() .failureRateThreshold(50) // 50%失败率触发熔断 .minimumNumberOfCalls(20) // 至少20次调用才评估 .slidingWindowSize(10) // 滑动窗口大小 .waitDurationInOpenState(Duration.ofSeconds(30)) .build();

参数里最关键的是waitDurationInOpenState。我见过很多团队把这个时间设成5秒、10秒,结果熔断恢复后下游还没喘过气,立刻被打回Open态,反复横跳。在生产里,这个值至少应该大于下游恢复时间的2倍,我给的是一个经验范围而不是具体值,因为不同业务恢复差异很大——有些下游是缓存故障,几秒能好;有些是数据库慢查询,可能要几分钟才能缓过来。

隔离层面我推荐舱壁隔离:每个Agent配置独立的线程池和消息队列,池子上限设小一点(比如10到50),宁愿让这个Agent积压消息,也不能让它借走其它Agent的资源。很多人觉得这样浪费资源,但在故障期间,这份“浪费”就是系统的保命钱。实际落地时,我会给每个Agent的线程池打上名字,方便在线程Dump里快速定位究竟是哪个Agent把线程占满了。

5. 容灾与恢复:快照、仲裁与脑裂防护

5.1 用快照加事件回放恢复Agent状态

降级只能缓解症状,容灾才能恢复业务。Agent是有状态的,单靠重启进程恢复不了现场。我的方案是“周期性全量快照 + 实时追加事件日志”的组合。每5分钟对Agent的核心状态做一次快照;两次快照之间的每一次状态变更,都追加写入事件日志,日志带上自增序列号。

故障后恢复的流程是:加载最近一次快照,然后按序列号回放增量事件。这样做的好处是,回放是确定性的,不会因为消息乱序而产生歧义。实际使用中要注意快照间隔不能太短:每5分钟一次对大型Agent可能IO压力过大,可以放宽到10分钟;间隔拉长后,回放时间会变长,需要在恢复速度和正常负载之间做取舍。

我踩过的坑是:快照只存了业务状态,没存消息游标。恢复后Agent重新处理了已经处理过的消息,导致状态重复。正确的做法是快照里同时记录消息队列的消费位置,恢复时从游标之后继续消费。这个细节不写进设计文档的话,基本都会被忽略。

5.2 协调者容灾的仲裁逻辑

如果系统里有中心协调者,容灾的关键在仲裁。我通常部署3个仲裁节点,日志采用过半写策略:任一决策必须得到2/3节点确认才算生效。这个和Raft的法定人数逻辑一致。3节点能容忍1节点故障,5节点能容忍2节点故障。对绝大多数MAS系统,3节点足够。

仲裁节点的心跳流量必须单独走一条通道,不能和业务Agent的消息共用一个broker。有一次我做故障注入,往网络里塞了大量延迟,结果仲裁节点的心跳包也被卡住,业务流没挂,仲裁先失联了。那次之后我把心跳通道独立出来,同时把心跳超时和业务超时分开设置——心跳超时短一点(比如2秒),业务超时按业务SLA走。

5.3 脑裂:网络分区后的冲突处理

脑裂是容灾里最麻烦的场景。如果网络被切断,两个分区各自选出协调者、各自接受写请求,恢复后状态必然冲突。解决思路是:以持久化日志的高水位为准,丢弃低水位的后续写请求,并做一次全量补偿。

具体到实现:仲裁节点在每次写请求前都记录一个全局递增的日志序列号。恢复后对比两边协调者的日志尾号,尾号高的一方保留,尾号低的一方回放差距并回滚未达成共识的过期操作。我额外加了一个保护:网络分区期间,如果某分区内的副本数低于法定人数,直接进入只读保护模式,不处理写请求。这样可以减少恢复时要做补偿的数据量。

脑裂场景还有另一个容易忽略的问题:人工介入的指令如何送达被孤立的分区。我在设计里给每个Agent留了一条带外管理通道,走独立的网络出口。这样即使业务网断了,运维还能通过管理通道把Agent切到保护模式,而不是干等网络恢复。这个通道收到的指令要记录到审计日志里,否则恢复后排查期间做了哪些操作就说不清了。

6. 故障排查链路与混沌工程验证

6.1 从告警到根因的完整排查链路

一次风暴加死锁事故,我按这个顺序排查:

  1. 先看全局降级等级变化:确认系统当前在哪个等级、动作是什么时候触发的。如果降级已经正确执行但指标还在恶化,说明降级太保守,等级不够低。
  2. 再看消息积压分布:从消息中间件的监控里按Agent维度拆,找出积压最深的几个队列,这些队列的消费者多半是风暴的放大器或死锁的参与方。
  3. 然后看等待图:把等待图上持续了异常时长的边拉出来、找环。这一步能直接定位死锁链路,不需要逐个Agent翻日志。
  4. 接着看超时和重试日志:确认环上的Agent到底在重试什么。很多时候是同一个补偿任务在反复执行,这就是补偿放大的实锤。
  5. 最后人工处置:必要时直接切L4冻结协同,清队列、解环,再切回L0观察。

这套链路每一步都要有对应工具支持:监控面板一个tab显示全局等级,消息中间件自带积压看板,等待图做成可视化图。如果哪一步没有工具支撑,排查效率就差很多——故障现场每一分钟都很贵。

6.2 关键监控指标清单

我把和风暴、死锁强相关的指标整理成表,每个团队都可以直接参考:

指标采集方式建议告警阈值观察要点
消息队列积压量中间件offset差值3分钟内持续超阈值风暴的早期信号
端到端延迟P99业务埋点高于SLA的40%超时扩散的迹象
等待图边长图组件上报单边超过SLA的1/3死锁前的先兆
等待图环数图组件检测大于0持续1分钟确认死锁
重试消息占比消息header计数超过30%补偿放大的信号
降级动作次数审计日志短时间多次触发配置是否合理

这些指标如果只是“有”还不够,必须按Agent维度打标签。否则看到“消息积压10万条”只能干着急,不知道压在哪、谁在消费、哪个环节被堵住。标签维度至少要包含Agent名、消息类型、队列名三个字段。

6.3 混沌工程:把降级方案真正打穿一次

降级与容灾方案不经过验证,和不存在没有本质区别。我强烈建议在预发环境引入混沌工程,至少打三组场景:

  • 消费者崩溃:随机kill掉20%的Agent进程,观察降级等级是否按预期触发、消息积压是否在阈值上稳定。
  • 消息延迟注入:把部分链路的处理延迟人为拉高到3到5倍,观察重试波峰是否出现、熔断器是否在预期窗口进入Open态。
  • 仲裁节点分区:把协调者网络断开30秒,观察业务Agent是否进入保护模式、恢复后高水位回放是否正确。

第一次做故障注入时,我把仲裁节点的心跳通道和业务通道混在一起,结果延迟注入直接把仲裁也打挂了,系统进入不可恢复的停滞。这个教训逼着我把心跳通道独立出来,也让我明白:混沌工程的价值一半在验证,一半在暴露架构里那些“想当然没问题”的薄弱点。

验证通过的标准我定为三条:降级等级在预期时间内触发;故障期间的错误率控制在SLA允许范围内;恢复操作能在10分钟内自动完成,不需要人工杀进程。

做这套治理方案的过程,让我最深的体会是:降级不是降低标准,而是提前定义好系统在压力下的行为。与其在故障现场靠人肉判断,不如在架构设计时就把每一级降级的动作、触发条件、恢复条件写清楚,并且保证每次降级都有审计、有监控、可回放。

最后分享一个小技巧:把全局降级等级做成分发式配置,下发给所有Agent,而不是写死在Agent代码里。这样故障发生时,运维只需在配置中心把等级切到L3,全链路立即生效,不用动代码、不用重启进程。别小看这个动作,它能把平均恢复时间从小时级降到分钟级。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 0:42:19

动态环境下视觉SLAM如何保持鲁棒?SOF-SLAM语义与几何双重校验解析

做视觉SLAM的人&#xff0c;多半都有过这种经历&#xff1a;实验室里跑得飞起的demo&#xff0c;一搬到地铁站、商场这种人多的地方&#xff0c;轨迹就开始画龙。移动的行人、旋转的推拉门、飞过的球——任何一个出现在画面里的动态物体&#xff0c;都可能让相机的位姿估计在一…

作者头像 李华
网站建设 2026/10/16 0:42:17

OpenCV与CNN车牌识别系统实现:从定位分割到字符识别的完整流程

简介&#xff1a;一套基于OpenCV与卷积神经网络的车牌识别系统完整代码&#xff0c;面向计算机视觉毕业设计、课程项目及入门学习者&#xff0c;解决从车牌定位到字符识别的完整流程问题。压缩包内共包含两千个文件&#xff0c;除大量车牌样本图片外&#xff0c;还包含Python源…

作者头像 李华
网站建设 2026/10/16 0:28:16

VPKTool:一个用C语言读写VPK资源包的解析与打包库

简介&#xff1a;VPKTool是一套面向游戏开发者的C语言VPK文件处理库与命令行工具&#xff0c;专为Valve引擎项目&#xff08;如CS:GO、半条命2&#xff09;设计&#xff0c;意在解决VPK打包格式的读取、创建、修改与分析问题&#xff0c;帮助团队更高效地管理音频、纹理、模型等…

作者头像 李华
网站建设 2026/10/16 0:27:54

电子元器件比价平台:4个真实比价渠道和方法

元器件比价不能只看单价。同一颗料&#xff0c;不同平台的报价差异可达30%——但低价背后可能藏着翻新料、无检测、无售后的坑。真正靠谱的比价要同时比4样东西&#xff1a;单价、检测能力、账期、售后。比价渠道本身也要选对&#xff0c;用错渠道比出来的价格没有参考价值。一…

作者头像 李华
网站建设 2026/10/16 0:20:38

手写极简Promise:从源码理解异步编程与微任务机制

1. 为什么Promise在异步编程里如此重要很多同学在刚接触JavaScript时&#xff0c;最先接触到的异步写法是回调函数。回调模式最大的痛点不在“回调”本身&#xff0c;而在于当回调嵌套多层之后&#xff0c;代码的可读性会急剧下降&#xff0c;也正因如此才诞生了Promise。是的&…

作者头像 李华