L1: 微服务架构认知 -- 什么时候该拆,怎么拆,拆多细
基于 Python 生态:Flask/FastAPI + gRPC + Consul + pybreaker + OpenTelemetry
一、为什么要微服务?单体架构到底有什么问题?
先说清楚:单体架构不是错,而是特定阶段的合理选择
单体架构的优势:
- 开发简单:一个代码库,一个部署单元
- 调试方便:本地跑起来就是全部
- 事务简单:数据库本地事务就能解决
- 运维简单:部署、监控、回滚都很直接
- 性能优秀:没有网络调用开销
单体架构适合:
- 业务初期,快速验证 MVP
- 团队规模小(3~10 人)
- 业务逻辑简单
- 流量不大(单机扛得住)
举个例子:
一个创业团队做电商平台,3 个后端开发,用 Django/Flask 写一个单体应用:
ecommerce/
├── user/ # 用户模块
├── product/ # 商品模块
├── order/ # 订单模块
├── payment/ # 支付模块
└── manage.py # 启动入口这很合理,团队小、业务简单,单体足够。
但是,单体架构会在这些时候出问题:
1. 代码耦合,改一处影响全局
场景:
- 商品服务要做秒杀改造
- 因为和订单、库存逻辑耦合在一起
- 改代码时不敢大改,怕影响订单
- 测试时要测全链路
- 上线时整个应用都要重新部署
后果:
- 改动成本高,速度慢
- 风险大,容易相互影响
2. 团队协作冲突
场景:
- 用户团队和订单团队都在改同一个代码库
- Git 冲突频繁
- 代码评审相互等待
- 一个团队发布阻塞另一个团队
后果:
- 团队效率下降
- 人多了反而慢
3. 技术栈绑定
场景:
- 整个系统是 Django
- 想引入机器学习服务(TensorFlow),但和 Django 集成麻烦
- 想用 Go 写高性能网关,但单体架构做不到
后果:
- 技术选型受限
- 无法为不同场景选最优方案
4. 扩展性差
场景:
- 商品浏览量大,需要多实例
- 但整个单体应用都要扩展
- 订单服务、支付服务也被迫扩展,浪费资源
后果:
- 资源利用率低
- 成本高
5. 故障影响面大
场景:
- 某个模块(比如推荐服务)写了个慢 SQL
- 拖垮整个应用的数据库连接池
- 所有功能都不可用
后果:
- 局部故障 → 全局故障
- 可用性差
什么时候是拆分的信号?
| 信号 | 说明 |
|---|---|
| 团队超过 10 人 | 单体代码库协作效率急剧下降 |
| 发布周期拉长 | 一个小改动也要整个应用发布,等待时间长 |
| 某些模块明显是性能瓶颈 | 想单独扩展某个模块 |
| 不同模块想用不同技术栈 | 比如订单用 Python,推荐用 Go |
| 故障频繁相互影响 | 一个模块挂了,整个系统挂 |
| 代码库超过 10 万行 | 单体过于庞大,维护成本高 |
二、微服务是什么?核心特征是什么?
微服务的定义
微服务是一种架构风格,将一个单一应用拆分成一组小服务,每个服务:
- 运行在独立进程中
- 通过轻量级通信机制(通常是 HTTP REST 或 gRPC)相互协作
- 可以独立部署、独立扩展、独立演进
- 由独立的小团队负责
微服务的核心特征(必须记住)
1. 服务自治
- 每个服务独立开发、独立部署、独立运维
- 服务有自己的数据库(或独立 Schema)
- 服务可以用不同技术栈
2. 按业务能力拆分
- 不是按技术层拆分(MVC),而是按业务领域拆分
- 比如:用户服务、订单服务、支付服务
3. 去中心化治理
- 没有统一的技术栈要求
- 每个服务团队自己选型
- 数据去中心化:每个服务管理自己的数据
4. 基础设施自动化
- 强依赖 CI/CD
- 强依赖容器化(Docker/K8s)
- 强依赖自动化测试
5. 为失败而设计
- 默认服务会失败
- 要有熔断、降级、重试、超时机制
- 要有监控、告警、链路追踪
三、微服务不是银弹,代价是什么?
微服务的代价:
1. 分布式复杂性
- 网络延迟
- 网络不可靠
- 分布式事务难
- 数据一致性难
2. 运维复杂度暴增
- 单体 1 个部署单元 → 微服务可能 20 个
- 监控、日志、链路追踪必须有
- 没有自动化运维会累死
3. 服务间调用开销
- 本地方法调用变成网络调用
- 性能有损耗
- 要做序列化/反序列化
4. 数据一致性难
- 不能用数据库本地事务
- 要引入分布式事务、最终一致性
- 业务逻辑变复杂
5. 测试复杂
- 集成测试要启动多个服务
- 端到端测试成本高
- 环境管理复杂
6. 团队能力要求高
- 需要懂分布式
- 需要懂容器化
- 需要懂服务治理
- 小团队可能 hold 不住
所以,微服务的前提条件:
| 前提条件 | 说明 |
|---|---|
| 团队规模 | 至少 10+ 人 |
| 业务复杂度 | 业务足够复杂,值得拆分 |
| 自动化能力 | 有 CI/CD、容器化能力 |
| 监控能力 | 有日志、监控、链路追踪基础设施 |
| 分布式系统经验 | 团队有分布式系统开发经验 |
| 业务稳定性要求高 | 愿意投入成本做高可用 |
如果以上条件不满足,不要盲目上微服务。
四、服务怎么拆?拆分的 5 个核心原则
原则 1:按业务能力拆分,不按技术层拆分
错误示范(按技术层拆分):
- user-controller-service
- user-service-service
- user-dao-service这不是微服务,这是"分布式单体"。
正确示范(按业务能力拆分):
- user-service # 用户服务:注册、登录、信息管理
- product-service # 商品服务:商品 CRUD、库存查询
- order-service # 订单服务:下单、订单查询、订单状态
- payment-service # 支付服务:支付、退款
- notification-service # 通知服务:发邮件、发短信每个服务是一个完整的业务能力。
原则 2:高内聚,低耦合
高内聚:
- 一个服务内部的功能高度相关
- 比如订单服务:下单、取消订单、查询订单,都和"订单"相关
低耦合:
- 服务之间依赖尽量少
- 服务之间通过 API 通信,不直接访问对方数据库
判断标准: 如果两个功能经常一起变,应该在一个服务里。 如果两个功能可以独立演进,应该在不同服务里。
原则 3:服务要有明确的业务边界
用 DDD(领域驱动设计)的概念:
- 每个服务对应一个 限界上下文(Bounded Context)
- 限界上下文内的概念和术语统一
- 不同上下文的概念可以不同
举例:
在 订单服务 里,"商品"可能只是:
class OrderItem:
product_id: str
product_name: str # 快照
price: Decimal # 快照在 商品服务 里,"商品"是完整的:
class Product:
id: str
name: str
price: Decimal
stock: int
category: str
description: str
images: List[str]两个服务里的"商品"概念不同,这是合理的。
原则 4:一个服务一个数据库(或独立 Schema)
为什么?
- 数据隔离,服务自治
- 避免数据库层耦合
- 方便独立扩展
错误做法:
user-service ──┐
order-service ──┼──> 同一个数据库
product-service ─┘这样服务间会通过数据库相互影响,本质上还是单体。
正确做法:
user-service → user_db
order-service → order_db
product-service → product_db每个服务有自己的数据库。
但注意: 这会带来数据一致性问题,需要分布式事务或最终一致性方案。
原则 5:服务粒度要适中
过粗:
- 一个"业务服务"包含用户、商品、订单
- 本质上还是单体
过细:
- 一个服务只有一个接口
- 运维成本暴增
- 调用链路过长
合适的粒度:
- 一个服务对应一个业务领域
- 一个团队(或半个团队)能维护
- 服务数量控制在 10~30 个(中小团队)
判断标准:
- 如果两个服务离了对方都没法工作 → 应该合并
- 如果一个服务太大,一个团队维护吃力 → 应该拆分
五、实战案例:电商系统怎么拆微服务?
需求分析
一个典型电商系统包含:
- 用户注册、登录
- 商品浏览、搜索
- 购物车
- 下单、支付
- 订单查询
- 库存管理
- 通知(邮件、短信)
- 推荐系统
拆分方案(基于业务能力)
┌─────────────────────────────────────────────────┐
│ API Gateway │ # 统一入口
└─────────────────────────────────────────────────┘
│
├──> user-service # 用户服务
├──> product-service # 商品服务
├──> cart-service # 购物车服务
├──> order-service # 订单服务
├──> payment-service # 支付服务
├──> inventory-service # 库存服务
├──> notification-service # 通知服务
└──> recommendation-service # 推荐服务各服务职责
1. user-service(用户服务)
- 用户注册、登录、登出
- 用户信息管理
- 用户鉴权(JWT 签发)
数据库:
user_db:
- users 表
- user_profiles 表2. product-service(商品服务)
- 商品 CRUD
- 商品分类
- 商品搜索(可能调用 Elasticsearch)
- 商品详情查询
数据库:
product_db:
- products 表
- categories 表3. cart-service(购物车服务)
- 加入购物车
- 购物车查询
- 删除购物车
数据存储:
- Redis(购物车通常不持久化)
4. order-service(订单服务)
- 下单
- 订单查询
- 取消订单
- 订单状态变更
数据库:
order_db:
- orders 表
- order_items 表(订单明细)冗余字段(快照):
- 商品名称快照
- 商品价格快照
- 收货地址快照
为什么冗余? 因为下单后,商品价格可能改变,但历史订单价格不能变。
5. payment-service(支付服务)
- 发起支付
- 支付回调
- 退款
数据库:
payment_db:
- payments 表对外调用:
- 第三方支付(微信支付、支付宝)
6. inventory-service(库存服务)
- 库存查询
- 库存扣减
- 库存回滚
数据库:
inventory_db:
- inventory 表注意: 库存扣减是高并发场景,要做好并发控制。
7. notification-service(通知服务)
- 发送邮件
- 发送短信
- 发送站内信
数据库: 可选,通常只记录发送日志。
消息队列: 通常由其他服务发消息到 MQ,通知服务消费。
8. recommendation-service(推荐服务)
- 个性化推荐
- 热门商品推荐
技术栈: 可能用 Python(机器学习)+ TensorFlow
数据来源:
- 读取商品服务数据
- 读取用户行为数据
服务间调用关系
用户下单流程:
user-service(鉴权)
↓
order-service(创建订单)
↓
├──> product-service(查询商品价格、信息)
├──> inventory-service(扣减库存)
└──> payment-service(发起支付)
↓
notification-service(发送通知)数据一致性问题
问题: 下单时需要:
- 创建订单
- 扣减库存
- 发起支付
如果 2 或 3 失败了怎么办?
解决方案(后续章节详细讲):
- 本地消息表 + 最终一致性
- Saga 分布式事务
- 补偿机制
六、拆分的常见坑点(必须规避)
坑点 1:过度拆分
案例: 一个 3 人团队,拆了 15 个微服务。
问题:
- 运维成本暴增
- 一个需求改 5 个服务
- 联调成本比开发成本还高
教训: 服务数量要和团队规模匹配。
坑点 2:共享数据库
案例: 多个服务直连同一个数据库。
问题:
- 表结构耦合
- 数据库成为瓶颈
- 本质上还是"分布式单体"
教训: 每个服务必须有独立数据库(或独立 Schema)。
坑点 3:同步调用链路过长
案例:
A → B → C → D → E问题:
- 任何一个环节慢,整条链路都慢
- 延迟叠加
- 可用性差
教训:
- 同步链路不超过 3~4 层
- 能异步的用消息队列
坑点 4:服务划分不清
案例: 订单服务和库存服务相互调用,循环依赖。
问题:
- 服务边界不清
- 维护困难
教训: 服务间应该是单向依赖,不应该循环依赖。
坑点 5:没有 API 网关
案例: 前端直接调多个微服务。
问题:
- 跨域问题
- 鉴权重复
- 前端要知道所有服务地址
教训: 必须有 API 网关作为统一入口。
七、面试高频题
1. 什么是微服务?和单体架构有什么区别?
参考答案:
微服务是一种架构风格,将单一应用拆分成一组小服务,每个服务独立部署、独立扩展、独立演进。
和单体的区别:
| 维度 | 单体架构 | 微服务架构 |
|---|---|---|
| 部署单元 | 一个 | 多个 |
| 技术栈 | 统一 | 可不同 |
| 数据库 | 共享 | 独立 |
| 团队协作 | 集中式 | 去中心化 |
| 扩展性 | 整体扩展 | 按服务扩展 |
| 故障隔离 | 局部故障全局影响 | 故障隔离 |
| 复杂度 | 低 | 高(分布式复杂性) |
2. 什么时候该从单体拆微服务?
参考答案:
以下信号说明该考虑微服务:
- 团队超过 10 人,单体协作效率低
- 发布周期拉长,小改动也要整体发布
- 某些模块是性能瓶颈,想单独扩展
- 不同模块想用不同技术栈
- 故障频繁相互影响
但前提是:
- 有自动化能力(CI/CD、容器化)
- 有监控、日志、链路追踪
- 团队有分布式系统经验
如果条件不满足,不要盲目拆。
3. 微服务怎么拆分?有什么原则?
参考答案:
核心原则:
- 按业务能力拆分,不按技术层拆分
- 高内聚,低耦合
- 服务有明确的业务边界(DDD 限界上下文)
- 一个服务一个数据库
- 服务粒度适中(不过粗不过细)
判断标准:
- 经常一起变的放一个服务
- 可以独立演进的拆开
- 服务数量和团队规模匹配
4. 什么是"分布式单体"?怎么避免?
参考答案:
分布式单体: 看起来拆成了多个服务,但本质上耦合严重,改一处影响全局。
典型特征:
- 多个服务共享数据库
- 服务间循环依赖
- 一个需求要改多个服务,且必须同时发布
- 服务按技术层拆分,不按业务拆分
怎么避免:
- 每个服务独立数据库
- 服务间单向依赖,不循环依赖
- 按业务能力拆分
- 服务能独立部署
5. 微服务的优缺点?
参考答案:
优点:
- 服务独立部署、独立扩展
- 技术栈灵活
- 故障隔离
- 团队自治
缺点:
- 分布式复杂性(网络、一致性、事务)
- 运维成本高
- 测试复杂
- 服务间调用有开销
- 团队能力要求高
结论: 微服务不是银弹,要根据团队规模、业务复杂度、技术能力决定。
八、核心结论
- 单体不是错,微服务不是万能,要根据团队和业务选择
- 拆分的信号:团队大、发布慢、模块瓶颈、技术栈限制
- 拆分原则:按业务能力、高内聚低耦合、服务有边界、独立数据库
- 微服务代价:分布式复杂性、运维成本、数据一致性
- 避免分布式单体:独立数据库、单向依赖、按业务拆分
- 电商系统典型拆分:用户、商品、订单、支付、库存、通知
- 服务粒度要适中,和团队规模匹配
- 必须有 API 网关、注册中心、监控、链路追踪
九、练习题
练习 1:服务拆分设计
假设你要设计一个 在线教育平台,包含:
- 用户注册、登录
- 课程浏览、购买
- 视频播放
- 学习进度记录
- 作业提交、批改
- 讨论区
- 推荐课程
请设计:
- 如何拆分服务?
- 每个服务的职责是什么?
- 每个服务的数据库表有哪些?
- 服务间的调用关系?
练习 2:判断是否该拆微服务
场景 1: 一个 5 人团队,做一个内部管理系统,日活 100 人。
是否该拆微服务?为什么?
场景 2: 一个 50 人团队,做一个电商平台,日活 10 万,单体应用代码 20 万行,发布周期从 1 周拉长到 1 个月。
是否该拆微服务?为什么?
练习 3:思考题
为什么微服务要"一个服务一个数据库",而不能多个服务共享一个数据库?
列出至少 3 个理由。