一句话总结
微服务不是为了拆分才拆分,而是围绕业务能力划分独立可部署的服务单元.
拆分的终极判断标准只有一个:能否让不同团队以不同节奏独立交付.
为什么需要拆分单体服务
一个典型的 Go 单体后端:所有业务逻辑在一个 binary 里,共享一个数据库,一次 CI/CD 部署整个应用.
早期团队小,迭代快,运维简单.但问题出在规模上升之后:
| 痛点 | 表现 | 根因 |
|---|---|---|
| 交付速度下降 | 改一行代码要全量回归 + 全量部署 | 代码耦合,部署粒度 = 整个应用 |
| 故障爆炸半径大 | 一个 OOM / panic 拖垮整个进程 | 所有模块共享进程资源 |
| 团队协作冲突 | 10 个人改同一个仓库,频繁 merge conflict | 代码所有权边界模糊 |
| 技术栈锁定 | 想升 Go 1.22 但有模块依赖 CGO 旧库 | 所有模块必须统一编译环境 |
| 扩容浪费 | 只有搜索模块需要 CPU,但只能整体扩容 | 资源粒度 = 整个应用 |
单体 = 一栋综合办公楼,所有部门共享一个电梯.早高峰一层拥堵,全楼瘫痪.
微服务 = 各部门搬进独立写字楼,各有自己的电梯和供电系统.楼之间通过班车(RPC / 消息)通勤.
单体应该被淘汰吗?
如果你的团队只有 3-5 人,产品还在探索期,业务逻辑不超过 10 万行.那么单体是最优解.
微服务的代价(网络通信,分布式事务,运维复杂度) 在小团队里远大于收益.后面会详细讨论"何时不该拆".
微服务定义
先给一个工作定义:
定义
微服务架构是一种将应用程序构建为一组小型,自治服务的方法.
每个服务围绕一个业务能力(Business Capability)构建,拥有自己的数据存储,通过轻量级协议(HTTP/gRPC/消息队列)通信,可被独立开发,部署和扩缩容.
关键特征:
| 特征 | 含义 | 反例 |
|---|---|---|
| 围绕业务能力 | 按业务域而非技术层拆分 | 把"数据库层"拆成一个服务 ✗ |
| 独立数据存储 | 每个服务拥有私有 DB / schema | 10 个服务共享一个大库 ✗ |
| 独立部署 | 改 A 服务不需要重新部署 B | 发布前要"集成联调会" ✗ |
| 轻量级通信 | gRPC / HTTP / 事件 | 共享内存 / 直接读对方数据库 ✗ |
| 去中心化治理 | 各服务可选择适合的技术栈 | 强制所有服务同一框架版本 ✗ |
单体不止一种形态
在讨论"拆分"之前,先识别你手上的单体类型--不同形态的药方不同:
经典单体(Single Deployment Unit)
所有代码编译成一个 binary / 一个 WAR 包部署.内部可能有模块划分(package / module),但运行时是一个进程.
分布式单体(Distributed Monolith)
服务虽然物理拆开了,但因为共享数据库,同步链式调用,联合发布,实际上没有获得独立部署的能力.这是最糟糕的状态--拥有分布式的全部代价,却没有微服务的任何收益.
分布式单体的典型症状
改了 A 服务的一个字段,B/C/D 都要跟着发布;每次上线需要所有服务"同时切流";任何一个下游超时整条链路就瘫痪.
如果你的"微服务"有这些表现--它本质上还是单体,只是多了网络开销.
模块化单体(Modular Monolith)
单一部署单元,但内部有清晰的模块边界(独立 DB schema,接口契约,显式依赖声明).这是许多团队的最优中间态--享受单体的简单运维,同时为未来可能的拆分保留边界.
|
用 DDD 划定服务边界
微服务拆分最大的难题不是技术,而是在哪里画线.领域驱动设计(Domain-Driven Design,DDD)给出了最实用的思维框架.
限界上下文(Bounded Context)
核心概念:每个限界上下文是一个自洽的业务领域,内部的术语和规则在上下文内有明确含义,出了边界可能语义不同.
"账户"在不同上下文里含义完全不同:
用户中心的"账户" = 登录凭证 + 个人信息.
财务系统的"账户" = 资金余额 + 交易记录.
权限系统的"账户" = 角色 + 权限列表.
把它们强行塞进一张 Account 表,就是混淆了限界上下文.
实操步骤:
- 事件风暴(Event Storming):召集业务方和开发,在白板上列出所有业务事件("订单已创建","支付已完成","库存已扣减"...)
- 聚合命令和事件:将相关事件归入同一个聚合(Aggregate),聚合是事务一致性的边界
- 识别上下文边界:当两组事件之间的交互变成"通知"而非"直接调用"时,它们大概率属于不同上下文
- 上下文 = 服务候选:每个限界上下文对应一个或一组微服务
上下文映射(Context Mapping)
服务之间不是孤岛--它们必须协作.DDD 定义了几种典型的上下文间关系:
| 关系模式 | 含义 | 微服务场景 |
|---|---|---|
| 防腐层(ACL) | 在边界处翻译外部模型,不让外部概念污染内部 | 调用第三方支付 API 时,封装一层适配器 |
| 开放主机服务(OHS) | 提供标准化 API 供多方调用 | 用户中心对外暴露 gRPC 接口 |
| 共享内核(SK) | 两个上下文共享一小部分模型 | 订单和物流共享 Address 值对象 |
| 客户/供应商 | 下游需求驱动上游 API 演进 | 前端 BFF 驱动后端接口变更 |
防腐层是微服务最重要的模式之一
当你需要对接外部系统(第三方 API,遗留单体,另一个团队的服务),永远在边界处加一个翻译层.
它的价值:
- 外部模型变更不会扩散到你的核心领域
- 你可以随时替换外部依赖而不改内部逻辑
- 测试时可以 mock 防腐层而非整个外部系统
拆分策略与粒度
常见拆分维度
| 维度 | 适用场景 | 示例 |
|---|---|---|
| 按业务能力 | 最常见,DDD 导出 | 订单服务,支付服务,库存服务 |
| 按数据所有权 | 数据隔离需求强 | 用户数据服务(GDPR 合规要求独立) |
| 按变更频率 | 部分模块迭代极快 | 营销活动服务(每周变)vs 结算服务(每季度变) |
| 按团队组织 | 康威定律驱动 | A 团队负责搜索,B 团队负责推荐 |
| 按扩缩容需求 | 资源画像差异大 | CPU 密集的图片处理 vs IO 密集的消息推送 |
康威定律(Conway's Law)
系统架构 = 组织沟通结构
"设计系统的组织,其产出的设计等同于组织的沟通结构."--Melvin Conway, 1967
康威定律不是建议,是物理定律级的观察.实践中的含义:
- 如果你的组织是按"前端组 / 后端组 / DBA 组"划分的,你很难做出围绕业务能力的微服务--因为每个功能都要跨三个组协调
- 反向操作(逆康威定律):先设计你想要的系统架构,再按这个架构调整团队结构
- 一个服务的最佳 owner 数量 = 一个团队(5-9 人).超过这个数字通常意味着服务太大了
粒度判断:太粗 vs 太细
| 太粗(本质还是单体) | 太细(Nano-service) | 合适 | |
|---|---|---|---|
| 部署频率 | 改一行要全量发布 | 一个功能要改 5 个服务 | 改动通常只影响 1 个服务 |
| 团队所有权 | 多个团队争抢同一个服务 | 一个人维护 10 个服务 | 一个团队完整拥有 |
| 数据一致性 | 事务在内部即可保证 | 到处都是分布式事务 | 服务内强一致,跨服务最终一致 |
| 通信模式 | 内部模块调用 | 大量同步链式 RPC | 核心路径 ≤ 3 跳 |
粒度经验法则
如果你无法在两周内完全重写一个服务的核心逻辑--它可能太大了.
如果一个用户请求需要串行调用 5 个以上的服务--你可能拆太细了.
常见反模式
反模式一:共享数据库
多个服务直接读写同一个数据库的同一张表.
为什么有害:
- 数据库 schema 变更影响所有服务--耦合回来了
- 无法独立扩缩容数据层
- 数据所有权不清,谁都能改,没人敢动
正确做法: 每个服务拥有自己的 schema(甚至独立数据库实例).需要对方数据时,通过 API 或事件获取,不直接查表.
反模式二:同步链式调用
A → B → C → D → E,链路上任何一环超时,整个请求失败.
为什么有害:
- 可用性 = 各环节可用性的乘积(99.9%5 = 99.5%)
- 延迟 = 各环节延迟之和
- 任何一个下游的部署或故障都影响上游
正确做法: 异步解耦(事件驱动),并行调用,缓存,设置合理超时 + 降级.
反模式三:分布式单体
前面已经提到--物理拆分但逻辑耦合.核心症状:不能独立部署.
检测方法: 问自己"我能不能只发布 A 服务而不通知其他团队?"如果答案是"不能"--你有一个分布式单体.
反模式四:按技术层拆分
把"数据访问层","业务逻辑层","API 层"各自拆成独立服务.
为什么有害: 一个业务需求改动要同时修改 3 个服务--拆分没有减少协调成本,反而增加了网络调用和部署风险.
正确做法: 按业务能力纵向拆分,每个服务内部自己包含 API → 业务逻辑 → 数据访问的完整栈.
|
何时不该拆微服务
这可能是整篇笔记最重要的一节.微服务有真实的代价:
| 代价 | 具体表现 |
|---|---|
| 网络不可靠 | RPC 超时,重试风暴,消息丢失--单体里不存在这些问题 |
| 数据一致性难 | 跨服务没有数据库事务,需要 Saga / 补偿 / 最终一致 |
| 运维复杂度 | 10 个服务 = 10 套 CI/CD,10 套监控告警,10 套日志收集 |
| 调试困难 | 一个请求经过 5 个服务,分布式追踪成为刚需 |
| 测试复杂 | 集成测试要启动整个依赖链,或维护大量 mock |
| 团队认知负担 | 开发者需要理解服务间的交互协议,数据流向,故障模式 |
以下场景建议保持单体(或模块化单体):
- 团队 ≤ 5 人
- 产品处于 MVP / 探索期,业务边界还不清晰
- 没有 K8s / 容器化基础设施,运维能力不足
- 不存在独立部署的需求(所有功能总是一起发布)
- 核心路径对延迟极度敏感(每多一跳网络增加 1-5ms)
渐进式演进策略
最务实的路径:先构建模块化单体(内部清晰边界 + 独立 schema),等真正出现独立部署 / 独立扩容的需求时,再沿着已有边界拆出去.
这比一开始就强行拆微服务安全得多.
从单体到微服务的演进路径
如果决定要拆,不要一次性重写,而是渐进式拆分(Strangler Fig Pattern):
|
绞杀者模式(Strangler Fig Pattern)
名字来自热带的绞杀榕--种子落在宿主树上,缓慢包裹宿主,最终取而代之.
应用到迁移中:
- 在单体前面加一个路由层(API Gateway / Nginx / Envoy)
- 新功能直接写成新服务,流量通过路由层导入
- 逐步迁移旧功能:从低风险模块开始,迁移完后把流量切到新服务
- 旧单体逐渐缩小,直到只剩空壳(或彻底下线)
第一刀切哪里
优先拆出满足以下条件的模块:
- 变更频率高(拆出后能加快交付)
- 与其他模块耦合度低(拆分成本小)
- 有独立扩容需求(拆出后能降本)
通常"用户认证"或"通知推送"是不错的第一刀--它们职责清晰,接口简单,几乎所有系统都需要.
数据所有权与数据库拆分
微服务最难的不是代码拆分,而是数据库拆分.代码拆完可以独立部署,但如果还共享数据库,本质上没有解耦.
Database per Service 原则
每个服务拥有私有数据存储,其他服务不能直接访问--只能通过该服务的 API 或发布的事件获取数据.
落地方案有几个层次:
| 方案 | 隔离程度 | 适用场景 |
|---|---|---|
| 独立数据库实例 | 最强 | 安全性要求高(如支付),独立扩缩容 |
| 同实例不同 schema | 中等 | 大多数场景,运维成本可控 |
| 同 schema 不同表前缀 | 最弱 | 迁移过渡期的临时方案 |
拆库后的 JOIN 问题
单体里一个 SQL JOIN 就能拿到的数据,拆分后怎么办?
- API 组合:在上层服务里调两个下游 API,在内存中组装(适合低频查询)
- 数据冗余:通过事件同步一份只读副本到本地(适合高频查询)
- CQRS:写入走各自服务,查询走专门的读模型(适合复杂报表)
不要为了避免网络调用而走捷径
"直接查对方数据库不就行了?"--这会让两个服务通过 schema 耦合.对方改一个字段名,你就挂了.
这正是前面"共享数据库反模式"的问题.宁可多一次 RPC 调用,也不要偷看别人的数据库.
服务间通信概览
微服务之间的通信方式是架构设计的核心决策.整体分为两大类:
| 同步通信 | 异步通信 | |
|---|---|---|
| 模式 | 请求-响应(Request/Response) | 事件驱动(Event-Driven) |
| 协议 | gRPC / HTTP REST / GraphQL | Kafka / RabbitMQ / NATS / Redis Streams |
| 耦合度 | 时间耦合(调用方等待响应) | 解耦(发布方不关心谁消费) |
| 适用场景 | 需要立即结果(查询,校验) | 通知,异步处理,跨域解耦 |
| 失败处理 | 重试 / 超时 / 熔断 | 重试消费 / 死信队列 / 幂等 |
同步通信 = 打电话,必须对方接了才能说事,对方不在就只能等.
异步通信 = 发微信,发完就不用等,对方看到了自然会处理.
关键业务对话用电话(确认收到),日常通知用微信(异步即可).
下一篇将深入讲解 gRPC 同步通信,第三篇讲异步消息--两种模式互补使用是微服务通信的最佳实践.
真实世界的微服务长什么样
以一个电商系统为例,展示合理的服务划分:
|
注意几个设计决策:
- 订单 → 支付 / 库存:同步 RPC(创建订单时需要立即确认扣款和锁库存)
- 订单 → 通知 / 数据分析:异步事件(不需要等它们处理完)
- 支付 → 订单:支付结果通过事件回调(解耦支付渠道的异步性)
- API Gateway 统一对外,内部服务之间直接 gRPC 通信
快速回顾
- 微服务的本质:围绕业务能力划分的,可独立部署和演进的服务单元
- 拆分判据:能否让不同团队以不同节奏独立交付--不是代码行数或技术层
- DDD 是最佳工具:限界上下文 = 服务边界,事件风暴帮你发现边界
- 康威定律:系统架构反映组织结构,拆服务前先看团队怎么分
- 四大反模式:共享数据库,同步链式调用,分布式单体,按技术层拆分
- 渐进式优于激进:模块化单体 → 绞杀者模式逐步拆出 → 持续演进
- 数据所有权:每个服务私有数据库,跨服务通过 API / 事件获取数据
- 通信两种模式:同步(gRPC/HTTP)+ 异步(事件/消息),互补使用
动手练习
- 画模块依赖图:画出你当前工作项目的模块依赖图,尝试识别哪些模块之间是"通知"关系(可异步)vs "直接调用"关系(需同步)
- 事件风暴划边界:用事件风暴的方式,列出你最熟悉的一个业务流程中的所有事件,尝试划分限界上下文
- 检查共享数据库:检查项目中是否存在"共享数据库"反模式--多个模块直接 JOIN 对方的表?如果要拆分,数据怎么解耦?
- 评估拆分条件:评估你的项目是否满足拆微服务的前提条件(团队规模,基础设施,部署频率差异),给出"拆/不拆/先模块化"的结论