核心心法:拆分本身不产生价值,只产生成本。 没有驱动力的拆分,全都是在制造技术负债。
一、 为什么拆:先确认有没有"驱动力"
在动手之前,必须确认业务或技术现状是否命中了以下 5 个驱动力之一。如果没有,坚决不拆:
| 驱动力 | 典型信号 |
|---|---|
| 组织扩张 | 10+ 人改同一个代码库,合并冲突天天有,发布排队 |
| 变更频率差异 | 报表业务一周改 5 次,交易核心一季改 1 次,却必须捆绑发布 |
| 伸缩性差异 | 只有查询/大促接口需要扩容,却要带着整个单体应用一起扩容 |
| 故障隔离 | 非核心功能(如通知、日志)挂了,把核心交易链路拖死 |
| 技术异构 | 时序数据需要 ClickHouse,检索需要 ES,强塞进一个 MySQL |
⚠️ 拆分的代价清单(动手前必须算清):
分布式事务难题、网络不可靠(超时/重试/幂等)、跨服务 Join 限制、调用链路变长、运维复杂度指数级上升、契约管理成本、团队协作沟通成本。
一句话判据:如果拆分解决不了上述 5 个驱动力中的任何一个,就别拆。
二、 怎么拆:四步法定边界
第 1 步:按"业务能力"切第一刀(绝不是按技术层)
列出"企业为了交付价值必须做的事",得到粗粒度能力清单。
-
支付域示例:收单 → 渠道路由 → 渠道适配 → 清结算 → 账簿 → 对账 → 商户结算 → 退款/拒付 → 风控。
-
🚫 绝对避坑 :绝不按
controller / service / dao分层切,那是"分布式单体"最常见的死法。
第 2 步:用 DDD 子域决定"投多少资源"
不同子域的价值不同,投入的资源也要有差异:
| 子域类型 | 判断标准 | 策略 |
|---|---|---|
| 核心域 | 构成企业核心竞争力(如清结算引擎、风控规则) | 自研,重兵投入,精细拆分 |
| 支撑域 | 必须有但无差异化(如工单、消息中心) | 一个服务包一层,甚至外采 |
| 通用域 | 到处都是(如短信、邮件、认证) | 直接买(SaaS) |
很多团队失败在这一步:不分主次,把所有服务都当核心域拆,导致几十个微服务只有几个人维护,80% 的精力耗在基础设施上。
第 3 步:用限界上下文(Bounded Context)定边界
如何识别边界?看三个可观测信号:
-
语言歧义:同一个词在不同上下文含义不同(交易域的"订单"和支付域的"订单"是两个对象)。
-
事务一致性:一组数据总在同一个事务里被修改。
-
变更共同体:一组功能总被同一批人、在同一天修改。
第 4 步:用"数据所有权"做最硬的验收
一个服务 = 一组必须放在同一事务里的数据 + 一组总被一起读写的表。
以下三条检验,有一条不通过,就说明边界划错了:
-
❌ 一张表被两个服务写入。
-
❌ 两个服务之间需要做数据库 Join 查询(要么拆错了,要么该做数据冗余)。
-
❌ A 服务直接读 B 服务的表(必须改为 API 调用或事件订阅)。
三、 粒度控制:6 条硬判据与红线
拆分粒度是微服务设计的深水区,请严格对照以下指标:
| 维度 | 判据 | 红线警告 |
|---|---|---|
| 组织 | 一个服务由一个团队(5~8人)完整负责(含 On-call) | 3 个团队才能改一个服务 = 太细 |
| 事务 | 需要强事务的放一起 | 拆开后要用 TCC 的,先问"真的需要吗" |
| 变更 | 一起变化的放一起,发布频率是量化指标 | 发布频率差 10 倍的硬凑一起 = 太粗 |
| 数据 | 存在大量跨表 Join + 强一致读 | 先别拆,或做数据冗余/读模型 |
| 性能 | 核心链路同步调用深度 ≤ 3~4 层 | 请求跨 10 个服务 = 故障率指数级上升 |
| 故障 | 核心与非核心物理隔离 | 报表挂了不能影响交易主链路 |
💡 经验值:宁可要 10 个大服务,不要 50 个小服务。 初期一个服务 1~3 个开发,超 8 人再拆。
四、 细节落地:真正会翻车的 5 个重灾区
1. 数据拆分(90% 的问题都在这里)
-
底线 :独立库/独立 schema,严禁跨库 Join ,严禁共享表。
-
跨服务查询四种解法:API 组合 / 字段冗余(宽表) / CQRS 读模型 / 数据同步到 ES。
-
跨服务事务优先级(从高到低):
-
业务上消灭(把强一致的合并成一个服务) ← 最优解,很多人想不到
-
本地消息表 + 最终一致(大多数场景够用)
-
SAGA(长流程、可补偿)
-
TCC(资金类,成本最高)
- 🚫 绝对不要用 XA/2PC ------ 性能与可用性双杀。
-
-
数据迁移顺序 :双写 → 灰度读 → 数据校验 → 切写 → 停双写(校验必须有 count/checksum/抽样比对,不能靠"看起来对")。
-
顺序警示:先做服务拆分,再做分库分表。两个维度混在一起做,路由会爆炸。
2. 接口契约(防扯皮神器)
-
契约先行(OpenAPI/Protobuf),字段只增不删 ,做好
/v1/版本化。 -
绝不暴露数据库实体,DTO 必须独立(否则对方一改表你就崩)。
-
写接口必须支持幂等键(Idempotency-Key),支付场景是硬要求。
-
契约里写清超时、重试、退避的约定(谁发起重试谁负责)。
-
分页用游标(cursor),避免深分页拖垮数据库。
3. 调用可靠性(拆分后故障点翻倍)
-
超时层层递减:下游超时 < 上游超时,必须有默认值。
-
重试:只重试幂等操作,指数退避 + 随机抖动(防重试风暴)。
-
熔断与隔离:按接口维度配熔断;慢接口独立线程池/信号量,避免共用导致级联雪崩。
-
限流双层:网关入口 + 服务自身(防上游打爆)。
-
幂等三件套:唯一请求号 + 状态机 + 乐观锁。
4. 可观测性("查不到问题"比"有问题"更可怕)
-
TraceId 全链路贯穿:跨 MQ 必须透传(很多团队漏掉这条)。
-
日志统一格式 + 聚合(ELK)。
-
RED 指标(Rate/Errors/Duration)+ 业务指标。
-
告警按 SLO,不要按 CPU 使用率。
5. 组织与基础设施(决定生死)
-
服务边界必须与团队边界对齐(康威定律,反着来一定痛苦)。
-
一个服务一个 owner + 明确 on-call 机制。
-
如果没有配置中心、注册中心、网关、CI/CD、监控、链路追踪------别拆微服务,否则团队 80% 时间在建轮子。
6. 落地节奏:绞杀者模式,绝不重写
单体内部先模块化(包边界 + 禁止跨包访问) → 抽边缘低耦合服务(通知、文件、日志) → 抽核心(交易、账户),加防腐层 ACL 做双跑对比 → 灰度切流量 → 下线旧逻辑。
注:一次性重写微服务项目,失败率高得惊人。
五、 反模式清单(面试直接背)
-
按技术分层拆(Controller 服务 / Service 服务 / DAO 服务)。
-
共享数据库,服务之间直接读表。
-
分布式单体:服务拆了,但发布必须一起(等于没拆)。
-
贫血服务:只有 CRUD,业务逻辑散在上层编排里。
-
Nano-service:一个实体一个服务。
-
按人数硬拆:三个人拆三个服务。
-
没有基础设施就硬拆微服务。
-
为了技术先进而拆。
六、 实战案例:如何讲出你的真实边界
用业务场景来解释边界,能瞬间拉开你与普通面试者的差距:
车联网场景(真实边界)
| 服务 | 为什么独立 | 边界依据 |
|---|---|---|
| 车辆接入 (MQTT/TBOX) | 长连接、海量会话、状态在内存 | 伸缩性 + 故障隔离 |
| 数采任务配置 | 低频写、高频读,强缓存 | 变更频率差异 |
| 诊断会话 (UDS/DoIP) | 有状态机、长事务、超时敏感 | 事务边界 |
| 时序数据 (ClickHouse) | 存储异构、写入吞吐极高 | 技术异构 |
| 告警策略引擎 | 规则频繁变更、可配置化 | 变更频率 |
| 线索运营 | 面向业务、查询复杂 | 业务能力 |
支付场景(可迁移版本)
-
渠道适配必须独立(通道天天加,适合插件化)。
-
清结算和账簿不要拆(强一致,放一个事务里)。
-
对账独立(批处理、独立库、可重跑)。
-
收单独立(高并发、无状态、易扩容)。
七、 面试回答模板(两分钟高分版)
【开场点题】
我认为做微服务拆分,第一步不是画架构图,而是看有没有驱动力(组织扩张、变更频率差异、伸缩性、故障隔离、技术异构)。如果都没有,拆分就是负债,不如保持模块化单体。
【拆分方法论】
确定要拆后,我会分四步走:首先按业务能力 切第一刀;接着用 DDD 子域 决定投入资源(核心域重兵自研,支撑/通用域轻量化或外采);然后用限界上下文 定边界;最后用数据所有权做最硬的验收------一张表只能有一个服务写,跨服务绝对不 Join。
【粒度控制】
粒度上我会卡死三条硬线:一个团队(5-8人)完整负责、核心链路同步调用不超过 3 层、需要强事务的绝不拆开。宁可 10 个大服务,不要 50 个小服务。
【落地与避坑】
落地时必须用绞杀者模式 渐进演进,先模块化再抽边缘服务。全程独立库、契约先行、做好幂等和超时约定。
我总结最容易翻车的三个点:跨服务事务滥用 TCC/2PC、直接暴露数据库实体、以及在没有可观测性和基础设施(CI/CD、链路追踪)的情况下盲目开拆。