--- title: "02-微服务架构划分与协作:最小最佳实践" created: 2025-12-01 tags: - 项目 aliases: - 微服务架构划分与协作:最小最佳实践 --- # 微服务架构划分与协作:最小最佳实践 > 核心目标:用最少的复杂度获得微服务的核心价值 --- ## 为什么这样划分 ### 关键问题 - **拆不拆?** 团队≥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)- 如果规模很大 → 数据分片 - 如果数据增长很快 → 多仓库拆分 - 如果团队扩大 ``` --- ## 三个一句话总结 1. **为什么这样划分** > 按业务域划分,每个服务围绕一个核心业务概念,独占数据源,一个小团队可完整负责 2. **如何保证不出问题** > 每服务独占数据库、无状态设计、统一协议、失败隔离 3. **服务如何协作** > JWT 透传做认证、快照冗余做数据一致、异步事件做削峰解耦 **记住:微服务不是目的,是手段。解决问题是目的。** --- **项目分区导航**:⬅️ [[01-微服务|01-微服务]] | 02-微服务架构划分与协作:最小最佳实践 | ➡️ [[03-项目完整启动指南|03-项目完整启动指南]]