ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Agent编排实战:从设计到落地的多智能体协作指南

Agent编排实战:从设计到落地的多智能体协作指南 1. 为什么“Agent 编排”值得单独拎出来讲1.1 从一个真实踩坑场景说起去年下半年我接手了一个内部工具链的改造项目目标是把几个独立的小助手串成一条能自动跑通的流水线一个负责读需求文档一个负责拆任务一个负责写代码草稿最后一个负责跑测试并回传结果。听起来不复杂对吧我一开始也是这么想的直接写了个主函数按顺序调用四个模块中间用字符串拼接传参。结果第一版跑通只花了半天后面修bug修了整整两周。问题出在哪出在“编排”这两个字上。单个助手的能力其实都还行但一旦把它们连起来各种边界情况就冒出来了上游输出格式变了下游解析直接崩某个环节超时了整条链路卡死两个环节都想调用同一个资源互相抢锁更别提中间某一步失败之后到底该重试、该跳过、还是该回滚根本没有统一策略。我那时候才意识到Agent 编排不是简单的函数调用链它是一套独立的工程问题。这篇文章就想把这件事讲透。不管你是刚接触多智能体协作的新手还是已经写过几版编排逻辑但总觉得别扭的老手我都希望这里的内容能让你少走点弯路。我会从设计思路、核心机制、落地实现、问题排查四个维度展开尽量把每个决策背后的“为什么”说清楚而不是只丢一堆代码让你抄。1.2 编排到底在编排什么先把概念对齐。所谓 Agent 编排指的是把多个具备独立决策能力的智能体按照某种控制逻辑组织起来协同完成一个超出单个智能体能力的任务。注意这里有两个关键词独立决策、控制逻辑。独立决策意味着每个 Agent 有自己的输入输出、有自己的工具集、甚至有自己的“脾气”比如有的擅长发散、有的擅长收敛。控制逻辑则决定了它们之间怎么传数据、怎么定顺序、怎么处理异常、怎么共享状态。很多人把编排等同于“写个循环依次调用”这就把问题想简单了。真正的编排要解决的是在不确定的执行环境下如何让一组不确定的组件稳定地产生确定的结果。这个定义听起来有点绕但你可以这样理解单个 Agent 像是一个员工编排则是管理这群员工的制度。员工能力再强制度混乱项目照样黄。反过来制度设计得好哪怕单个员工能力一般整体产出也能很稳。这就是为什么我说编排值得单独拎出来讲——它是多智能体系统从“能跑”到“能上线”之间那道最关键的坎。1.3 本文适合谁看如果你属于下面几类人这篇内容应该对你有用正在做多智能体协作项目但被状态传递、异常处理搞得头大的开发者想从单 Agent 应用升级到多 Agent 系统不确定该怎么设计控制流的工程师对编排框架选型犹豫不决想搞清楚各种方案取舍逻辑的技术负责人单纯想了解“Agent 编排”到底是怎么回事为后续学习打基础的好奇者。我会尽量不假设你用过某个特定框架因为编排的核心思想是跨框架通用的。你理解了这套逻辑换任何工具都能快速上手。2. 编排方案的整体设计思路2.1 先想清楚你的任务是什么形状在动手写任何编排代码之前我强烈建议你先做一件事把任务画出来。不是画流程图那种正式的东西就是拿张纸把每个环节的输入、输出、依赖关系、可能的失败点标出来。这一步花十分钟能省后面十小时的调试。任务大致可以分成几种形状。第一种是线性链A 做完给 BB 做完给 C像流水线一样。这种最简单但也是最容易出问题的因为任何一环卡住整条链就停了。第二种是扇出扇入一个环节产出多个结果分发给多个下游并行处理最后再汇总。这种适合可以并行拆分的任务但汇总逻辑往往很麻烦。第三种是带环的图某个环节的结果可能触发上游重新执行形成迭代。这种最灵活也最难控制因为你要防止死循环。我踩过的坑是一开始把任务想成线性链写了个顺序执行器结果后来发现某个环节需要根据结果决定是否回退硬生生把线性结构改成了图结构代码重构了一大半。所以在动手前把任务形状想清楚比选什么框架重要得多。2.2 控制流与数据流要分开设计这是我最想强调的一点。很多初学者会把控制流和数据流混在一起比如用“上一步的输出”直接作为“下一步是否执行的判断依据”。这样做短期看很省事长期看是灾难。正确的做法是控制流决定“谁在什么时候执行”数据流决定“执行时拿到什么数据”。两者通过一个共享的状态容器来交互但逻辑上要解耦。举个例子A 环节产出了一个结果控制流关心的是“这个结果是否满足继续条件”数据流关心的是“这个结果怎么传给 B”。如果混在一起当你想改判断条件时可能会不小心改动了数据传递格式引发连锁 bug。我现在的习惯是在编排层维护一个上下文对象所有环节的输入输出都往里面读写但每个环节只声明自己需要读哪些字段、写哪些字段。控制流则单独维护一张状态机记录每个环节的执行状态待执行、执行中、成功、失败、跳过。这样改控制逻辑不会影响数据格式改数据格式也不会影响控制逻辑。2.3 同步、异步还是事件驱动编排的执行模型有三种主流选择各有各的适用场景。同步阻塞式最简单一个环节执行完才执行下一个。优点是逻辑清晰、调试方便缺点是效率低遇到耗时环节整个链路都在等。适合环节少、耗时短、对实时性要求不高的场景。异步非阻塞式允许环节并行执行通过回调或 Future 来协调。效率高但复杂度也高尤其是错误传播和状态同步会变得很棘手。适合环节多、可以并行、对吞吐量有要求的场景。事件驱动式把每个环节看作事件的消费者和生产者通过事件总线解耦。灵活性最高扩展性最好但调试难度也最大因为执行路径不是显式写出来的而是运行时动态决定的。适合环节动态变化、需要热插拔的场景。我的经验是除非有明确的性能瓶颈否则优先选同步阻塞式。先把逻辑跑通等真的遇到性能问题了再针对性地把耗时环节改成异步。过早引入异步和事件驱动往往会让简单问题复杂化。我见过太多项目一开始就上事件驱动结果调试一个简单 bug 要翻十几个日志文件。2.4 状态管理编排的心脏状态管理是编排里最容易做砸的部分。我见过几种典型的错误做法把状态存在全局变量里、把状态序列化到文件里每次读写、把状态分散在各个环节内部靠参数传递。全局变量的问题是并发不安全多个环节同时读写会互相污染。文件序列化的问题是性能差而且容易产生不一致的中间状态。参数传递的问题是链路一长参数列表就爆炸了而且中间环节可能根本不需要某些参数却被迫透传。我推荐的方案是集中式状态容器 不可变快照。所有状态存在一个容器里每个环节执行前拿一份当前状态的只读快照执行后提交一份变更。这样既保证了并发安全又方便做回滚和审计。具体实现可以用一个简单的键值存储加上版本号机制。每次提交变更时版本号加一回滚时直接恢复到指定版本。注意状态容器里不要存大对象比如完整的文件内容或图片二进制。存引用或路径就行大对象单独管理。否则状态快照会变得非常大影响性能。2.5 异常处理策略要提前定异常处理是编排设计里最容易被忽视、又最不能忽视的部分。我建议在动手前就明确几个策略重试策略哪些环节失败可以重试重试几次重试间隔怎么算我的经验是对于调用外部服务的环节重试三次、间隔指数退避是比较稳妥的默认值。对于纯计算环节重试通常没意义直接失败更快。降级策略某个环节失败后是否有备选方案比如主模型调用失败是否切换到备用模型降级逻辑要显式写出来不要指望运行时自动发现。熔断策略如果某个环节连续失败多次是否要暂时跳过它避免拖垮整条链路这在依赖外部服务的场景下特别重要。补偿策略对于已经产生副作用的环节比如写入了数据库失败后是否需要回滚这需要每个环节自己声明是否支持补偿操作。这些策略不要等到出问题了再想那时候往往已经来不及了。在编排层统一实现这些策略比在每个环节里各写一套要可靠得多。3. 核心机制与实操要点拆解3.1 环节抽象每个 Agent 怎么接入编排层要能统一管理各种 Agent前提是每个 Agent 都符合一个标准接口。这个接口不需要很复杂但必须包含几个关键方法execute(context)执行主体逻辑接收上下文返回结果validate(context)执行前的校验检查输入是否满足要求compensate(context)补偿操作用于回滚副作用metadata()返回元信息比如名称、版本、依赖、超时时间。我见过有人为了省事直接让每个 Agent 暴露一个run()方法其他什么都不管。结果编排层没法做统一校验也没法做补偿异常处理只能靠 try-catch 包一层非常脆弱。多花半小时定义接口能省后面几天的返工。接口定义好之后还需要一个注册机制让编排层知道有哪些 Agent 可用。简单的做法是用一个字典键是 Agent 名称值是实例。复杂一点可以用插件式加载支持运行时动态注册。我一般先用字典等真的有动态需求了再升级。3.2 上下文传递数据怎么在环节间流动上下文传递的核心问题是下游环节怎么拿到上游环节的产出同时又不被上游的无关数据干扰。我的做法是给上下文加一个命名空间机制。每个环节写入数据时必须指定一个命名空间比如step_a.result。下游环节读取时也明确指定要读哪个命名空间。这样既避免了键名冲突又让数据依赖关系变得显式。具体实现上上下文可以是一个支持嵌套读写的字典。写入时用context.set(step_a.result, value)读取时用context.get(step_a.result)。如果需要传递复杂结构可以约定用 JSON 序列化但要注意序列化开销。还有一个细节上下文要不要在环节间传递引用还是拷贝。传递引用效率高但上游环节如果修改了数据下游会受影响。传递拷贝安全但大对象拷贝开销大。我的折中方案是小对象传拷贝大对象传引用但标记为只读。这样兼顾了安全和效率。3.3 执行调度谁先谁后怎么定调度逻辑决定了环节的执行顺序。最简单的调度是拓扑排序根据依赖关系排出执行序列。但实际场景往往比这复杂因为可能存在条件分支、循环、并行。我一般把调度逻辑分成两层静态依赖图 动态决策器。静态依赖图在初始化时构建记录哪些环节必须在哪些环节之前执行。动态决策器在运行时根据上下文决定下一步执行哪个环节支持条件分支和循环。动态决策器的实现可以很简单就是一个函数接收当前上下文和已执行环节列表返回下一个要执行的环节名称。返回空表示执行结束。这样调度逻辑和环节逻辑完全解耦改调度不影响环节改环节也不影响调度。提示动态决策器里一定要加最大步数限制防止死循环。我一般设成环节数量的三到五倍超过就强制终止并报错。3.4 并发控制并行环节怎么协调当多个环节可以并行执行时需要协调它们的启动、等待和结果收集。最直接的方式是用线程池或协程池提交任务后等待所有任务完成。但并行会带来几个问题。第一是资源竞争多个环节同时读写共享资源可能冲突。解决办法是给资源加锁或者让环节声明自己需要哪些资源调度层统一分配。第二是部分失败如果并行任务中有一个失败了其他任务要不要取消我的策略是默认等待所有任务结束然后统一处理失败除非失败是致命的才主动取消其他任务。第三是结果顺序并行任务完成顺序不确定但下游可能依赖特定顺序。解决办法是在收集结果时按环节名称排序而不是按完成顺序。我实测下来并行度不是越高越好。对于 IO 密集型环节并行度可以设高一些比如 CPU 核数的两到四倍。对于计算密集型环节并行度设成 CPU 核数就行再高反而因为上下文切换变慢。3.5 日志与可观测性出问题怎么查编排系统一旦跑起来出问题的时候如果没有足够的日志排查会非常痛苦。我的经验是日志要记录三类信息环节的开始和结束、上下文的变更、异常和重试。环节开始和结束的日志要包含环节名称、时间戳、执行时长。上下文变更的日志要包含变更前后的值但要注意脱敏不要把敏感数据写进日志。异常和重试的日志要包含异常类型、堆栈、重试次数。除了日志还建议加一个执行轨迹功能把每个环节的输入输出、执行状态、耗时都记录下来形成一个可查询的轨迹。这样出问题时直接看轨迹就能定位到哪个环节出了问题不用翻日志。轨迹可以存在内存里也可以持久化到数据库看需求。4. 完整实操流程与关键环节实现4.1 环境准备与依赖安装假设我们用 Python 来实现这套编排逻辑基础环境需要 Python 3.9 以上因为要用到一些较新的类型注解特性。依赖方面核心只需要标准库但为了更好的开发体验建议装几个辅助库pip install pydantic typing-extensionspydantic用来做数据校验和序列化typing-extensions用来支持更丰富的类型注解。如果你需要异步支持可以再加asyncio不过它是标准库自带的不用额外装。项目结构建议这样组织orchestrator/ __init__.py context.py # 上下文实现 agent.py # Agent 接口定义 scheduler.py # 调度器实现 executor.py # 执行器实现 exceptions.py # 自定义异常 agents/ # 具体 Agent 实现 __init__.py reader.py planner.py coder.py tester.py这样分层清晰后续扩展也方便。4.2 上下文容器的实现先来实现上下文容器。核心需求是支持命名空间读写、版本管理、快照。import copy from typing import Any, Dict, Optional class Context: def __init__(self): self._data: Dict[str, Any] {} self._version: int 0 self._snapshots: Dict[int, Dict[str, Any]] {} def set(self, key: str, value: Any) - None: self._data[key] value self._version 1 def get(self, key: str, default: Any None) - Any: return self._data.get(key, default) def snapshot(self) - int: self._snapshots[self._version] copy.deepcopy(self._data) return self._version def rollback(self, version: int) - None: if version not in self._snapshots: raise ValueError(fSnapshot version {version} not found) self._data copy.deepcopy(self._snapshots[version]) self._version version property def version(self) - int: return self._version这里有几个设计决策值得说明。第一set方法每次都会增加版本号这样任何变更都可追踪。第二snapshot方法会深拷贝当前数据保证快照不受后续修改影响。第三rollback方法恢复到指定版本用于补偿操作。注意深拷贝大对象开销很大如果上下文里存了大对象建议只存引用快照时只拷贝引用本身。或者用写时复制Copy-on-Write策略只在真正修改时才拷贝。4.3 Agent 接口与基类实现接下来定义 Agent 接口。我用抽象基类来实现强制子类实现必要方法。from abc import ABC, abstractmethod from typing import Any, Dict class BaseAgent(ABC): property abstractmethod def name(self) - str: pass abstractmethod def execute(self, context: Context) - Any: pass def validate(self, context: Context) - bool: return True def compensate(self, context: Context) - None: pass def metadata(self) - Dict[str, Any]: return { name: self.name, timeout: 30, retry: 3, compensable: False, }execute是必须实现的其他方法有默认实现。metadata返回元信息调度器会根据这些信息决定超时、重试等策略。实现一个具体的 Agent 示例class ReaderAgent(BaseAgent): property def name(self) - str: return reader def execute(self, context: Context) - Any: doc_path context.get(input.doc_path) if not doc_path: raise ValueError(Missing doc_path in context) with open(doc_path, r, encodingutf-8) as f: content f.read() context.set(reader.content, content) return content def metadata(self) - Dict[str, Any]: return { name: self.name, timeout: 10, retry: 1, compensable: False, }这个 Agent 读取文档内容并写入上下文。注意它从上下文读输入也往上下文写输出不通过参数传递这样解耦得更彻底。4.4 调度器的实现调度器负责决定执行顺序。我实现一个基于依赖图的调度器。from typing import List, Dict, Set class Scheduler: def __init__(self): self._dependencies: Dict[str, Set[str]] {} self._agents: Dict[str, BaseAgent] {} def register(self, agent: BaseAgent, depends_on: List[str] None): self._agents[agent.name] agent self._dependencies[agent.name] set(depends_on or []) def topological_sort(self) - List[str]: visited set() result [] def visit(node: str): if node in visited: return visited.add(node) for dep in self._dependencies.get(node, set()): visit(dep) result.append(node) for name in self._agents: visit(name) return result def next_agent(self, executed: Set[str]) - Optional[str]: for name in self.topological_sort(): if name in executed: continue deps self._dependencies.get(name, set()) if deps.issubset(executed): return name return Nonetopological_sort给出一个合法的执行顺序next_agent根据已执行集合返回下一个可执行的 Agent。这样调度逻辑和 Agent 实现完全解耦。4.5 执行器与异常处理执行器负责实际调用 Agent并处理超时、重试、补偿。import time from typing import Optional class Executor: def __init__(self, scheduler: Scheduler, context: Context): self.scheduler scheduler self.context context self.executed: Set[str] set() self.max_steps len(scheduler._agents) * 5 def run(self) - None: steps 0 while steps self.max_steps: steps 1 name self.scheduler.next_agent(self.executed) if name is None: break agent self.scheduler._agents[name] self._execute_with_retry(agent) self.executed.add(name) def _execute_with_retry(self, agent: BaseAgent) - None: meta agent.metadata() retry meta.get(retry, 1) timeout meta.get(timeout, 30) last_error None for attempt in range(retry): try: start time.time() if not agent.validate(self.context): raise ValueError(fValidation failed for {agent.name}) agent.execute(self.context) elapsed time.time() - start if elapsed timeout: raise TimeoutError(f{agent.name} exceeded timeout) return except Exception as e: last_error e if attempt retry - 1: time.sleep(2 ** attempt) raise last_error这个执行器实现了重试和超时检查。注意重试间隔用指数退避第一次等 1 秒第二次等 2 秒第三次等 4 秒避免密集重试压垮下游。4.6 组装与运行最后把所有部分组装起来跑一遍。def main(): context Context() context.set(input.doc_path, ./requirements.md) scheduler Scheduler() scheduler.register(ReaderAgent()) scheduler.register(PlannerAgent(), depends_on[reader]) scheduler.register(CoderAgent(), depends_on[planner]) scheduler.register(TesterAgent(), depends_on[coder]) executor Executor(scheduler, context) executor.run() print(Final result:, context.get(tester.result))这就是一个最小可用的编排系统。麻雀虽小五脏俱全包含了上下文管理、Agent 抽象、调度、执行、异常处理这些核心机制。你可以在这个基础上按需扩展比如加并行执行、加事件驱动、加持久化。5. 常见问题与排查技巧实录5.1 环节卡死不动怎么办这是最常见的问题。表现是程序跑着跑着就没反应了日志也不输出。可能的原因有几个第一某个环节在等待一个永远不会到来的信号比如等待一个已经被消费掉的事件。排查方法是看日志最后停在哪然后检查那个环节的等待条件。第二死锁两个环节互相等待对方释放资源。排查方法是看线程堆栈找出互相等待的线程。第三死循环调度器一直在两个环节之间来回跳。排查方法是看执行轨迹如果发现环节重复执行就是死循环。我的经验是给每个环节加超时是最有效的预防手段。不管什么原因卡住超时后强制终止并报错至少不会让整个系统挂死。5.2 数据传递丢失或错乱表现是下游环节拿到的数据不是上游产出的或者干脆是空的。可能的原因键名拼写错误、命名空间冲突、上下文被意外清空。排查方法是加一个上下文变更日志每次读写都记录键名和值。这样出问题时直接看日志就能定位到是哪一步出的错。我一般会在开发环境开启这个日志生产环境关掉避免性能开销。预防措施是给上下文加类型校验。写入时检查值的类型是否符合预期读取时如果类型不对就报错。这样能在早期发现问题而不是等到下游环节才暴露。5.3 重试导致副作用重复执行这是个隐蔽但危害很大的问题。如果某个环节有副作用比如写数据库、发消息重试时副作用会重复执行导致数据不一致。解决办法是让有副作用的环节支持幂等。具体做法有几种用唯一 ID 去重、用乐观锁检查版本、用事务保证原子性。如果实在做不到幂等就要在编排层标记这个环节不可重试失败后直接走补偿逻辑。我在实际项目里踩过这个坑一个环节负责给用户发通知重试了三次用户收到了三条一样的通知。后来改成先写一个待发送记录发送成功后标记为已发送重试时先检查记录状态就解决了。5.4 并行环节结果顺序不确定并行执行时结果收集顺序和完成顺序一致但下游可能依赖特定顺序。解决办法是在收集结果时按环节名称排序而不是按完成顺序。results {} for future in as_completed(futures): name futures[future] results[name] future.result() ordered [results[name] for name in sorted(results.keys())]这样无论哪个环节先完成最终结果顺序都是确定的。5.5 常见问题速查表问题现象可能原因排查方法解决措施环节卡死等待信号、死锁、死循环看日志最后位置、线程堆栈、执行轨迹加超时、检查等待条件、限制最大步数数据丢失键名错误、命名空间冲突上下文变更日志加类型校验、统一命名规范副作用重复重试未幂等检查副作用记录实现幂等、标记不可重试结果顺序乱并行完成顺序不定检查结果收集逻辑按名称排序收集性能差串行执行、大对象拷贝性能分析改并行、传引用、写时复制5.6 几个独家避坑技巧第一个技巧在开发环境加一个“慢动作模式”每个环节执行前暂停一秒方便你观察执行顺序和上下文变化。这个模式在调试复杂编排逻辑时特别有用能让你直观看到数据怎么流动。第二个技巧给每个环节加一个唯一执行 ID格式是环节名-时间戳-随机数。这个 ID 会出现在所有相关日志里方便你追踪一次执行的完整链路。我试过在日志量很大的时候靠这个 ID 快速定位问题。第三个技巧上下文里加一个“调试信息”命名空间专门存中间结果和调试数据。这个命名空间的数据不参与业务逻辑只用于排查。生产环境可以关掉开发环境全开。第四个技巧定期做“混沌测试”故意让某些环节失败或超时看编排系统能不能正确处理。我一般会在测试环境随机注入故障验证重试、降级、补偿逻辑是否按预期工作。这个习惯帮我提前发现了好几个隐藏 bug。6. 编排方案的扩展与演进方向6.1 从硬编码到配置驱动最初的编排逻辑往往是硬编码的环节顺序、依赖关系都写在代码里。这种方式简单直接但改起来麻烦每次调整都要改代码、重新部署。演进方向是配置驱动把编排逻辑抽到一个配置文件里比如 YAML 或 JSON。代码只负责解析配置并执行不关心具体有哪些环节。这样调整编排逻辑只需要改配置不用动代码。配置大概长这样agents: - name: reader class: agents.reader.ReaderAgent depends_on: [] - name: planner class: agents.planner.PlannerAgent depends_on: [reader] - name: coder class: agents.coder.CoderAgent depends_on: [planner]解析配置后动态加载类并注册这样扩展新环节只需要加配置和实现类不用改调度代码。6.2 从单机到分布式当环节数量增多、耗时变长单机执行可能扛不住。这时候需要考虑分布式编排把环节分散到多台机器上执行。分布式编排的核心挑战是状态同步和任务调度。状态同步可以用中心化的状态存储比如 Redis 或数据库。任务调度可以用消息队列把环节任务投递到队列工作节点消费并执行。分布式带来的好处是扩展性和容错性但复杂度也大幅上升。我的建议是除非单机真的扛不住否则不要轻易上分布式。很多项目其实单机加并行就够了上分布式反而引入了一堆新问题。6.3 从静态编排到动态编排静态编排的依赖关系是预先定义好的运行时不会变。动态编排则允许在运行时根据上下文决定下一步执行什么甚至动态生成新的环节。动态编排的典型场景是任务分解一个规划环节根据输入生成一组子任务每个子任务对应一个环节这些环节在运行时才确定。这需要编排系统支持动态注册和动态调度。实现动态编排的关键是把环节定义和环节实例分开。环节定义是静态的描述这类环节能做什么环节实例是动态的描述这次执行具体要做什么。调度器根据实例来调度而不是根据定义。6.4 编排的可观测性建设随着编排系统变复杂可观测性变得越来越重要。除了前面说的日志和轨迹还可以加指标监控和告警。指标包括每个环节的执行次数、成功率、平均耗时、P95 耗时。这些指标能帮你发现性能瓶颈和异常环节。告警则是在指标超过阈值时主动通知比如某个环节失败率超过 10% 就告警。我一般用 Prometheus 收集指标Grafana 做可视化Alertmanager 做告警。这套组合比较成熟接入也简单。如果不想引入这么多组件至少要把关键指标打到日志里方便后续分析。6.5 我个人在实际操作中的体会做了几个编排项目之后我最大的体会是编排的复杂度不在于技术而在于对业务的理解。技术方案再先进如果对任务形状、失败模式、数据依赖理解不到位照样做不好。所以我现在做编排第一步永远是跟业务方聊清楚这个任务有哪些环节每个环节的输入输出是什么哪些环节可能失败失败了怎么办把这些聊透了技术实现反而是水到渠成的事。另外一点体会是不要追求一步到位。我见过太多项目一开始就想设计一个完美的编排框架结果框架还没写完业务需求已经变了。更好的做法是先实现一个最小可用版本跑通核心流程然后根据实际遇到的问题逐步演进。这样每一步都有明确的改进目标不会过度设计。最后分享一个小技巧给编排系统加一个“回放”功能。把每次执行的上下文变更记录下来出问题时可以回放整个执行过程一步步看数据怎么变化的。这个功能在排查复杂问题时特别有用比看日志直观得多。实现也不复杂就是在上下文变更时追加写日志回放时按顺序重放。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表