Uber 前 CTO 级技术负责人在公开分享里给过一个很反共识的总结:Uber 的微服务不是设计出来的,是被增长逼出来的。
这句话不是谦虚,也没有否定微服务,它讲的是一个非常现实的工程过程。Uber 最早用一套单体系统,就能跑通一个城市的实时派单;但当业务扩展到全球几百个城市、多条产品线、上千名工程师同时开发的时候,单体架构在代码维护、发布效率、团队协作和数据库扩展四个方向同时撞墙。于是团队开始一个一个地往外拆服务,先拆最痛的模块,再补基础设施,最后形成了一套大规模微服务架构。
对绝大多数开发者来说,Uber 这个案例比任何微服务教程都有价值,因为它回答了最实际的问题:微服务到底什么时候该上、怎么上、上了以后要付什么代价、故障怎么查、面试怎么答。
这篇文章会按这条线展开:先还原 Uber 单体架构为什么一开始很快、后来为什么顶不住;再拆解它被增长逼着走向微服务的真实路径;然后把微服务化的前置条件、迁移步骤、通信治理、架构图、性能观测、高频面试题和常见故障做一次系统梳理。适合正在维护单体系统的后端工程师、准备微服务改造的技术团队,以及备考微服务架构面试的开发者收藏阅读。
1. 微服务演进核心看点速览
在进入细节之前,先把 Uber 微服务演进这件事的关键信息列成一个速览表,方便你判断这篇文章讨论的到底是什么范围、和你关心的场景是否相关。
| 维度 | 关键信息 |
|---|---|
| 演进起点 | 单体应用,核心是一个 Python 编写的实时派单系统 |
| 演进动力 | 城市数量、业务线、团队规模同步扩张,单体先顶不住 |
| 服务规模 | 高峰时期 Uber 内部运行着 2000 到 2200 个微服务,这个数字在公开技术分享中被广泛引用 |
| 技术栈 | Go、Java、Python、Node.js 多语言并存,按服务特性选择语言 |
| 关键基础设施 | 服务发现、RPC 框架、配置中心、分布式追踪、消息队列、工作流引擎 |
| 主要代价 | 分布式事务、链路排障、依赖治理、资源开销、版本兼容 |
| 对普通团队的启示 | 先单体后拆分是常态;微服务不是"架构先进",而是"增长成本"的交换 |
| 适合读者 | 单体系统维护者、微服务改造团队、分布式系统学习者、后端面试备考者 |
这个表里最值得记住的一件事是:Uber 的核心系统最初是单体,不是微服务。微服务是它在快速增长过程中逐步演化出来的结果,而不是一开始就画好的蓝图。
2. 单体没做错什么:Uber 最初为什么能跑得飞快
很多文章讲到 Uber 的微服务,习惯从"单体很烂"讲起。但 Uber 技术团队自己的回顾口径恰恰相反:最早的单体架构在对应的发展阶段里是高效、合理的选择。
Uber 起家时,核心业务就是一件事:把乘客和司机撮合起来。整个系统的灵魂是一套实时派单模块,它维护着城市里所有司机的位置、状态、订单和行程状态机。派单逻辑本质上就是状态流转:空闲、接单、去接乘客、行程中、结束、支付。用一个 Python 进程把地图、定位、派单、计费、通知全部串在一起,在单一城市、低并发、小团队的情况下,开发和调试效率非常高。工程师改一行代码,跑一遍本地测试,部署上去就完事,几乎没有跨服务联调成本。
单体在这个阶段的优势是实打实的。业务逻辑在同一个进程内,事务天然一致,不需要考虑分布式事务;调试可以直接看堆栈,不需要链路追踪;部署就是一个进程,运维极其简单;团队规模小,代码冲突可控,发布节奏快。
也就是说,Uber 并不是一开始就判断"微服务更好",而是先老老实实把单体用到了极限。这给我们的第一个教训是:不要因为微服务流行就否定单体。单体架构本身不是反模式,持续膨胀且无人治理的单体才是。
3. 增长是怎么一步步逼垮单体的
Uber 的转折点,是城市数从几个变成几十个,再变成上百个、几百个。这里的难点不是并发量爬升那么简单,而是业务规则开始不受控制地膨胀。
每个城市都有不同的市场规则和产品形态。有的城市允许路边招手叫车,有的必须以预约为核心;不同城市对车型、价格、支付方式、司机资质的要求都不一样;同一个城市里还同时存在 UberX、UberBLACK、UberPool 等多条产品线。为了在单体里支撑这些差异,工程团队只能不断往代码里加 if/else、加配置开关、加特殊逻辑。派单模块逐渐变成了公开分享里提到过的"上帝对象":一个对象里装着所有行程、所有司机、所有订单的全局状态。
与此同时,数据库的压力也开始显现。Uber 早期把城市数据放在同一个 MySQL 集群里,数据量上来之后,单库连接数、主从延迟、慢查询、分库分表全部成为问题。更麻烦的是,当几百名工程师在同一个代码仓库里提交代码,一次发布就要把所有人的改动一起带上线,任何一个小模块出问题,整个派单系统都可能不可用。发布窗口越来越长,回滚越来越难,线上故障的爆炸半径越来越大。
这一段非常关键:微服务拆分表面上是在解决"代码结构"问题,实际上解决的是三个更深的问题——规则隔离、团队协作、故障隔离。代码乱只是症状,规则互相干扰、团队互相阻塞、故障互相传染,才是真正的病因。
4. 微服务不是设计出来的,是问题逼出来的
Uber 的拆分顺序在公开分享里有比较一致的描述:不是有人先画了一张理想架构图,然后按图施工,而是每个团队在具体业务压力下,把最影响迭代速度、最容易出问题的模块一个个独立出去。派单、计费、用户、司机、支付、通知、地图服务,都是先有独立部署的诉求,才逐步发展成独立服务。
每个独立服务的诞生,都对应着一个具体痛点。派单模块要按城市水平扩展,需要独立部署、独立扩缩容;计费逻辑经常要按城市调价格,不能每次改价都触发全系统发布;支付需要对接多个第三方渠道,必须保证高可用和幂等,不适合和派单耦合在一起;地图和路径规划计算量大,需要单独的服务和资源池。
这种"从痛点出发、按业务域拆分"的方式,才是微服务正确落地的姿势。反过来讲,微服务最容易失败的方式,是架构师先画图、定规范、切边界,然后强行推动一刀切拆分。这种"设计出来的微服务",往往会得到一堆无人维护、调用关系混乱的分布式单体。
这里要区分一个概念:Uber 拆出来的东西,是"能独立演进、独立部署、独立故障恢复"的服务,而不是把单体里的一个类、一个模块直接挂上 HTTP 接口就完事。如果只是换了个调用方式,数据库还共用一个,业务状态还互相依赖,那跟"分布式单体"没有区别。很多团队拆了微服务之后反而更慢,原因就在这里:服务边界没有按业务域切,数据没有真正隔离,调用链变长了,复杂度一点没减少。
5. 微服务适用场景、前置条件与使用边界
不是所有团队都该学 Uber。微服务化有一套明确的前置条件,缺了任何一个,拆分方案都很容易翻车。
第一是组织条件。康威定律在这里是硬约束:微服务的边界会很快对齐到团队边界。如果团队还是一个大后端组,十个人互相改同一个仓库,拆出来的服务反而会增加沟通成本。比较健康的节奏是"服务跟着团队走"——一个服务对应一个长期负责的小团队,团队对服务的可用性、性能、迭代全权负责。
第二是工程基础设施。微服务上线后,代码能独立部署只是开始,真正消耗精力的是服务发现、配置中心、网关、日志、监控、链路追踪、CI/CD 流水线。这些设施在单体阶段可以没有,在微服务阶段几乎缺一不可。Uber 当年拆得快,本质上也付出了大量自研基础设施的代价,包括服务发现框架、RPC 框架、分布式追踪系统和工作流引擎。
第三是数据能力。微服务化的最大难点从来不是接口,而是数据。一个服务对应一个数据域,用户数据、订单数据、资金数据要逐步从共享库中拆出去。拆分期间要考虑数据一致性、历史数据迁移、双写、回滚预案,这些都需要专门的数据工程能力。
如果出现下面这些信号,说明现在不应该拆:团队规模不到两位数,单体开发效率并没有遇到瓶颈;业务模型还没验证清楚,需求还在快速变化;没有配套的监控、日志、CI/CD 体系;只是觉得微服务更高级,或者想写进简历。这种情况下,更务实的选择是先维护好一个结构清晰的单体,或者采用模块化单体,等痛点出现再拆。
使用边界同样要讲清楚。微服务擅长解决的问题是:大团队并行开发、多业务线规则隔离、关键模块独立扩缩容、故障局部化。它不擅长解决的是:小团队快速验证业务、强一致事务密集场景、数据边界天然耦合的场景。涉及用户手机号、定位、支付账号等敏感数据时,拆服务还必须做数据分类和权限隔离,敏感字段加密存储、脱敏展示、访问审计,跨服务传递遵循最小必要原则。Uber 自身在数据治理上也有过公开的教训,这一点不能因为它在技术上成功就忽略。
6. 从单体到微服务的迁移路径与落地策略
结合 Uber 的案例,大多数团队的微服务迁移都适合采用"绞杀者模式":新服务在老的单体旁边长出来,流量逐步切换,老单体逐步废弃,而不是一次性重写。
一个通用迁移步骤是这样的:选一个边界清晰、变化频繁、对稳定性和扩展性要求高的业务域作为第一个拆分对象,比如计费、支付、通知;先把单体内部对该模块的调用抽象成接口,出口定义清楚;把这个模块抽成独立服务,对外提供 HTTP 或 gRPC 接口;采用双写或者读流量灰度,逐步把调用切到新服务;确认稳定后,把老代码从单体中删除。
第一步最重要的工作是定义接口契约。接口契约相当于两个团队之间的合同,一旦定下来,后续的字段新增、版本升级都围绕契约展开。一个简化版的 OpenAPI 契约示例:
openapi: 3.0.0 info: title: Billing Service version: v1 paths: /api/v1/bills: post: summary: 创建账单 requestBody: required: true content: application/json: schema: type: object required: [trip_id, amount, currency] properties: trip_id: type: string amount: type: number currency: type: string discount_code: type: string responses: '200': description: 创建成功,返回账单ID content: application/json: schema: type: object properties: bill_id: type: string status: type: string这只是契约模板,实际项目中需要根据业务场景补充鉴权、幂等键、错误码和限流字段。契约定义好后,服务内部怎么实现、用什么语言,都可以独立决策,这正是微服务"技术异构"价值的来源。
服务独立之后,单体里的内部调用就要替换成网络调用。以 Python 单体为例,拆分前可能是这样的:
# 拆分前:单体内部直接调用 from billing import create_bill bill = create_bill(trip, amount)拆分后变成调用独立计费服务:
# 拆分后:调用独立计费服务 import requests def create_bill(trip, amount, idempotency_key): resp = requests.post( "http://billing-service/api/v1/bills", json={"trip_id": trip.id, "amount": amount}, headers={"X-Idempotency-Key": idempotency_key}, timeout=2 ) resp.raise_for_status() return resp.json()["bill_id"]注意这里两个细节:一是超时必须有上限,二是调用必须带幂等键。在单体时代,一次方法调用失败可以立刻重试;在分布式环境下,一次请求可能已经到达对端但对端处理超时,不带幂等键的重试会导致重复创建账单、重复扣款。这种"先抽象调用、再抽服务、再切流量"的顺序,比一次性重写安全得多,也是 Uber 这类案例给到的最通用经验。
7. 服务间通信与 API 治理
微服务从两个服务开始,服务间通信就会成为日常问题。通信方式的选择原则可以简单归纳为:强一致、实时性要求高的场景,用同步调用,比如查询订单详情;允许最终一致、对实时性不敏感的场景,用异步消息,比如发送通知、更新搜索索引。
同步调用必须配三件套:超时、重试(带幂等)、熔断。异步消费必须配消息幂等和顺序保障。Uber 这种规模下的真实教训是,任何一个依赖服务的抖动,如果不做保护,都会顺着调用链一路传导,最终表现为全线超时甚至服务雪崩。
API 网关在微服务架构里承担统一出入口的角色:路由转发、身份认证、限流、灰度、流量审计都在这一层处理。一个常见的网关路由配置示例:
spring: cloud: gateway: routes: - id: billing-service uri: lb://billing-service predicates: - Path=/api/v1/bills/** - id: user-service uri: lb://user-service predicates: - Path=/api/v1/users/**这段配置的意思是:/api/v1/bills/**的请求转发到注册中心里名为 billing-service 的服务,/api/v1/users/**的请求转发到 user-service。lb://前缀表示走客户端负载均衡,从注册中心动态获取实例列表。
服务发现是微服务基础设施中最基础的一环。每个服务启动时把自己注册到注册中心,调用方从注册中心拿实例列表,再结合负载均衡策略发起调用。国内团队常用的开源方案是 Nacos 或 Consul,如果技术栈是 Spring Cloud Alibaba,通常就是 Nacos + OpenFeign + Sentinel 的组合。服务发现配置一般是这样的模板:
spring: application: name: billing-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848 namespace: dev在真实项目中,注册中心地址、命名空间、鉴权信息都需要按环境替换。配置中心单独管理,数据库连接串、开关、限流阈值这类配置不要写死在代码里。
更稳妥的判断是:微服务的通信问题,本质上是把单体内部的"函数调用"升级成了"服务调用"。原来由编译器保证的东西,现在要靠超时、重试、熔断、幂等、注册中心、网关来保证。这一层不做扎实,服务拆得越多,线上越不稳定。
8. 微服务架构图与典型参考架构
很多人在搜索"微服务架构图"的时候,其实是想知道:一套标准的微服务架构,从上到下应该有哪些层、每个层放什么组件。结合 Uber 案例和国内主流开源方案,下面这个分层结构可以作为参考:
客户端 / 外部系统 ↓ 接入层:API 网关(鉴权、限流、路由