Skip to content

微服务架构与拆分 ​

讲清微服务"怎么拆、怎么连、怎么保持一致、何时别拆"的决策方法论,与组件用法笔记互补,给出可直接照做的拆分与取舍框架。


一、拆分方法论 ​

1.1 拆分的本质 ​

微服务拆分不是技术动作而是管理复杂度的动作:把"一个大泥球"切成"多个边界清晰的小世界",每个小世界可以独立演进。判断一次拆分成功与否,标准只有一个——是否让某个维度的复杂度下降了(发布更快、故障更小、协作更顺),而不是"服务数量变多了"。

1.2 四个拆分驱动视角 ​

视角核心问题适用信号
按业务能力业务上有哪些独立能力域模块间职责重叠、需要独立演进
按子域(DDD)领域边界画在哪规则复杂、需要模型自治(见《DDD领域驱动设计》)
按团队结构谁拥有什么(康威定律)团队间提交冲突、发布互相阻塞
按变更频率哪些代码总是一起变一处改动高频牵连多处回归

四个视角不是并列选项而是校验组合:一般先用"业务能力/子域"画出候选边界,再用"团队结构"检查是否有人长期拥有,最后用"变更频率"验证——如果两个候选服务每两个迭代就要一起改,就该合并。

1.3 按业务能力拆分 ​

把业务拆成能力域,如电商:用户、商品、订单、库存、支付、营销、售后。

  • 优点:边界直观、业务团队可认领;
  • 风险:容易只按"名词"切(订单、商品),忽略"动词/流程"(下单链路、履约链路),造成跨服务编排泛滥。

1.4 按子域拆分(DDD 视角) ​

以限界上下文为边界来源(详版见《DDD领域驱动设计》),要点:

  1. 同一限界上下文内的数据由同一服务独占(数据不共享是拆分的底线);
  2. 一次事务只改一个聚合 → 一个聚合归属一个服务,跨服务事务尽量消灭;
  3. 上下文间用防腐层/开放主机服务协作。

1.5 按团队结构与康威定律 ​

康威定律:系统结构会镜像组织沟通结构。反向利用它:

  • 服务边界与团队归属对齐,一个服务(或一组内聚服务)一个团队长期负责("你拆的你维护");
  • 若团队按"前端组/后端组/数据库组"划分,强行上微服务只会得到"分布式的单体加更长的沟通链";
  • 反模式:"架构组画完服务边界,分配给各团队"——边界必须由未来的服务 Owner 参与共同决定。

1.6 按变更频率拆分 ​

  • 变更频率 + 发布节奏:低频稳定能力(如账号注册)与高频迭代能力(如营销活动)混在一起,是发布冲突的根源;把它们分开,高频服务可独立发版;
  • 判断工具:看两个模块的历史共同变更率——共同变更率超过 70% 的模块应属同一服务。

1.7 拆分边界权衡(粒度) ​

维度拆得太粗(大单体)拆得太细(微单体粒度过碎)
部署发布互相阻塞一次发布涉及几十个服务编排
数据单一库便于事务跨服务调用链深、延迟叠加
运维定位问题难实例数爆炸、监控告警疲劳
成本一个超大库难扩展基础设施成本随服务数线性上涨

建议的粒度判据:一个服务应做到"团队规模与业务复杂度匹配 + 能在 1 个迭代内独立发布 + 内部聚合内聚、对外 API 精简"。实践上 3~9 人团队负责 1~3 个服务是比较稳的配比。

1.8 拆分实操步骤 ​

  1. 从高变化高耦合的模块开始试点(不要一次全拆);
  2. 先代码库内分包(模块化单体),稳定 2~4 周验证边界;
  3. 再拆数据库(表归属到服务,是最痛的一步,需处理历史关联查询);
  4. 服务独立部署、注册进注册中心、切流量灰度;
  5. 每拆一个就复盘"发布频率/故障半径/团队协作"三个指标是否变好。

1.9 拆分正确性校验清单 ​

  • [ ] 每个服务数据独占,无跨服务 join 的强需求;
  • [ ] 一次用户请求默认只穿 1~2 个服务(超过 3 个是坏味道);
  • [ ] 每个服务能被一个团队长期拥有;
  • [ ] 拆分后高频变更服务能独立发版;
  • [ ] 没有为拆分引入"分布式事务才能完成"的新用例。

二、服务间通信 ​

2.1 通信方式全景 ​

方式典型实现特点
同步 RESTSpring MVC + Feign简单直观、易于调试、强实时
同步 RPCgRPC / Dubbo高性能、强类型、支持流式
异步消息RocketMQ / Kafka / RabbitMQ削峰、解耦、最终一致
事件驱动领域事件 + MQ天然贴合 DDD 边界(见 DDD 笔记)

2.2 同步:REST vs gRPC ​

维度REST(HTTP+JSON)gRPC(HTTP/2+Protobuf)
契约OpenAPI/Swagger 文档.proto 强类型定义
序列化JSON(可读、略慢、体积大)Protobuf(二进制、快、体积小)
生态浏览器友好、调试工具多需 grpcurl/反射等工具链
浏览器直连支持不支持(需 grpc-web 网关转换)
适用对外 API、异构系统、调试频繁的场景内部服务间、高吞吐、低延迟、强类型

选型结论:对外与跨语言边界用 REST;对性能敏感且工具链齐全的内部南北向/东西向调用用 gRPC。规模不大时统一 REST 更省心。

2.3 异步:消息队列 ​

消息的核心价值是削峰、解耦、异步化(用法与产品对比见《消息队列》笔记)。本笔记强调三个系统性决策:

  1. 削峰:请求先落 MQ,消费端按能力匀速消费(秒杀等场景见《经典系统设计案例》);
  2. 解耦:核心链路只发事件,扩展动作(通知、审计、风控)通过订阅接入;
  3. 缓冲:瞬时流量打平,避免下游被冲垮。

使用 MQ 必须同时设计三种兜底:消息不丢(生产端确认 + 消费端手动 ack)、消息不重(消费幂等)、消息不乱序(分区键按业务主键路由)。

2.4 同步与异步的选型决策 ​

判断顺序:

  1. 调用方是否必须立刻拿到结果 → 是则同步(或同步 + 异步兜底);
  2. 结果可容忍延迟(秒级~分钟级)且失败可重试 → 倾向异步;
  3. 是否需要削峰/解耦/广播 → 异步;
  4. 是否强一致跨服务写 → 都不行,先看能否合并事务(见第六章)。

一个实用经验:查询走同步、命令走异步。读链路(订单详情聚合)用同步拼装;写链路(下单后发券、发短信、记流水)用事件异步化。

2.5 服务调用链的降级思考 ​

无论同步还是异步,调用链上都要回答三个问题:下游超时怎么办(超时时间 + 重试上限)、下游挂了怎么办(降级预案)、请求激增怎么办(限流/熔断,见《高并发系统设计》第六章)。


三、注册中心与服务发现 ​

3.1 注册中心解决什么问题 ​

服务实例的地址是动态的(扩缩容、故障替换),调用方不能写死 IP。注册中心提供:服务注册、健康检查、地址发现、负载均衡的实例列表来源。是否引入注册中心取决于是否有"动态实例"需求——单机部署或 K8s 场景可交给平台能力。

3.2 主流方案对比 ​

维度NacosEurekaConsulKubernetes(K8s DNS/SVC)
部署形态独立中间件(需高可用集群)独立中间件独立中间件平台内置(etcd + DNS + Service)
一致性AP + 配置中心(CP 可选)AP(自我保护)CP(Raft)平台保证
健康检查心跳 + 主动探测心跳(自我保护)心跳 + HTTP/gRPC 探针存活/就绪探针
配置中心内置(强项)无(需配 Spring Cloud Config)内置 KVConfigMap/外部配置中心
多环境/命名空间Namespace/Group 完善弱弱Namespace 按集群
适用Spring Cloud Alibaba 栈、需要配置中心一体Spring Cloud Netflix 传统栈多数据中心、对一致性敏感已容器化/K8s 化、无需自建注册中心

3.3 注册中心选型决策 ​

  1. 已容器化并上 K8s → 优先用 K8s 服务发现,不要再自建一套注册中心(除非业务需要 Spring Cloud 生态的配置/网关联动);
  2. Spring Cloud Alibaba 技术栈 → Nacos(注册 + 配置一体,运维成本最低);
  3. 存量 Eureka 栈 → 可继续维护,但新服务不建议再引入(维护停滞);
  4. 需要严格一致的选主/强一致场景 → Consul。

组件配置方法见《SpringCloud微服务》笔记,本笔记不再重复。

3.4 服务发现的常见故障模式 ​

  • 脑裂/假死:网络分区时注册中心把健康实例误判——Nacos 的保护阈值、Eureka 的自我保护机制正是为此设计,不要轻易关闭;
  • 慢启动被打流量:实例注册即被分配流量,但 JVM 尚未热身——配合预热与最小连接数负载均衡;
  • 注册中心抖动雪崩:注册中心不可用时调用方全挂——客户端必须有本地缓存兜底 + 快速失败。

四、网关层与配置中心 ​

4.1 网关的职责清单 ​

网关是南北向流量的统一入口,职责应克制在横切能力:

职责说明是否必须
路由转发按 URL/Header 分发到服务是
统一鉴权Token 校验、白名单,不下沉业务逻辑是
统一限流按 IP/用户/接口维度限流(Sentinel 网关限流用法见 SpringCloud 笔记)是(高并发时)
跨域/协议转换CORS、HTTP/HTTPS 终止按需
日志与埋点统一访问日志、Trace ID 注入是

网关的禁忌:不要把业务聚合(拼装多个服务响应)、业务校验(订单规则)写进网关——网关会变成第二个单体,且升级成本极高。

4.2 网关 vs BFF ​

  • 网关:横切(鉴权、限流、路由),对所有端统一;
  • BFF(Backend For Frontend):按端适配(App 端 / Web 端 / 小程序端各自接口不同),做响应裁剪与聚合,是"应用层"而非"基础设施层"。

多端差异大时在网关后加 BFF 层;差异小时一个网关 + 服务端聚合接口即可,避免每端一个 BFF 导致团队分裂。

4.3 配置中心:管理的不是配置而是变更 ​

配置中心选型(Nacos Config/ Apollo / Spring Cloud Config)见组件笔记,此处讲三个配置管理方法论:

  1. 分环境隔离:dev/test/prod 用命名空间隔离,禁止 prod 配置与测试配置混用;
  2. 变更即风险:配置发布要像发代码一样走审批与灰度,避免"改一个配置下线整个集群";
  3. 配置下沉/收敛:业务开关(功能开关、降级开关)集中在配置中心,配合监控在故障时快速"一键降级";敏感配置(密码、密钥)必须加密存储,不落代码仓库。

五、契约与 API 版本管理 ​

5.1 为什么服务契约要单独管理 ​

服务间通过契约(接口定义)协作,契约变更的破坏面是"所有消费者"。没有契约管理,一次"删个字段"就可能让下游全体报错——架构层的 LSP 要求"提供方演进不得破坏消费者"。

5.2 消费者驱动契约(CDC) ​

  • 由消费者定义自己期望的契约(Consumer-Driven Contract,工具如 Spring Cloud Contract/Pact);
  • 提供方用消费者契约做持续校验,发现"我的改动会破坏谁";
  • 效果:兼容性问题在 CI 阶段暴露,而不是上线后爆炸。

5.3 API 版本管理策略 ​

策略做法适用
URI 版本/v1/orders、/v2/orders对外公开 API 的标准做法,简单直观
Header/媒体类型版本Accept: application/vnd.api+json;version=2需要同一 URL 语义收敛时
兼容性演进(首选)只增不改不删:加字段、加默认值、字段可空内部服务间尽量走这条路

内部服务间原则:默认做兼容性演进(加字段),确实破坏兼容时走 URI 版本 + 双版本并行 + 消费者迁移 + 旧版本下线(设明确下线日期),同一时刻最多维护两个大版本。

5.4 契约反模式 ​

  • 服务间共享同一个"大 DTO"工程(改一处全量重编);
  • 直接共享数据库表作为"接口"(表结构即契约,改动即爆炸);
  • 无版本、无所有者、无契约测试的裸 HTTP 调用。

六、分布式事务与最终一致性 ​

6.1 挑战的本质:从 CAP 说起 ​

本地事务由单库保证 ACID;跨服务 = 跨库 = 分布式事务,受 CAP 约束:网络分区(P)不可避免时,只能在一致性与可用性间取舍。强一致方案(2PC/XA)牺牲可用性换取 ACID,弱一致方案(SAGA/消息)换取高可用但只能最终一致。

组件落地(Seata 的 AT/TCC/Saga/XA 配置)见《SpringCloud微服务》笔记第六章,本笔记只讲模式取舍与决策。

6.2 强一致:2PC / XA ​

  • 原理:协调者分两阶段(Prepare 全部成功 → Commit;任一失败 → Rollback);
  • 优点:业务侵入小(XA 由数据库实现)、语义接近本地事务;
  • 致命伤:两阶段期间资源被锁住(阻塞),协调者单点、参与者故障需人工介入,性能与可用性差;
  • 适用:极少使用——仅用于并发低、必须强一致、可接受锁时段的场景(如某些对账核心)。互联网高并发业务基本排除。

6.3 TCC(Try-Confirm-Cancel) ​

  • 原理:业务方把每个操作实现成 Try(预占资源)、Confirm(确认提交)、Cancel(回滚释放)三段,Try 成功则最终 Confirm,任一失败则全部 Cancel;
  • 优点:无长事务锁、性能较好;
  • 代价:侵入最强(每个参与方都要写三段逻辑 + 空回滚/幂等/悬挂处理),开发量最大;
  • 适用:强一致要求较高且必须即时、参与方少的核心链路(如跨行资金冻结),典型如"预占 + 确认"语义的资源类操作。

6.4 SAGA(长事务/补偿) ​

  • 原理:把长事务拆成有序本地事务序列,每步成功推进、失败则执行反向补偿(已提交步骤逐一补偿);
  • 两种编排:编排式(Choreography)——各服务通过事件互相触发(实现简单但流程隐式);协同式(Orchestration)——中央 SAGA 协调器显式编排步骤(流程可见、易管控,推荐);
  • 优点:无锁、高可用、适合长流程;
  • 代价:最终一致(中间态对外可见,需业务容忍 + 展示兜底)、补偿逻辑需完整设计(补偿幂等、失败重试);
  • 适用:订单履约、跨境汇款等"长流程 + 可补偿 + 容忍最终一致"的业务。

6.5 本地消息表 ​

  • 原理:业务本地事务内"写业务 + 写消息表",同库同事务保证不丢;异步任务扫表投递 MQ,消费方幂等消费;投递成功后删除/标记消息。
sql
-- 本地消息表(与业务表同库同事务)
CREATE TABLE t_message_outbox (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  msg_key       VARCHAR(64)  NOT NULL COMMENT '业务幂等键,如 order:123:paid',
  topic         VARCHAR(64)  NOT NULL,
  payload       TEXT         NOT NULL,
  status        TINYINT      NOT NULL DEFAULT 0 COMMENT '0待投递 1已投递',
  retry_count   INT          NOT NULL DEFAULT 0,
  next_retry_at DATETIME     NOT NULL,
  create_time   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
) COMMENT '本地消息表:业务事务与消息同库落盘';
-- 本地消息表(与业务表同库同事务)
CREATE TABLE t_message_outbox (
  id            BIGINT PRIMARY KEY AUTO_INCREMENT,
  msg_key       VARCHAR(64)  NOT NULL COMMENT '业务幂等键,如 order:123:paid',
  topic         VARCHAR(64)  NOT NULL,
  payload       TEXT         NOT NULL,
  status        TINYINT      NOT NULL DEFAULT 0 COMMENT '0待投递 1已投递',
  retry_count   INT          NOT NULL DEFAULT 0,
  next_retry_at DATETIME     NOT NULL,
  create_time   DATETIME     NOT NULL DEFAULT CURRENT_TIMESTAMP
) COMMENT '本地消息表:业务事务与消息同库落盘';
  • 优点:不依赖 MQ 的事务特性,任何 MQ 都可用;消息可靠性由 DB 保证;
  • 代价:需写扫表任务(可用延迟队列轮询),消息表与业务表耦合在同一库;
  • 适用:RocketMQ/Kafka 等不具备事务消息、或团队想自控投递可靠性时的通用兜底方案。

6.6 事务消息(RocketMQ 半消息) ​

  • 原理:先发"半消息"(对消费者不可见),本地事务执行成功后 commit 半消息使其可见,失败则 rollback;本地事务结果未知时靠回查接口兜底;
  • 优点:消息可靠性与本地事务原子绑定,无扫表任务;
  • 适用:使用 RocketMQ 且希望少维护代码的场景(主流电商下单 → 异步扣减/发券的标准解法);
  • 局限:依赖 MQ 产品能力(RocketMQ 支持,Kafka 原生不支持)。

6.7 模式对比与取舍决策 ​

方案一致性锁业务侵入性能典型适用
2PC/XA强一致长锁低差极少用(低频强一致)
TCC准强一致无长锁极高较好资金冻结类短操作
SAGA最终一致无锁中高(补偿)好长流程业务
本地消息表最终一致无低好通用可靠投递
事务消息最终一致无低好RocketMQ 栈的下单异步化

选型决策树:

6.8 实践原则:先想方设法消灭分布式事务 ​

最重要的"方案"是不让分布式事务出现:

  1. 服务内聚:把高频强一致的操作放在同一服务同一库(如"下单 + 扣库存"强一致要求极高时,先把库存并入订单服务,或库存聚合在订单服务的库内);
  2. 业务幂等:所有写接口带幂等键(orderId + action),重试安全;
  3. 对账兜底:最终一致不等于不校验——每日对账 + 差错处理任务(SAGA 补偿失败的最终防线);
  4. 展示层补偿:中间态(如"支付中")用状态文案引导用户,而不是阻塞等结果。

参考案例:电商"下单"通常是"订单服务本地事务落单 + 事务消息通知库存扣减 + 对账兜底"的组合,而不是一个全局 XA 事务。


七、何时不该上微服务 ​

7.1 不适合微服务的信号 ​

信号说明
团队小于等于 10 人独立服务无人长期维护,治理成本压垮收益
业务以 CRUD 为主没有复杂协作,单体事务是最大优势
无独立部署/发布诉求一个版本发全站即可满足
基础设施空白没有监控、日志、CI/CD,先补基建再谈拆
强一致业务密集订单-库存-账户必须同一事务且不可最终一致,拆分代价极高
代码库尚可维护没有实际的耦合痛点,拆分只是想象出来的收益

7.2 单体优先与演进路线 ​

正确的路线是"先单体,后按需演进"(详版见《设计原则与架构演进》):

  1. 单体 + 良好模块化(模块化单体)起步;
  2. 出现明确的"独立演进 / 独立伸缩 / 独立团队"需求后再拆;
  3. 从变化最频繁、最独立的模块开始,逐块拆出。

7.3 模块化单体:被低估的中间形态 ​

在代码库内用包边界 + 模块依赖规则(禁止跨模块直接引用 DAO、禁止循环依赖,用 ArchUnit 强约束)模拟服务边界,数据仍在一库便于事务。它拿到了"边界清晰"的大部分收益,却不用支付微服务的全部成本,可作为"拆之前"和"不敢拆"两种场景的共同答案。

7.4 落地误区清单 ​

误区后果
为"技术先进"上微服务治理成本吞掉业务效率
只拆代码不拆库服务独立了,库还是共享大库,等于没拆
引入分布式事务解决所有跨服务写用最大复杂度解决本可内聚的问题
网关写业务逻辑网关膨胀成新单体
服务间共享数据库/共享 DTO 工程契约失效,牵一发动全身
没有监控与链路追踪就上线故障定位靠猜
一次性全量拆分大面积返工风险失控

7.5 上微服务前自查清单 ​

  • [ ] 存在明确的独立演进/独立伸缩/团队自治需求;
  • [ ] 已跑过模块化单体,确认边界稳定;
  • [ ] 监控、日志、CI/CD、链路追踪四件套就绪;
  • [ ] 每个候选服务都有长期 Owner 团队;
  • [ ] 数据归属已明确,无跨服务强 join;
  • [ ] 已设计好跨服务一致性的兜底(幂等 + 对账)。