微服务架构划分与协作:最小最佳实践
核心目标:用最少的复杂度获得微服务的核心价值
为什么这样划分
关键问题
- 拆不拆? 团队≥3人、业务模块有明显差异、需要独立扩展 → 拆
- 怎么拆? 按业务域划分,不按技术层
- 拆多细? 一个人/一个小团队能完整负责 → 这就是合适的粒度
划分的三个维度
| 维度 | 判断标准 | 例子 |
|---|---|---|
| 业务独立性 | 功能可独立提供价值? | 用户认证 vs 作品管理 = 两个域 |
| 变更频率 | 逻辑变化是否一起发生? | 登录规则改不影响拍卖逻辑 |
| 团队边界 | 一个人能否全责? | Yes = 合适的服务粒度 |
为什么不按技术层划分?
❌ 反面教材:按技术分层
├── auth-service(只做认证)
├── data-service(只做数据访问)
├── cache-service(只做缓存)
└── notification-service(只做通知)
问题:
• 业务功能需要跨多个服务协调
• 改一个需求要动多个服务
• 没人对完整的业务负责
CanvasChain 的划分思路
按业务域划分:三个核心业务实体
身份域 内容域 交易域
(Identity) (Content) (Transaction)
↓ ↓ ↓
user-service artwork-service auction-service
特点:
• 各自独占数据库
• 对应明确的业务概念
• 可独立部署和扩展
• 一个小团队可完整维护
微服务核心原则
原则 1:单一职责 ✅
✅ 用户服务:只管身份
职责 = {注册、登录、资料、JWT签发}
✅ 作品服务:只管内容
职责 = {作品CRUD、搜索、缓存策略}
✅ 拍卖服务:只管交易
职责 = {拍卖、出价、结算、事件驱动}
验证法:能否用一句话描述服务职责?
NO → 职责过多,需要进一步拆分或整合
原则 2:数据所有权 ✅
禁忌:共享数据库
✅ 正确做法
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ user-service │ │artwork-svc │ │auction-svc │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ user_db │ │ artwork_db │ │ auction_db │
│ (users表) │ │ (artworks表) │ │ (auctions表) │
└──────────────┘ └──────────────┘ └──────────────┘
规则:
• 每个服务独占一个数据库
• 禁止跨库直连
• 数据交换只能通过 API 调用
好处:
• 避免"改一张表影响多个服务"
• 数据库可独立优化和迁移
• 服务边界明确
原则 3:无状态设计 ✅
目标:任何实例都能处理任何请求
┌────────────┬────────────┬────────────┐
│ 实例 A │ 实例 B │ 实例 C │
│ (无状态) │ (无状态) │ (无状态) │
└────────────┴────────────┴────────────┘
↑ ↑ ↑
└─────── 负载均衡器 ───────┘
请求 1 → 实例A
请求 2 → 实例B (可以切换,无影响)
请求 3 → 实例C
实现方式:
• 用 JWT 做无状态认证(信息存在 Token 中)
• 不使用服务端 Session
• 扩容只需加实例,无需数据同步
原则 4:接口契约优先 ✅
所有服务遵守统一的返回体格式
{
"code": 0, // 业务状态码
"message": "成功", // 用户可读消息
"data": {...} // 实际业务数据
}
统一错误码:
SUCCESS(0) → 成功
PARAMS_ERROR(40000) → 参数错误
UNAUTHORIZED(401) → 未授权
NOT_FOUND(40400) → 资源不存在
SYSTEM_ERROR(50000) → 系统错误
好处:
• 前端只需一套错误处理
• 服务间协议一致
• 监控告警易统一
原则 5:失败隔离 ✅
场景:拍卖服务宕机
❌ 坏设计(级联故障)
用户登录 → 作品展示 → 拍卖功能
↓ ↓ ↓
正常 正常 宕机
↓
超时堆积 → 整个系统不可用
✅ 好设计(隔离故障)
• 用户服务正常 → 可登录
• 作品服务正常 → 可浏览
• 拍卖服务故障 → 仅拍卖不可用,其他功能照常
实现手段:
• 设置调用超时时间
• 配置熔断器(超过阈值快速失败)
• 异步解耦(用消息队列)
• 降级策略(主动返回默认值)
服务间协作的三种模式
模式 1:JWT 透传认证(轻量)
前端携带 JWT → 网关转发 → 后端服务本地解析
特点:
✅ 服务间无需调用验证
✅ 性能最优
✅ 支持水平扩展
✅ 适合:大多数场景
流程图:
前端
│ GET /api/artwork/1
│ Authorization: Bearer eyJhbGc...
▼
网关 → 转发 → 作品服务
│
├─ 解析 JWT 获取 userID
├─ 查询数据库
└─ 返回数据
模式 2:ID 引用 + 快照冗余(数据一致性)
场景:拍卖需要显示作品信息
问题:
拍卖表里只存 artwork_id
→ 每次查询都要调作品服务
→ 作品服务故障 → 拍卖也故障
解决:快照冗余
拍卖表结构:
┌──────────────┐
│ auction_id │
│ artwork_id │ ← 只是外键引用
│ artwork_title│ ← 冗余:创建时的标题快照
│ thumbnail_url│ ← 冗余:创建时的封面快照
└──────────────┘
好处:
• 拍卖展示无需调用作品服务
• 作品服务故障不影响已有拍卖
• 历史拍卖保留当时的作品信息
注意:只冗余展示字段,业务字段仍应查主表
模式 3:事件驱动解耦(异步处理)
场景:拍卖成交后要通知买卖家、更新积分、发邮件
❌ 同步链式调用(不推荐)
拍卖服务
├─ 调用用户服务更新积分 ← 可能超时
├─ 调用通知服务发邮件 ← 可能故障
└─ 调用支付服务扣款 ← 可能重试
问题:
• 链式调用导致延迟累加
• 任一环节故障导致整体失败
✅ 异步事件驱动(推荐)
拍卖成交
│
├─ 发布事件到 RabbitMQ
│ (auction.completed)
│
├─ 返回响应给前端 ← 立即返回,不等待下游
│
└─ 异步消费者订阅事件:
├─ 用户服务消费 → 更新积分(可重试)
├─ 通知服务消费 → 发邮件(可重试)
└─ 支付服务消费 → 结算(可重试)
好处:
• 拍卖服务不等待下游
• 下游故障可重试,不影响主流程
• 新增消费者无需改拍卖服务代码
• 高并发场景支撑能力强
系统架构全景
┌─────────────────────────────────────────┐
│ 前端 (Vue3+TS) │
│ 仅通过 /api/* 与后端通信 │
└─────────────┬───────────────────────────┘
│ Bearer Token
▼
┌─────────────────────────────────────────┐
│ API 网关 (Spring Cloud Gateway) │
│ 职责:路由转发、负载均衡、限流、CORS │
│ 规则:/api/service/** → lb://service │
└────────┬──────────────┬──────────────┬──┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│user-svc │ │artwork │ │auction │
│ │ │-svc │ │-svc │
├─────────┤ ├─────────┤ ├─────────┤
│user_db │ │artwork │ │auction │
│ │ │_db │ │_db │
└─────────┘ └────┬────┘ └────┬────┘
▲ │ │
│ ▼ ▼
└──────┐ Redis RabbitMQ
│ 缓存 事件队列
│
┌──────┴─────────┐
▼ ▼
Nacos 注册中心
(服务发现、配置)
各层职责
| 层次 | 工具 | 职责 | 注意 |
|---|---|---|---|
| 前端 | Vue3+TS | 用户交互、调用 API | 不涉及业务逻辑 |
| 网关 | Spring Cloud Gateway | 路由、负载均衡、限流 | 不做业务逻辑 |
| 业务服务 | Spring Boot | 业务实现、数据库访问 | 单一职责 |
| 公共模块 | common jar | 统一返回体、错误码、异常 | 保持轻量 |
| 中间件 | Nacos、Redis、RabbitMQ | 服务发现、缓存、消息 | 支撑生态 |
项目目录组织
CanvasChain/
├── gateway/ # 网关项目
│ └── application.yml # 路由配置
│
├── services/ # 业务服务目录
│ ├── user-service/ # 身份域
│ ├── artwork-service/ # 内容域
│ └── auction-service/ # 交易域
│
├── canvaschain-common/ # 公共模块
│ ├── response/
│ │ ├── BaseResponse.java
│ │ └── ErrorCode.java
│ ├── exception/
│ │ └── GlobalExceptionHandler.java
│ └── util/
│ └── JwtUtil.java
│
└── frontend/ # 前端项目
└── src/api.ts # API 客户端
每个服务的标准结构
{service}/
├── src/main/java/{package}/
│ ├── controller/ # REST 入口
│ ├── service/ # 业务逻辑
│ ├── mapper/ # 数据访问
│ ├── entity/ # 数据实体
│ ├── config/ # 配置类
│ └── util/ # 工具类
└── src/main/resources/
└── application.yml # 服务配置
快速检查清单
划分时问自己
- 这个服务是否围绕一个核心业务概念?
- 一个小团队能否完整负责这个服务?
- 这个服务的数据是否完全独立?
- 这个服务的变更是否不需要同时改其他服务?
- 这个服务是否需要独立扩容?
设计时确保
- 每个服务有独占的数据库
- 服务间通过 API 调用,不跨库查询
- 网关作为唯一入口
- 认证用 JWT,无状态设计
- 统一返回体和错误码
- 高风险操作用异步消息解耦
上线前检验
- 单个服务故障不导致整体系统下线
- 可以独立部署和回滚
- 新增团队成员不需要了解其他服务细节
常见反模式(踩坑警惕)
| 反模式 | 表现 | 危害 | 正确做法 |
|---|---|---|---|
| 共享数据库 | 多服务连同一个 DB | 强耦合、无法独立演进 | 每服务独占数据库 |
| 网关做业务逻辑 | 业务校验、数据转换在网关 | 网关成单点、难维护 | 网关只做路由和横切关注点 |
| 同步链式调用 | A→B→C→D 逐个调用 | 延迟累加、级联故障 | 必要同步加超时熔断,非必要用异步 |
| 分布式事务 | 多个服务间的 2PC | 复杂、性能差、维护难 | 服务内事务 + 跨服务最终一致性 |
| 过度拆分 | 20+ 个微服务 | 运维复杂度爆炸 | 按团队能力拆分,不是越多越好 |
演进路线
阶段一(现在):基础架构
✓ 三个业务服务按域划分
✓ 网关统一入口
✓ JWT 无状态认证
✓ 公共模块统一返回体
阶段二(稳定性):可靠性增强
→ 超时配置 + 熔断器
→ 链路追踪(OpenTelemetry)
→ 完善监控告警
→ 压测与容量规划
阶段三(云原生):生产就绪
→ 容器化(Docker)
→ 编排(Kubernetes)
→ 配置中心(Nacos Config)
→ 灰度发布
阶段四(规模化):进阶优化
→ 服务网格(Istio)- 如果规模很大
→ 数据分片 - 如果数据增长很快
→ 多仓库拆分 - 如果团队扩大
三个一句话总结
-
为什么这样划分
按业务域划分,每个服务围绕一个核心业务概念,独占数据源,一个小团队可完整负责
-
如何保证不出问题
每服务独占数据库、无状态设计、统一协议、失败隔离
-
服务如何协作
JWT 透传做认证、快照冗余做数据一致、异步事件做削峰解耦
记住:微服务不是目的,是手段。解决问题是目的。
项目分区导航:⬅️ 01-微服务 | 02-微服务架构划分与协作:最小最佳实践 | ➡️ 03-项目完整启动指南
💬 评论