微服务(一):架构认知、拆分方法与服务自治
微服务(一):架构认知、拆分方法与服务自治
导语:微服务面试最容易失分的地方,不是"会不会用 Nacos / Gateway",而是说不清它为什么存在、代价是什么、边界怎么划。本篇先把架构演进讲清楚(单体 → SOA → 微服务 → 云原生),再给出拆分方法论(DDD 限界上下文)、粒度判断标准与"分布式单体"的自检清单,然后回答两个高频决策题:什么时候不该上微服务、数据该不该共享库,最后以"编排 vs 协同"收尾,共 7 题。
一、架构认知
1. 什么是微服务架构?它与单体架构、SOA 有什么区别?
答: 微服务架构是一种把应用拆成一组围绕业务能力构建、可独立部署、去中心化治理的小型服务的架构风格。业界引用最多的是 Martin Fowler & James Lewis 的定义,可归纳为 6 个特征:
| 特征 | 含义 |
|---|---|
| 围绕业务能力组织 | 按"订单、库存、支付"这类业务边界划分,不是按技术分层 |
| 独立进程与独立部署 | 每个服务有自己的进程、代码仓库、构建与发布流水线 |
| 去中心化治理 | 没有统一 ESB 强制规范,各服务可自选技术栈与存储 |
| 去中心化数据管理 | 每个服务拥有自己的数据(Database per Service),不共享库 |
| 轻量级通信 | HTTP/REST、gRPC、Dubbo 或消息中间件,而非重型 ESB 协议 |
| 面向失败设计 | 默认网络与依赖不可靠,内建超时、重试、熔断、降级 |
架构演进图:
三者的关键差异:
| 维度 | 单体架构 | SOA | 微服务 |
|---|---|---|---|
| 部署粒度 | 整体打包部署 | 服务粒度,但常需整体协调 | 每个服务独立部署 |
| 通信方式 | 进程内方法调用 | ESB 总线(集中式、重) | 轻量协议直连 / MQ |
| 治理中心 | 无 | 强中心化(ESB 是瓶颈) | 去中心化 |
| 数据 | 共享一个库 | 常共享库 | 服务各自持有数据 |
| 技术栈 | 统一 | 一般统一 | 允许异构 |
| 典型问题 | 代码耦合、发布互相阻塞 | ESB 成为性能与演进瓶颈 | 分布式复杂度、运维成本高 |
三个高频误区:
- "微服务 = Spring Cloud / Dubbo":错。框架只是实现工具;用 Spring Cloud 但共享一个库、必须一起发布,那是穿着微服务外衣的单体。
- "拆得越细越好":错。粒度越细,网络跳数、排障成本、一致性成本越高(见第 4 题)。
- "SOA 就是微服务的旧名字":错。SOA 强调集成与复用(常伴 ESB 与共享数据模型),微服务强调自治与独立演进,二者治理方向相反。
答题加分点:追问"微服务到底解决了什么问题?"——标准答法是解决规模化研发的协作与交付效率问题(团队自治、独立发布、按需伸缩),而不是"性能问题"。微服务通常会让单次调用变慢,换来的是组织可扩展性。
2. 微服务架构有哪些优点?代价(缺点)是什么?
答: 微服务的价值几乎都来自"自治",代价几乎都来自"分布式"。
优点:
- 独立部署与交付:改订单服务不用发整站,发布频率提升。
- 故障隔离:一个服务 OOM 不会拖垮全部(前提是有超时/熔断,否则反而雪崩)。
- 按需伸缩:热点服务(如商品详情)单独扩容,不必整体扩容。
- 技术异构:不同服务可以选不同语言、不同存储(如搜索用 ES、关系数据用 MySQL)。
- 团队自治:小团队端到端负责,符合康威定律。
- 可维护性:单个服务代码量小,新人上手快。
代价("微服务税"):
| 代价 | 具体表现 | 应对 |
|---|---|---|
| 分布式复杂度 | 网络超时、部分失败、乱序、重复 | 超时、重试、幂等、熔断 |
| 数据一致性难 | 本地事务失效,需要最终一致 | Saga / 事务消息 / 对账(见 Seata 篇) |
| 排障困难 | 一次请求跨十几个服务 | 全链路追踪 + 统一日志(见第 15 题) |
| 运维成本陡增 | 服务数 × 实例数 × 环境数 | CI/CD、容器编排、配置中心 |
| 测试困难 | 端到端测试依赖全链路环境 | 契约测试 + 泳道环境 |
| 接口兼容负担 | 提供者与消费者版本错配 | 向后兼容、契约测试、灰度 |
| 网络性能损耗 | 序列化 + 网络跳转 + 多跳放大 | 批量接口、并行调用、避免 N 跳链 |
关键结论:微服务降低的是"认知复杂度",提高的是"运维复杂度"。如果团队的运维、监控、自动化能力跟不上,上微服务是净亏。
二、拆分方法
3. 微服务该如何拆分?DDD 限界上下文怎么落地?
答: 主流的正确姿势是以业务语义而非技术分层划分边界,DDD(领域驱动设计)提供了可操作的方法论。
拆分的层次:
- 子域:把领域切成块,并区分投入优先级——核心域(决定竞争力,重投入自研,如交易、风控)、支撑域(自研但可简化,如库存)、通用域(直接外购/开源,如认证、通知)。
- 限界上下文:术语与模型自洽的边界。同一个词在不同上下文含义不同——"商品"在商品上下文是 SPU/SKU 描述,在订单上下文是下单时的价格快照,在库存上下文是可用库存数。同一个词在不同上下文含义不同,就说明这里该有一条边界。
- 聚合根:上下文内部的一致性单元,是本地事务的边界,也是服务内拆分模块的依据。
可执行的拆分顺序(实操建议):
- 先按"变化频率"和"伸缩需求"找缝:把迭代最快、流量特征最特殊的模块先剥出去(例如营销活动、搜索、报表)。
- 再按数据所有权收口:确保被拆出的模块能独占自己的表。
- 最后按组织架构对齐:让服务边界与团队边界一致(康威定律)。
拆分优先级示例(电商):
反直觉提醒:核心域(订单、支付)往往最不该先拆——它稳定、一致性强、改动风险高。先拆边缘模块,团队积累分布式经验后再动核心。
4. 服务粒度怎么把握?什么是"分布式单体"?
答: 粒度没有固定数字(不是"多少行代码"),用三条判断标准衡量:
- 数据自治:这个服务能独占自己的数据吗?如果必须和其他服务共享表,边界划错了。
- 独立发布:改一个常见需求,能否只改这一个服务并单独发布?如果每次都要改 N 个服务一起发,边界划错了。
- 变更收敛:需求的变更是否总在同一边界内?如果一个需求频繁横跨多个服务,边界划错了。
粒度过大 vs 过小:
"分布式单体"(Distributed Monolith) 是微服务落地最常见的失败形态:物理上拆成了多个服务,逻辑上仍然强耦合。
自检清单(命中 2 条以上就要警惕):
康威定律:系统的架构会趋同于组织的沟通结构。 如果两个团队共管一个服务,边界必然模糊;反之可以用逆康威定律——先按期望的架构调整团队划分,让架构自然长成期望的样子。
5. 什么时候不该上微服务?
答: 这是面试中非常常见的"反向题",答好它说明你见过真实项目。
决策树:
明确不建议上微服务的场景:
| 场景 | 原因 |
|---|---|
| 创业早期 / 业务形态未定型 | 领域边界一天一变,拆了就得反复合,成本极高 |
| 小团队(几个人) | 服务数超过人数,每个人要运维一堆服务 |
| 强事务、强一致的核心系统 | 分布式事务成本高,收益低(如账务核心) |
| 性能敏感的低延迟链路 | 网络跳转带来的延迟可能不可接受 |
| 没有运维/监控基建 | 出问题无法定位,等于"盲飞" |
更好的替代品:模块化单体(Modular Monolith)——
在一个进程内严格划分模块边界(模块间只通过接口交互、数据互相隔离、禁止跨模块直接访问表),享受微服务的可维护性,不付分布式的税。等边界稳定、团队扩大后,模块可以低成本地"长出"为独立服务。
答题加分点:成熟的回答是"先单体后微服务"——Martin Fowler 也建议几乎所有微服务成功案例都始于一个被拆掉的单体,而从零开始的微服务项目失败率更高。
三、数据与协作
6. 什么是 Database per Service?共享数据库为什么是反模式?
答: Database per Service(每个服务独占数据) 是微服务数据治理的基本原则:服务的数据只能通过该服务的 API 访问,其他服务不得直接读写它的库表。
共享库的危害:
- 耦合泄漏:一个服务改表结构,所有依赖它的服务都要改——这直接摧毁"独立部署"。
- 无法独立伸缩与选型:数据库成为共享瓶颈,也无法为报表单独选 OLAP 存储。
- 一致性边界失控:任何人都能绕过服务直接改数据,业务规则(校验、状态机)形同虚设。
- 故障扩散:一个服务的慢 SQL / 锁表拖垮全部。
拆库之后的三个现实问题与解法:
| 问题 | 解法 |
|---|---|
| 跨服务查询(如"我的订单页要展示商品名") | API 组合(订单服务调商品服务补齐字段)、CQRS 读模型(冗余一份宽表) |
| 跨服务事务(下单 + 扣库存) | Saga / 事务消息实现最终一致(详见本站 Seata 系列) |
| 跨服务数据分析 | 建数仓/读模型,由 CDC 或事件同步,而不是在线库直接 JOIN |
注意:如果两个服务共享的表被高频 JOIN,且业务上强一致,通常说明它们本就不该拆开。此时应合并服务,而不是加分布式事务。
7. 服务编排(Orchestration)与协同(Choreography)有什么区别?怎么选?
答: 二者是跨服务业务流程的两种组织方式。
| 维度 | 编排 Orchestration | 协同 Choreography |
|---|---|---|
| 控制方式 | 集中式,有一个编排器显式调用各服务 | 去中心,各服务订阅事件自行响应 |
| 流程可见性 | 高——流程定义集中,一眼看全 | 低——逻辑散落在订阅关系里,难追溯 |
| 耦合度 | 编排器依赖所有参与者(时序耦合) | 服务只依赖事件契约(时间/空间解耦) |
| 补偿处理 | 编排器统一驱动回滚,清晰 | 需要"反向事件链",复杂且难验证 |
| 新增环节 | 改编排器即可 | 加一个订阅者,对上游无侵入 |
| 单点风险 | 编排器挂了流程就停(需高可用) | 无单点,但排障靠追事件链 |
| 典型实现 | 工作流引擎(Flowable/Camunda)、Seata Saga 状态机 | MQ 事件驱动、Kafka Streams |
选型建议:
- 长流程、有状态、需要强补偿的业务(下单、退款、开户审批)→ 编排。流程复杂时,事件链会变成"没人知道整体流程长什么样"的噩梦。
- 简单的一对多通知、可独立演进的旁路逻辑(下单后发券、加积分、发通知、更新搜索索引)→ 协同。
- 工业界主流是混合式:外层用编排器管主干流程,内层用事件驱动旁路。既保证主干可见可控,又保持旁路灵活。
答题加分点:追问"协同模式下怎么看到完整流程?"——答:给每次业务流转带上全局业务 ID(如 orderId + traceId),通过链路追踪/事件日志把散落的事件串起来,否则线上排障只能靠猜。
