微服务不是设计出来的:Uber架构演进背后的组织逻辑

发布时间:2026/9/2 20:01:22
微服务不是设计出来的:Uber架构演进背后的组织逻辑 如果你维护过一个从十来个人成长到上百人的后端团队大概率会有这种体感系统体积的增长速度总是领先于架构调整的速度。一开始所有功能在一个应用里数据库就一个接口文档靠 Wiki部署靠一个人一支脚本。后来人多了功能多了一次发布要协调十几个人的代码合并一次事故要在一个庞大的日志文件里翻上半天。Uber 的微服务演进是国内后端社区反复讨论的经典案例。它在早期也经历过典型的单体架构阶段后来逐渐走向大规模服务拆分最终成了各种技术分享里“别人家架构”的代表。这个故事经常被包装成“如何设计微服务架构”但站在工程现实的角度看更准确的说法是微服务不是设计出来的是被增长逼出来的。这个判断不是要否定架构设计的意义而是想提醒所有做后端的工程师架构选型最重要的输入不是你画了多少张漂亮的架构图而是组织、代码库和业务复杂度一起增长之后旧结构的摩擦已经到了无法忽视的地步。Uber 的前任 CTO 在回顾这段历史时也表达过类似的意思这套微服务架构不是一次规划会议的结果而是业务增长和团队扩张一步一步推着走出来的。如果把 Uber 这条演进路径拆开看你会发现它真正值得学习的不是“拆了多少个服务”而是它在每一个增长节点上如何判断旧的架构已经扛不住以及如何选择切下去的那条线。1. 当一辆车的所有请求还挤在一个进程里Uber 早期和后端团队里很多项目一样是一个单体应用。乘客端叫车、司机端接单、下单、路径计算、支付核心业务逻辑基本都在这套单体代码里。一个共享数据库一套部署流程前端请求打到同一套后端服务上。在业务量不大、团队人数不多的时候这套架构非常高效改一个接口、修一个 Bug、发一个新版本链路很短。但增长会改变一切。当用户规模和订单量开始快速上涨团队也从几个核心工程师扩张成多个业务小组时单体架构的短板会从三个方向同时暴露出来。1.1 单体阶段看似高效其实把三个矛盾埋在了同一个进程里第一个矛盾是部署矛盾。单体应用里任何模块改动都要走同一个发布流程。支付团队改了一行配置整个应用要重新编译和上线上线过程中如果出现问题乘客端、司机端、派单逻辑都会一起受到影响。更麻烦的是发布窗口会变得越来越长因为改动合并前需要回归的不仅是自己的模块而是整个系统的核心链路。第二个矛盾是协作矛盾。代码仓库只有一个但团队已经分成好几拨。支付、派单、用户、地图每个团队都在同一份代码里改东西。代码冲突、分支管理、联调环境、发布权限这些消耗的不是技术热情而是最宝贵的迭代时间。当两个团队的发布节奏互相依赖时任何一个阻塞都会传导到业务侧。第三个矛盾是数据矛盾。单一数据库在设计上很简单但也意味着所有业务模块共享同一份存储。一个慢查询如果走了索引错误影响的不只是某个接口而是整库的数据库连接池被拖满。订单、支付、司机状态都挤在同一个数据库里任何一次资源争抢都是全局性的。这三个矛盾有一个共同特征它们不是某一个具体接口写得好不好造成的而是“多个独立业务领域共享同一进程、同一存储、同一发布单元”造成的。只要增长还在继续这些矛盾就会越来越尖锐。1.2 “增长逼出来的拆分”长什么样我在带团队时经常看到一种现象架构开始拆分通常不是因为技术团队预先画好了一张宏伟蓝图而是因为某个业务需求在单体架构里实在推不动了。Uber 的情况也类似。支付要支持新的支付方式派单要接入更多运力地图要处理更复杂的路线数据。每一条业务线都在快速变化但如果所有变化都汇入同一个代码仓库、同一次发布流程那么业务响应速度会被架构拖慢。这时候技术团队才会认真考虑把某一块业务从单体里抽出来独立成服务独立部署独立迭代。所以微服务不是被“设计”出来的而是被“倒逼”出来的。先有业务增长和团队扩张才有了拆分的必要。如果没有这个前提微服务的复杂度反而会成为负担。我见过很多团队在单体阶段遇到问题第一反应是把“性能优化”和“拆微服务”绑在一起。但在 Uber 这个案例里真正驱动拆分的不是性能而是组织生产关系的矛盾——多个团队在一个进程里协作成本已经超过拆分成本。2. 真正把单体拆开的不是架构是增长带来的摩擦很多文章讨论微服务时会把重点放在技术细节上服务注册、负载均衡、分布式事务、链路追踪。这些当然重要但如果只盯着技术容易忽略一个更底层的问题为什么是“现在”拆而不是更早或更晚答案往往藏在组织规模和业务复杂度的变化里。2.1 先有组织边界才有服务边界服务边界不会凭空产生。你很难在代码层面定义一个“合理”的微服务边界因为边界本质上是组织协作关系的映射。Uber 拆分服务的时候最自然的切分方式是沿着团队职责切支付团队负责支付服务派单团队负责派单服务用户团队负责用户服务。每一个服务背后都有一个可以独立决策、独立排期、独立上线的团队。代码边界和团队边界一旦对齐协作成本就会明显下降团队之间的接口约定变成一个服务 API团队内部的修改可以自主决定。这也是微服务和“模块化”的本质区别。模块化是代码层面的边界同一个进程内依然要一起部署、一起发布。微服务是部署层面的边界每个服务可以独立发版、独立扩容、独立故障。所以微服务不是把类拆开而是把团队协作的方式拆开。2.2 故障半径和部署成本才是拆分的真正触点那么什么信号出现时管理者才真正下决心拆从工程经验看两个信号最强烈。第一个信号是故障半径过大。单体架构里一个模块内存泄漏可能拖垮整个进程一个服务的慢调用可能占满所有 Tomcat 线程。最难受的是你的系统并没有那么脆弱但因为所有东西都在同一个进程里隔离性天然就没有。当故障排查不得不从业务模块 A 一直看到业务模块 Z 的时候团队会开始意识到单一进程已经提供不了“可控性”。第二个信号是部署成本过高。当一次常规发布需要协调多个团队为了一个小改动要等待完整的回归测试甚至因为跨模块依赖而不断调整发布时间时独立部署的收益就会显现出来。微服务的核心收益之一是把“频繁并发发布”变成“独立小步快跑”。它不是让发布变得更快而是让单个团队的发布不再被其他团队阻塞。这两个信号在 Uber 的案例里都出现过。支付、派单、用户这些核心链路在快速增长中不断触碰单体架构的边界于是服务一个个被切出来。整个过程不是一蹴而就的而是边增长、边拆、边补治理能力。3. Uber 微服务化路线里的几个关键转折从公开的技术分享来看Uber 的演进并不是一站式地从一个单体跳到几千个服务。它经历了一条相对清晰的路径先解决入口和数据层问题再服务化最后治理化。理解这条路径比单纯看最终的微服务架构图有用得多。3.1 从单体到网关先把入口变清晰在拆分服务之前首先要面对的是流量入口的混乱。单体时代所有请求都进同一个后端前端和 App 只需要知道一个地址就够了。可一旦服务数量增加客户端不可能为每个服务去配置不同的地址也不应该直接暴露内部服务结构。于是API 网关这一类统一入口层出现了。它负责路由、鉴权、限流、灰度等公共能力。客户端只和网关通信网关再把请求转发到后端的各个服务。Uber 的演化路径里也经历过类似的阶段先让入口变得可控再让后端服务之间的关系变得可控。这一步的意义容易被低估。如果没有一个统一入口服务拆分得越多客户端适配成本越高安全隐患也越多。网关本质上是把“服务暴露”这件事收拢到一个统一位置来管理。只有入口清晰了后续扩容、灰度、故障隔离才有操作空间。3.2 从服务化到治理化服务数量只是中间结果拆服务本身不难难的是拆完之后的治理。当微服务数量从几十个增长到几百个、上千个时团队成员面对的不再是一个代码仓库而是一个需要被“管理”的分布式系统。服务怎么发现新节点上线后调用方怎么知道它的地址服务怎么配置不同环境下参数如何隔离一次跨服务请求怎么在日志里定位到完整链条这些都不是“拆完服务”自然就会有的能力必须投入专门的基础设施建设。Uber 在服务数量达到一定规模后遇到的一个重要问题就是可观测性。服务多了请求链路过长日志散落在不同实例上如果不做集中日志和链路追踪排障效率会急剧下降。这也是后来分布式追踪、集中日志、监控告警、注册中心、配置中心这类组件在云原生时代流行的原因。3.3 服务类型不统一是规模化之后最先暴露的问题服务数量增长之后还有一个很容易被忽视的坑服务类型不统一。有些服务是 HTTP API 服务对外提供接口有些服务是消费者型服务从消息队列里拿任务处理有些服务是定时任务有些服务只做计算不对外暴露任何接口。如果所有服务都用同一套模板来管理运维同学会非常痛苦但如果每个服务都自己定义一套模式排障、监控、发布又很难统一。Uber 自己也经历过这种规模化的阵痛。当团队开始统一服务类型、统一部署方式、统一可观测性接入时背后的驱动已经不是“要不要拆”而是“拆完之后怎么继续活下去”。这一步对很多中小团队来说很远但它能提前提醒一点拆微服务不是终点拆完之后你还要补齐一整套服务治理能力。演进阶段要解决的核心问题常见动作主要代价单体阶段快速验证业务一个仓库、一个库、一次发布部署、协作、故障扩散入口治理流量入口混乱引入网关/统一 API 层新增一跳网络转发服务化独立迭代、独立扩容按团队或领域拆服务分布式部署复杂度上升治理化服务多到失控补充注册、链路、日志、配置基础设施投入变大这个表格不是 Uber 官方路线而是从大规模微服务演进案例中提炼出来的通用阶段。具体到每个团队顺序和粒度会不同但整体逻辑基本一致。4. 从 Uber 的案例提炼一套“要不要拆”的判断框架Uber 的微服务演进很成功但它不是拿来就能用的模板。普通团队如果直接把 Uber 的复杂度搬过来很可能得不偿失。关键在于你的业务增长速度、团队规模、治理能力是否已经达到需要微服务的水平。4.1 五分钟判断清单你的业务是否需要微服务我在做架构咨询时通常会建议团队先回答五个问题业务规模是否还在持续增长如果业务量本身不涨单体架构的摩擦不会成为主要矛盾拆服务反而增加维护成本。团队是否已经大到“一个代码仓库协作困难”如果只有一两个团队代码冲突和发布协调成本并没有高到需要拆部署单元。是否有一个模块需要独立扩容比如支付和派单的流量特征完全不同如果不拆分只能整个应用扩容资源利用率会很低。是否已经出现故障半径过大的事故例如一次慢查询拖垮了全部业务这时候隔离边界才有真实的业务价值。团队有没有能力维护注册中心、网关、链路追踪、日志平台、配置中心如果答案是没有拆出来的服务越多运维压力越大。这五个问题里至少有三个答案是“是”拆分才具备必要条件。如果只是看着大厂架构图觉得“我们也应该微服务”那我建议再等一等。4.2 如果确定要拆先从边界最清晰的服务动手不是所有模块都适合优先拆分。选第一个服务要满足三个条件业务边界清晰。它不依赖太多内部共享状态能独立描述自己的领域语义。调用链路相对独立。它的上游和下游比较稳定拆出去不会导致大量跨服务来回调用。能独立验证。拆完之后可以通过接口测试、监控数据、日志链路确认它工作正常。一个常见的选择是从非核心但业务完整的功能开始比如通知、短消息、定时任务、报表等。它们不影响主交易链路即使拆分过程中出问题故障半径也相对可控。等这套流程跑顺了积累了服务治理经验再动支付、订单这样更核心的链路。4.3 拆分时的排查链路拆服务这件事技术含量不只是“把代码搬出去”还包括拆分过程中的问题定位。我自己常用的排查顺序是这样的先看现象是服务不可用、超时、报错还是数据不一致。再看输入输出调用方是否传对了参数返回结果是否符合预期。再看网络依赖服务间调用是否走了网关是否有超时配置重试策略是否正确。再看数据边界新服务是否还在读旧表是否和别的服务共享了同一个数据库。再看日志链路一个请求跨了多少跳每一跳的耗时分布在哪里。最后看资源配置连接池、线程池、内存、CPU是否因为实例数变多而出现资源分配不均。大部分“拆完之后才出现的故障”根源不是服务拆分本身而是拆分前的依赖没有被完整梳理清楚。尤其要注意共享数据库这个坑服务是拆开了但两个服务仍然读写同一张表这时你需要先做数据边界拆分否则服务化只会增加延迟不会带来真正的独立性。如果业务本身还处在模式验证阶段拆微服务的成本大概率会吃掉增长红利。Uber 能承受拆分成本是因为拆分可以换来更快的团队迭代速度小团队如果复制这套做法得到的往往是更慢的发布和更复杂的排障。5. 服务拆到几千个之后难的反而不是服务本身很多技术文章喜欢强调微服务的数量仿佛服务越多越先进。但服务数量从来不是目标而是结果。当服务规模大到一定程度真正的挑战会从“怎么写一个新服务”变成“怎么管理这么多服务”。5.1 服务数量不是目标故障隔离和团队自治才是微服务带来的核心收益只有两个故障隔离和团队自治。故障隔离是指一个服务出问题不会直接拖垮整个系统。支付服务不稳定时至少叫车和派单还能继续跑。要做到这一点服务之间必须做好超时、重试、熔断、降级、限流而不是简单地拆成多个进程就完事。团队自治是指一个团队可以独立开发、独立部署、独立上线自己的服务而不需要和另一个团队反复对齐版本。要做到这一点服务 API 必须长期稳定接口变更需要兼容策略团队之间依赖关系要尽量少。如果拆完服务之后团队之间依然需要频繁联调服务之间依然会因为超时设置不合理而相互拖垮那微服务带来的只是物理隔离不是逻辑隔离。后者才是治理的重点。5.2 可观测性、配置中心、服务发现必须走在拆分前面在工程实践里我强烈建议先建基础设施再大规模拆服务。否则你会发现每拆一个服务排查问题的成本就高一分。等拆到几十个时一个请求跨了 5 跳但日志散落在 5 个系统里这时候再补链路追踪要花的时间已经是早期补的好几倍。最基本的服务治理能力至少包括这几项能力解决的问题常见开源方案服务发现动态扩缩容下的服务地址维护Nacos、Consul、Eureka统一网关入口路由、鉴权、限流Nginx、Kong、Spring Cloud Gateway、APISIX链路追踪跨服务调用链分析SkyWalking、Jaeger、Zipkin集中日志日志统一采集、检索ELK、Loki配置中心动态调整配置、环境隔离Nacos、Apollo这些组件不是说必须全上而是说在服务数量突破某个阈值之前至少要有一个可用的日志链路和监控告警体系。否则拆服务带来的不确定性和排障成本会很快超过它带来的团队自治收益。Uber 后来的很多工程能力建设本质上都是在补这块课服务多了之后如何让工程师能在一个可视化的因果链里定位问题而不是靠直觉和运维经验。6. 给普通团队的三步迁移法Uber 是几百上千人团队面对全球业务规模的案例普通公司不需要也不可能照搬。但它的经验可以压缩成一个更通用的迁移方法先拆职责边界再拆数据边界最后拆部署边界。6.1 第一步拆职责边界第二步拆数据边界第三步拆部署边界很多团队拆微服务失败是因为直接跳到了第三步先把部署拆开但代码里模块之间仍然是“你说我调你我调你库”的纠缠状态。正确的顺序应该反过来第一步先做模块化。在一个应用内部按领域划分模块禁止跨模块直接操作对方内部实现。比如支付模块和派单模块之间只通过明确接口交互不允许支付模块直接读写派单表。第二步再做数据边界划分。数据库层面开始拆分把核心数据的归属权明确到模块。一个模块只操作自己的核心表对外提供数据访问接口避免多个服务共享同一张表或同一个数据库。第三步最后做部署边界拆分。只有职责边界和数据边界都稳了把一个模块独立成服务才真正有隔离效果。此时拆出去的服务可以在保持数据独立的情况下独立发布、独立扩容。这个过程听起来慢但每一步都是可验证、可回滚的。拆到一半发现不对你可以退回到模块化阶段而不用像直接拆部署那样把整个系统推倒重来。6.2 最容易翻车的五个细节根据我的观察微服务迁移真正翻车通常不是因为技术难而是因为这五个细节被低估了第一个是拆完成之后没有配套超时和重试策略。服务拆分后原本进程内的调用变成网络调用网络超时、重试、幂等都需要重新设计。如果直接复用以前的同步调用方式局部故障会迅速放大。第二个是服务之间循环调用。A 服务调 B 服务B 服务又调 A 服务接口一拆链路被拉长一次请求的耗时成倍增加。设计服务边界时一定要警惕双向依赖。第三个是网关变成新瓶颈。所有请求都经过网关之后网关的容量和稳定性反而成了整条链路的命门。网关要提前做好限流、降级和有损服务方案。第四个是数据库没拆就上了服务化。两个服务共享同一个数据库看起来是微服务实际还是单体而且多了网络损耗和运维复杂度。数据边界是微服务的地基。第五个是同时拆了太多服务。一次拆五六个服务出了问题根本不知道是迁移引入的还是原本就存在的。更稳妥的做法是每次只拆一个服务验证稳定后再拆下一个。每次拆分一个服务都要把它当一次小项目来做。先跑一个最小可用版本观察日志、错误率、耗时和依赖再把它接入正式流量。不要同时拆多个服务也不要在一台机器上验证完就直接上生产。7. 架构演进终究是一场与组织复杂度的对话在技术社区里我们讨论微服务时很容易陷入“架构图崇拜”。一张漂亮的微服务架构图确实比一张混乱的单体依赖图舒服但架构图是结果不是起点。Uber 的故事之所以值得拿出来反复讲不是因为“微服务天下第一”而是因为它提醒我们架构演进的真正驱动力是组织、业务和团队规模一起增长后旧结构产生的摩擦。微服务只是回应这一摩擦的一种方式方式要服从于阶段、规模和治理能力。如果你正在纠结“要不要拆微服务”不妨先把这个问题抛到一边回答更实际的三组问题单一代码库和单一发布流程是否已经拖慢了业务上线速度一个模块的故障是否还在影响其他完全无关的业务团队是否已经大到跨团队协作的成本已经明显超过独立部署的维护成本如果答案都是否那现在的架构可能还不是你的主要瓶颈。如果答案都是是那你要做的也不是一次性把整张架构图重画而是像 Uber 那样从一个边界最清晰的服务开始拆一个、验证一个、稳定一个逐步把组织增长带来的复杂度和架构形态重新对齐。每次拆分之后别忘了补治理能力。没有日志、追踪、监控、配置和超时策略托底微服务只会把单体阶段的“大泥球”拆成“很多个小泥球”。微服务不是一颗银弹也不是一种潮流。它是一套用复杂度换可扩展性和团队自治的工程取舍。而读懂 Uber 这段历史最值得记住的一句话是架构演进本质上不是画图的智力游戏而是一场与组织复杂度的漫长对话。增长会推着你走你需要做的是在合适的时机、用合适的方法做出能回退、能验证、能积累经验的改变。