AI Agent实现原理与实践(六):Observability & Operations

1. 前言 在上一篇文章中,我们深入剖析了 Permission, Approval & Sandbox 的设计——Agent 如何在拥有强大执行能力的同时,通过多层防御体系保证安全可控。如果说 Agent Loop 是 Agent 的"心跳",Tool System 是"双手",Context Management 是"记忆系统",SubAgent System 是"团队协作能力",Permission & Sandbox 是"免疫系统",那么 Observability & Operations(可观测性与运维)就是 Agent 的"神经系统"——它决定了 Agent 在生产环境中能否被监控、追踪、调试和治理。 当 Agent 真正部署到生产环境——作为 CLI 工具被数千名开发者日常使用、作为后台服务持续运行数周、作为编码助手修改关键代码——可观测性变成了Agent的基本能力。 可观测性不是简单的打日志,报个监控,而是一组更根本的问题: Agent 的一次运行,事后能不能完整回放和审计? Agent 正在做什么,当前处于什么状态,有没有卡住? 子 Agent、后台任务、定时调度——这些异步执行单元能不能被追踪? 出问题后,第一现场在哪里?如何区分是模型问题、工具问题、上下文问题还是系统问题? Token 消耗、API 延迟、工具调用耗时——这些成本指标能不能被量化和管理? 会话能不能被恢复、归档、导出、切换?这些运维动作是不是系统的一等能力? 本文是 Agent 实现原理系列的第六篇,继续参考 Codex CLI、Claude Code、nanobot、pi 四个主流开源项目的实现,从工程角度总结 Agent 可观测性与运维体系的设计原则和关键权衡。 2. 可观测性是 Agent Runtime 的骨架 2.1 从"打点日志"到"多层信号体系" Agent 的运行过程不是一条直线,而是一棵分叉的树——有主会话、有子 Agent、有后台任务、有定时调度、有工具调用链、有压缩边界、有权限决策点。单靠几条零散的 log,在出问题时根本无法还原"当时到底发生了什么"。 更深一层看,可观测性之于 Agent,就像飞行数据记录仪之于飞机——不是为了日常飞行时盯着看,而是为了两件事: 事后可恢复:会话中断了能继续,系统崩溃了能重建状态 事中可诊断:运行时能判断 Agent 是正常推理、卡在工具执行、还是在等待用户审批 因此,一个 production-grade Agent Runtime 的可观测性,不是"有空再补点日志",而是和主执行链同等级的基础设施。它至少需要覆盖五种不同粒度:会话级(transcript、session metadata)、任务级(background task status、task output)、工具级(tool call 轨迹、shell output、permission 事件)、平台级(analytics、tracing、metrics)、运维级(状态快照、健康检查、成本追踪)。 ...

June 7, 2026 · Skyan

AI Agent实现原理与实践(五):Permission, Approval & Sandbox

1. 前言 在上一篇文章中,我们深入剖析了 SubAgent System 的设计——Agent 如何将复杂任务拆解、委托给多个专职子 Agent 并行执行。如果说 Agent Loop 是 Agent 的"心跳",Tool System 是 Agent 的"双手",Context Management 是 Agent 的"记忆系统",SubAgent System 是 Agent 的"团队协作能力",那么 Permission, Approval & Sandbox(权限、审批与沙箱系统)就是 Agent 的"免疫系统"——它决定了 Agent 在拥有强大执行能力的同时,是否会对宿主环境造成不可逆的破坏。 这其实是一个在Agent规模化之后出现的工程难题,在 Demo 阶段,让 Agent “能跑起来"容易,但当 Agent 真正接入开发者的生产环境——能读写文件、执行 shell 命令、访问网络、修改 Git 历史——安全就不再是"锦上添花”,而是"生死攸关"。 Anthropic 曾经内部维护了一个事故日志,专门记录 Agent 行为失控的案例1:Agent 因误解指令而删除了远程 Git 分支;Agent 把工程师的 GitHub 认证 token 上传到了内部计算集群;Agent 试图对生产数据库执行迁移操作。这些事故的共同特征不是"模型意图作恶",而是模型过度热心(overeager)——它以用户没有预料到的方式主动采取了行动。 更隐蔽的威胁来自 prompt injection:攻击者在网页、文件或工具输出中植入指令,劫持 Agent 偏离用户任务、转向攻击者的目标。2025 年 10 月,Anthropic 发表的 Claude Code 沙箱技术博客开门见山地指出:即使是一次成功的 prompt injection,如果发生在 sandbox 之内,攻击者也什么都拿不到2。 ...

June 7, 2026 · Skyan

AI Agent实现原理与实践(四):SubAgent System

1. 前言 在上一篇文章中,我们深入剖析了 Context Management 的设计——Agent 如何在有限的上下文窗口中管理记忆、压缩历史和治理信息。如果说 Agent Loop 是 Agent 的"心跳",Tool System 是 Agent 的"双手",Context Management 是 Agent 的"记忆系统",那么 SubAgent System(子Agent系统)就是 Agent 的"团队协作能力"——它决定了单个 Agent 能否将复杂任务拆解、委托给多个专职子 Agent 并行执行,再将结果汇合为一个整体产出。 这是 Agent 从"单兵作战"升级为"团队协作"的关键一跃。考虑一个真实的编程场景:用户说"帮我重构这个模块,把数据库层从 PostgreSQL 迁移到 MySQL,同时重写所有测试"。在传统单 Agent 模式下,主 Agent 需要先读 Schema、再改 ORM 代码、再迁移数据、再改写测试——每一步的结果都灌进同一个上下文窗口,很快上下文就会膨胀到填满。更糟的是,这些任务天然可并行(改 ORM 和写测试完全可以同时进行),但单 Agent 只能串行。 SubAgent System 解决了这个根本矛盾,在保持主会话可控的前提下,将独立子任务剥离到独立的执行上下文中,并行推进,结果按需回流。这个设计目标看似简单,但要在工程上做好,涉及一套完整的子系统设计:子 Agent 如何创建?父子之间共享什么、隔离什么?子 Agent 之间如何通信?结果如何汇合?worktree 隔离做到什么程度? 最近这一年来,SubAgent System 的研究已经从实验室走向了超大规模的生产实践。2026 年初,Anthropic 研究员 Nicholas Carlini 进行了一项轰动一时的实验1:用 16 个并行 Claude Agent、近 2,000 个 session、两周时间,从零写出一个 10 万行的 C 编译器。 这个编译器能通过 GCC torture test suite 99% 的测试用例,能成功编译 Linux 6.9 内核、QEMU、FFmpeg、SQLite、Postgres 和 Redis。API 费用约 $20,000。有点小贵,但对比一个资深工程师团队两周的人力成本,已经进入了"经济上可行"的区间。与此同时,Anthropic 工程团队也公开了他们在构建**长运行 Agent(Long-running Agent)**时的系统性实践经验2,提出了一套让 Agent 跨越多个上下文窗口持续工作的 Harness 设计。 ...

June 7, 2026 · Skyan

AI Agent实现原理与实践(三):Context Management

1. 前言 在上一篇文章中,我们深入剖析了 Tool System 的设计——Agent 如何定义、注入、执行和回写工具调用。如果说 Agent Loop 是 Agent 的"心跳",Tool System 是 Agent 的"双手",那么 Context Management(上下文管理)就是 Agent 的"记忆系统"——它决定了 Agent 能记住什么、忘记什么、以及如何在有限的上下文窗口中高效组织信息。 1.1 从 Prompt Engineering 到 Context Engineering 过去几年,应用 AI 领域的焦点一直是 Prompt Engineering——如何写出更好的 prompt,让模型产出更好的结果。但随着 Agent 系统从单轮对话演进为多轮、多工具、长生命周期的自主运行,一个更根本的问题浮现了出来:不只是 prompt 写得好不好,而是进入模型上下文窗口的每一段信息,是否都值得占据那有限的注意力预算。 Anthropic 在其 2025 年的工程博客中正式提出了 Context Engineering 这个概念,将其定义为 Prompt Engineering 的自然演进1: Prompt Engineering 解决的是"怎么写 prompt",Context Engineering 解决的是"什么信息该进入上下文窗口,以最大化达成目标行为的概率"。 这个转变的核心原因是:一个在 loop 中运行的 Agent,每轮都在产生越来越多的数据——tool result、思考过程、中间决策、用户反馈——这些数据都有可能对下一轮推理有用,但不一定都值得放进有限的上下文窗口。Context Engineering 的本质,就是从不断膨胀的信息熵中,反复筛选出那组最小的高信号 token 集合。 1.2 上下文窗口没有免费午餐:Context Rot 这个筛选之所以必要,根源在于 LLM 架构层面的一个基本约束。Anthropic 的研究指出,LLM 存在 上下文衰减(context rot) 现象:随着上下文窗口中的 token 数量增加,模型准确回忆其中信息的能力会下降1。 ...

June 6, 2026 · Skyan

AI Agent实现原理与实践(二):Tool System

1. 前言 在上一篇文章中,我们深入剖析了 Agent Loop 的核心原理——Agent 如何在"思考-行动-观察"的循环中持续迭代,直到完成任务。如果说 Agent Loop 是 Agent 系统的"心跳",那么 Tool System(工具系统)就是 Agent 的"双手"——它决定了 Agent 能做什么、怎么做、以及做的过程中如何保证安全和可控。 在2025年以前,大模型应用流行的形态还是Chatbot,这类应用只能说不能做:它可以给你建议,但无法读你的代码、改你的文件、执行你的命令、搜索你的文档。Tool System 把 LLM 从一个"聊天机器"升级为"能干活的行者"。但这也带来了新的工程挑战:工具怎么定义,才能既让模型理解又让运行时高效执行?工具怎么注入,才能既全面又不撑爆上下文窗口?工具怎么执行,才能既灵活又安全?外部工具(MCP)怎么接入,才能和内建工具一样丝滑? 本文是 Agent 实现原理系列的第二篇,继续参考 Codex CLI、Claude Code、nanobot、pi 四个主流开源项目的实现,从工程角度总结 Tool System 的设计原则和关键权衡。 2. Tool System 的本质 2.1 从"函数调用"到"Tool Runtime" 在深入架构之前,先要澄清一个根本问题:工具到底是什么? 传统软件开发是在两个确定性系统之间建立契约——调用 getWeather("NYC") 每次都会以完全相同的方式获取天气。但 Agent 是非确定性的:同一个用户问题,它可能选择调用天气工具,也可能凭通用知识回答,甚至可能产生幻觉或误解工具用法。Anthropic 在其工具设计最佳实践文章中给出了一个精辟的定义1:工具是确定性系统与非确定性智能体之间的契约。 这个定义意味着,为 Agent 编写工具需要从根本上转变思维——不能再像为其他开发者写 API 那样设计工具,而必须面向一个"聪明但偶尔会犯糊涂、上下文有限、需要引导"的调用者来设计。如果你的工具让模型感到困惑、给了它太多无关信息、或者参数命名模糊,那不是模型的问题,是契约设计的问题。带着这个视角来看后面的 Tool System 架构,很多设计取舍的原因就清晰了。 一个最简单的 tool 实现,可以写成这样: def execute_tool(name, args): tool = tools[name] return tool(args) 这种naïve的实现距离一个"能在真实产品中安全运行的 Tool Runtime"还有很大差距。真实场景需要回答的问题包括: ...

June 6, 2026 · Skyan

AI Agent实现原理与实践(一):Agent Loop

导言 自2025年下半年以来,AI大模型的应用热点从chatbot转变到了智能体Agent。以Claude Code,Codex,OpenClaw等为代表的Agent风靡一时,成为业界热点话题。对于开发者而言,一旦习惯使用Agent来开发代码,就不会再回到过去那种、code completion或者chatbot的开发模式了。随之而来的一个疑问就是,Agent和AI chatbot这样的软件有什么本质区别?事实上,主要经过深入学习和研究Agent原理之后,每个人都会发现一个现象:那就是Agent的开发原理和过去所有的软件都不相同,它是一种AI时代、新的、纯新、毫无争议的新软件架构范式。每一个希望深入应用Agent架构的开发者、希望深入了解Agent的学习者、希望深入优化Agent的高级架构师,都必须先了解Agent的实现原理和最佳实践,才能深刻体会到这种崭新的思想,才能在AI时代快速变化的迷雾中,找到一丝微弱的亮光,指引开发者们前行。 本系列文章以著名的四个Agent项目:Claude Code,OpenAI Codex,Pi,Nanobot作为基础案例,分别介绍Agent的主要机制的设计原理和最佳实践。共分为六章:「Agent Loop」,「Tool System」,「Context Management」,「SubAgent System」,「Permission, Approval & Sandbox」以及「Observability & Operations」,这六章可以从机制上剖析Agent的基本架构。但实际Agent应用的内涵远不止这些内容,Agent的工业级落地还会面临更为复杂的情况,这可能是另外一个话题了。 1. 前言 随着2022年底以ChatGPT为代表的LLM(大语言模型)爆发式发展,AI Agent(智能体)迅速从概念走向工程实践。从早期简单的单轮对话工具,到如今能够自主规划、调用工具、持续迭代的编程Agent(如Cursor、Claude Code、Codex CLI),Agent已经成为大模型应用落地的核心形态。正如OpenAI在其官方博客中所说,Agent Loop是连接用户、模型和工具的枢纽。 作为Agent系统的核心机制,Agent Loop(智能体循环)决定了Agent如何思考、如何行动、如何与外部环境交互。这个循环的本质是:Agent接收用户输入,查询模型进行推理,模型要么产生最终回复,要么请求工具调用;Agent执行工具后将结果回灌到prompt中,再次查询模型——如此反复,直到模型停止发出工具调用、产出面向用户的assistant message为止。这个assistant message不仅是对用户的回应,更标志着Agent Loop的一个终止状态:从Agent的视角看,它的工作已经完成,控制权交还用户。 它看似简单——不过是一个"调模型、执行工具、再调模型"的循环——但要在真实产品中稳定、高效、可扩展地运行,却涉及大量精妙的工程设计和架构取舍。比如,随着对话增长,prompt长度会不断增加,而每个模型都有上下文窗口(context window)限制,因此上下文窗口管理是Agent的核心职责之一。此外,Agent执行的工具调用会修改本地环境(如读写文件),所以Agent的"输出"远不止assistant message,还包括它实际产生的代码变更等副作用。 本文是Agent实现原理系列的第一篇,将从工程实现的角度,深入剖析Agent Loop的核心原理。我们将参考Codex CLI、Claude Code、nanobot、pi等主流开源项目的实现,抽象出Agent Loop的通用模型,分析其关键设计点和工程权衡。 2. Agent Loop的本质 2.1 从"会调LLM的程序"到"Agent Runtime" 一个最朴素的Agent实现,可以写成下面这样: while true: response = call_llm(messages, tools) if response has tool_calls: results = execute_tools(response.tool_calls) messages += [response, results] else: return response.content 这段代码确实构成了Agent Loop的核心骨架,但它距离一个"能在真实产品中运行的Agent Runtime"还有很大差距。真实场景需要回答的问题包括: ...

April 25, 2026 · Skyan

策略调度引擎的演进

在搜索、推荐和广告等高复杂度在线系统中,“策略算法的调度执行”是支撑系统智能的基础能力。为了高效运行各种策略算法,几乎所有大型平台最终都走向了相同的抽象:将策略算法封装为算子(Operator),并通过 DAG(有向无环图)驱动其有序并发执行。这种调度引擎的设计已经历二十余年的演进,并在实践中不断向更高层次的智能和复杂性发展。 策略调度引擎通常位于算法密集型在线服务的执行框架之中。下图展示了一个典型推荐系统的策略流程(搜索和广告系统也类似): graph TD A[解析请求参数] --> B[召回算法1] A --> C[召回算法2] A --> D[召回算法3] B --> E[去重] C --> E D --> E E --> F[过滤] G[历史曝光] --> F F --> H[排序] H --> I[重排序] I --> J[返回结果] 上述流程只是推荐系统的一个高层抽象。真实系统中,这样的流程会跨越数十甚至上百个微服务,每个微服务内部都可能执行一个多步骤策略流水线,规模可达几十到几百万行代码。在这种背景下,如何高效开发各阶段策略,并以流程化方式调度执行,成为系统设计的基础问题。 第一代引擎——算子调度(2004~2014) 第一代引擎诞生于桌面互联网兴起到移动互联网爆发前夜的十年间。当时以搜索和广告为代表的复杂在线系统,开始采用面向对象和微服务的设计思想,并把策略算法抽象为算子对象,由调度引擎顺序执行。 这一代的核心特征是: 策略抽象为算子,实现配置化的调度执行 算子化的设计带来了低耦合、高内聚的好处。推荐各阶段复杂算法被封装为独立算子,不同策略可以交给不同的开发者负责,调度引擎只负责顺序调度,从而实现了调度逻辑与策略逻辑的解耦。 但受制于当时的硬件和语言特性,这一代调度主要是单线程串行执行,对多核 CPU 的利用率极低。随着硬件演进和业务规模增长,这种调度方式逐渐暴露出瓶颈。 第一代策略调度引擎一般没有统一的标准,即使在一个系统中,每个微服务内部都有各自不同的调度框架。配置的方式,调度的方式也各有差别。以广告系统为例,一个典型的高级排序服务内部流程如下: graph TD A[解析请求参数] --> B[获取特征] B --> C[广告触发/基础位索引] C --> D[广告排序] D --> E[获取物料] E --> F[返回结果] 类结构如下: classDiagram class Strategy { <<interface>> } class Scheduler class ParserStrategy class SearchStrategy Scheduler ..> Strategy : Use Strategy <|-- ParserStrategy : Extends Strategy <|-- SearchStrategy : Extends Strategy <|-- OtherStrategies : Extends class OtherStrategies 框架通过Strategy设计模式,实现了逻辑上的抽象和隔离。策略算子的调度以顺序执行为主,并行计算很少。 ...

October 2, 2025 · Skyan

程序框架的最佳实践【翻译】

原文作者Chris Nokleberg and Brad Hawkes来自Google,Java框架开发者 原标题:程序框架最佳实践——虽然很强大,但并不适合所有人 共享代码库鼓励代码复用,实现不同团队技术的一致性并改进产品效率和质量。开发者需要选择合适的库,研究如何正确配置,最终把所有的库组装在一起。程序框架可以通过对库的默认安装和配置,来简化开发流程,提供更好的一致性,当然也损失了一些库的便利性。 框架不仅是一堆库的集合,还控制了整个程序的生命周期。确定的框架行为可以为开发开拓空间——例如,不用为每个程序应用都深入审查安全和隐私相关的代码。框架提供了跨团队和跨语言的功能一致性,同时也是更高级的自动化和智能系统的基础。 这篇文章先从框架的核心方面综述开始,然后深入到框架的优势、妥协以及我们推荐实现的最为重要的框架功能。最后,这篇文章展示了一个Google的实际框架应用案例:如何开发一个微服务平台,使得Google可以打破单一代码库的限制,以及框架如何使这一切成为可能。 什么是框架 框架和共享代码库在很多方面都类似。在Google,有两个技术原理用于区分框架和库:依赖反转和可扩展性。虽然看起来比较容易,但本文所讨论的框架很多优势,原则上都是从这两个原理衍生出来的。 IOC(依赖反转) 当从头开始开发一个程序的时候,工程师来决定程序的流程——这通常称为普通控制流。在一个基于框架的程序中,则是由框架来控制流程以及调用用户的代码——这叫做反转控制流。反转控制流有时也被引用为好莱坞原则:“别给我们打电话,我们会给你打电话”。框架控制流很好地定义了不同程序之间的标准。理想情况下,程序只需要实现它们特有的逻辑,而由框架来解决所有其他构建一个微服务所需要的各种细节。 可扩展性 扩展性是区分库和框架的第二个重要特点,它和依赖反转互相配合。因为框架的控制流是由框架负责的,唯一的改变框架运行方式的方法只能通过它暴露出来的扩展点。例如,一个服务端框架可能有一个扩展点是允许一个应用程序在每个请求来的时候运行一些代码。扩展点的模式也意味着框架的其他不可扩展性部分很固定,应用程序无法改变。 框架的好处 框架除了提供共享代码库所能提供的功能之外,还有很多好处,它们对不同的角色在不同方面都有益处。 对开发者 开发者这个角色最终决定是否使用一个可用的框架,也是最显然从框架中获益的角色。大部分的开发者的好处在于提升生产力,简便高效以及可应用最佳实践。开发者可以写很少的代码,利用内置的框架功能。由于框架处理了各种样板代码,因此他们所写的代码可以大幅简化。框架提供了合理的默认配置,消除了无意义而且浪费时间的决策判断,从而提供了充足的最佳实践。 对产品团队 除了为开发者提供生产力以外,框架也为产品团队解放了团队浪费在重复建设基础设施的资源。产品团队因此可以更关注于使他们产品与众不同的功能开发了。 框架可以让产品团队的开发与底层基础设施的变更隔离开,这也能让他们从中受益。虽然并不是普遍适用,但框架提供了额外的抽象,这意味着有些基础设施的迁移可以被当做实现细节,完全由框架维护者全权处理。 Google产品的发布需要很多团队的确认。例如,一个发布协调工程师负责审查产品安全性和有效性,同时有一个信息安全工程师检查程序应用的场景安全漏洞防范设计。当负责审查的团队熟悉框架并可以信任框架的功能保证时,框架就可以帮助简化审查流程。产品发布以后,标准化流程可以让系统更加便于管理。 对公司 从公司层面来说,常用的框架可以减少开发者上手启动一个新应用的时间,从而提升开发者的灵活性。如果一个公司有一个足够大的程序员社群,投入资源在高质量的文档和培训程序员将变得非常有价值。这也有助于吸引社群贡献文档和代码。一个被广泛应用的框架意味着,对框架很小的改进投入可以换来巨大的收益影响。 在过去一段时间,收敛框架架构可以使针对全局性的变更有广泛的应用范围。例如,如果你依赖一个统一的微服务/RPC框架,而且带宽比CPU更贵,那么框架可以基于成本权衡来优化默认的压缩参数。 框架的权衡 虽然框架有上述的多种好处,但也面临着各种权衡。 死板的框架能够掩盖创新 框架常常决定支持哪种技术方案。由于支持所有能想到的技术是不现实的,所以一个比较死板的框架也是有明显的好处的——那就是,它们更鼓励使用某种技术或者更偏好某种设计模式。 死板的框架可以极大简化一个开发者从无到有创建一个系统的工作量。当开发者有很多种方式来完成同一种任务时,他们很容易陷入是否会影响整个系统的细节决策中。对于这些开发者而言,接受一个框架所推荐的技术,可以让他们把重点放在构建他们系统的业务逻辑上。有一个普适并且一致的技术偏好对于整个公司而言更有利,虽然这个答案还不尽完美。 当然,你也必须处理程序和团队的长尾问题,有些产品需求或者团队喜好和现有的框架可能并不完全适配。框架的维护者被放在一个判断什么是最佳实践的位置上,也会需要确认一个不方便的使用案例是否“真实”,因为这个案例可能对每个用户来说都不方便。 另一个需要重点考虑的是,即使某些技术在今天看来的确是一个最佳实践,但技术也在不断快速地进化,对于框架而言也有一定的风险无法跟上技术创新的发展。采用不同的程序设计方案可能开发成本更高,因为开发者既需要学习框架实现细节,也需要框架维护者的协助。 普适性也会导致不必要的抽象 很多框架优势,例如通用的控制面板(后面解释),只有当大部分关键重要的应用都基于这个框架才有真正的意义。这样的一个框架必须足够普适来支持绝大多数用户的场景,这也意味着必须拥有丰富的请求生命周期,以及任何程序都需要的所有的扩展点。这些需求有必要在应用和底层库之间增加若干间接层,这些层也会增加学习成本以及CPU开销。对于应用开发者而言,软件栈中的更多层也会导致调试更复杂。 另一个框架潜在的缺陷是,它们需要工程师额外学习。当新来的Google员工学习如何让一个“hello world”样例运行的时候,他们经常会被他们所需要学习的技术的数量所抓狂。一个功能完备的框架也会让这个情况更糟而不会更好。 Google已经开始尝试让每个框架的核心尽量简单符合预期,让其他功能都成为可选模块,从而来解决这些问题。Google也会尝试提供配套框架的工具,可以帮助了解框架内部结构来简化调试。最终,虽然框架有一些你必须学习的成本,但你需要确保任何一个给定的框架提供了足够的好处来弥补这个成本。不同的编程语言的框架也许有不同的权衡组合,对于开发者而言这也是一个新的决策点和成本/收益权衡场景。 重要的框架特性 如上所述,反转控制和扩展性是框架最为基础的两个特点。除了这些基础能力以外,框架还需要负责若干其他功能。 标准化程序生命周期 再次重申,反转控制意味着框架拥有并且使一个应用程序的整个生命周期标准化,但这样的结构带来什么样的好处呢?我们以避免级联故障为例来说明这个问题。 级联故障是一个典型的引起系统超载的原因,Google内部也很有很多。它可能发生在分布式系统的部分服务失败,增加了其他部分失败的可能。关于级联故障更多的原因,以及如何避免他们,参考SRE(Site Reliability Engineering (O’Reilly Media, 2016))中的定位级联故障这一章。 Google的服务框架有很多内置的避免级联故障的保护功能,两个最重要的原则是: 持续运行。如果一个服务能成功响应请求,那么就应该保持。如果它能处理一部分请求但不能处理其他部分请求,那么就必须继续运行,而且响应它能服务的请求。 快速启动。一个服务必须尽量快地启动。更快地启动意味着可以快速从崩溃中恢复。服务必须避免类似启动时顺序等待RPC访问外部系统返回这样的操作。 Google生产环境给每个服务配置了一个成为健康状态(可以响应请求)的时长。如果超时了,系统则认为一个无法恢复的故障发生了,则终止这个服务进程。 有一个常见的反模式,常发生在缺乏框架的场景中:一个库创建他们管理的RPC连接,并等待连接可用。当服务端代码随着时间推移不断膨胀,你能获得一个实际有几十个这样的有序依赖的库。这样的结果是,处理初始化代码有效展开的话看起来如图1所示: 在通常的情况下,这样的代码工作很好。但因为没有任何潜在问题的提示,这样的代码会有个特殊问题。这个问题只有当一个相关的后端服务慢了或者同时挂了才会显现出来——这时关键服务启动已经延迟了。如果服务启动足够慢,那它将会在有机会处理请求之前被杀死,这会导致一场级联故障。 一种可能的改进是先创建RPC stubs,如图2所示,然后并行等待它们初始化完毕。在这种情况下,你只需等待stub初始化时间最长的那个而不是初始化时间之和。 虽然处理的并不十分完美,但这个有限的重构也说明了你需要某种在库所创建的RPC stubs之间的协调机制——它们必须可以有等待stub以及库以外的某些资源的能力。在Google的案例中,这样的事情是由服务端框架负责的,同时它也有如下功能: 通过定期拉取可用性来并行等待所有stubs可用(<1秒)。一旦配置的超时到了,即使并不是所有后端服务都准备好,服务依然可以继续初始化。 去掉用于人工或者机器可读的调试日志,而是用集成标准化监控和报警来代替。 通过一个通用的机制来支持插拔不同的资源,而不仅是RPC stubs。技术上来说,仅有返回布尔值的函数(为了“我是否准备好”)以及对应的名字,是有打印日志的必要。这些插拔点常被那些处理资源的通用库(例如:文件API)所使用;程序开发者通常只需要用这个库,就能自动地拥有这些能力 提供一个中心化的方式配置特定的关键后端服务,可以改变他们启动或者运行时的行为。 对任何一个单独的库来说,这些功能可能(也正确地)被认为是多了,但实现这些功能,能够让你在一个集中的地方实现,并在所有用这个库的后端服务都能生效。这也是有意义的一件事情。就像共享库是一种在不同应用程序中分享代码的方式,在这个定义下,框架也是一种在不同库之间共享功能的方式。 因为有类似这些的功能,SRE们更愿意支持基于框架的服务。他们也经过鼓励他们对口的程序员选择基于框架的开发方案。框架提供了一个生产规范的基础水平。这个生产规范将一堆没有关联的库关联到一起时是非常难以实现的——但也不是不可能。 标准化的请求生命周期 虽然细节取决于应用程序的类型,但很多框架支持在总的程序生命周期以外的生命周期管理。对于Google的服务端框架而言,最为重要的任务单位就是请求。除了遵循类似依赖反转的模型以外,请求生命周期管理的目标是将请求的不同方面的职责划分成独立的可扩展的代码片段。这允许程序开发者只关注于开发让他们的应用程序独一无二的实际业务逻辑。 这里有一个这样的实际使用的框架例子,它的组件片段见图3: Processors——拦截请求和返回的包。常用于打印日志,但也有一些短路请求的功能(例如,强制在整个程序中不强制执行不变量) Action——程序业务逻辑,接受请求,返回一个对象,可能有一些副作用。 Exception handler——将一个未捕捉的异常转换为一个响应对象。 Response handler——序列化一个响应对象给客户端。 ...

December 5, 2022 · Skyan