微服务架构里,一个请求失败,根因往往藏在层层嵌套的调用链深处。Uber 的工程师们每天面对的就是这种困境:几千个服务互相依赖,某个下游服务抖一下,上游一堆接口跟着报错,可到底是谁拖垮了谁?传统的分布式追踪(distributed tracing,记录请求在多个服务间流转路径的技术)理论上能回答这个问题,但在 Uber 的规模下,它慢得让人绝望。

先看一组数字你就明白为什么了。Uber 超过 99.9% 的请求都成功返回,且延迟在可接受范围内。这意味着错误请求是极少数。如果按 0.01% 的采样率去抓追踪数据,一个错误请求被采样到的概率是千万分之一。假设每秒有 500 个请求,想抓到一条失败请求的完整追踪链路,平均要等 5 小时 33 分 20 秒。等追踪数据出来,故障早就过去了,工程师只能对着历史数据复盘,没法在问题发生时快速定位。

Uber 的解法很务实:放弃对单个请求的完整可见性,转而用 RPC 中间件(RPC middleware,在服务间远程调用过程中拦截请求、附加处理逻辑的组件)从日志里直接统计调用方和被调用方的成败关系。他们把服务依赖分成三类:fail-close(下游一挂,上游必挂,属于强依赖)、fail-open(下游挂了,上游还能继续工作,属于弱依赖)、unknown(证据不足,无法判断)。这个分类一旦建立,就能沿着调用路径推演,看一个故障会怎样从底层服务一路传导到最外层的 API。

具体实现上,Uber 把分析逻辑塞进了自家的 RPC 框架 YARPC(Uber 自研的、支持 Go 和 Java 的远程调用框架)里。请求进来时,入站中间件生成一个请求 ID,并把调用方服务和接口记录在内存里的 RequestTracker 中。这个 ID 会跟着 Go 的 context(Go 语言中用于在函数调用链间传递请求级数据的标准机制)一路传递,出站中间件拿到它,就能把下游服务、接口和调用结果也记下来。请求结束时,中间件发出指标,把调用方和被调用方的成败状态对应起来。

有了这些成对数据,Uber 为每个调用方依赖计算一个故障相关概率:大于等于 0.8 判为 fail-close,小于等于 0.2 判为 fail-open,中间值归为 unknown。重试请求会被单独处理,避免被误判成独立的依赖关系。这套方案和基于 OpenTelemetry(一套开源的、用于生成和采集追踪数据的标准工具集)的分布式追踪管道不同——后者靠尾部采样(tail sampling,在追踪数据收集完成后,根据错误或延迟等条件有选择地保留部分追踪记录)来保留关键链路,而 Uber 直接用中间件指标捕获生产环境的失败,省去了存储和评估海量单条追踪的开销,用牺牲请求级细节换来了更广的故障覆盖。

最让我意外的是这个思路的朴素:没有引入新的复杂系统,没有上什么花哨的 AI 分析,就是在一个已有的 RPC 框架里加了两段中间件逻辑,配合日志统计,就把服务依赖图谱和故障传播路径给自动梳理出来了。它解决的核心问题是:当错误率极低、传统采样抓不到失败样本时,如何用低成本的方式获得足够多的故障关联数据。

这套方法对任何维护微服务架构的团队都有直接参考价值。如果你的服务数量还没到 Uber 的规模,但同样面临"错误请求太少、追踪采样抓不到"的窘境,可以借鉴这个思路:与其依赖昂贵的全链路追踪,不如在 RPC 层做轻量级的成败统计,用概率阈值来划分依赖强弱。要注意的坑是,这种统计方法只能告诉你"这个下游挂了,上游大概率会挂",但解释不了为什么挂——它适合做故障影响面的快速评估和依赖梳理,不适合做根因分析。另外,unknown 这个分类很重要,别为了追求全量分类而强行下结论,证据不足就承认证据不足,这本身就是一种严谨。

Accelerating Performance by Incrementally Integrating Rust into Existing Codebas
Accelerating Performance by Incrementally Integrating Rust into Existing Codebas
阅读原文 → 返回 AI 技术文档

内容与图片版权归原作者所有 · 原文: https://www.infoq.com/news/2026/10/uber-automated-dependency/?utm_campaign=infoq_content&utm_source=infoq&utm_medium=feed&utm_term=global