微服务架构划分与协作:最小最佳实践

核心目标:用最少的复杂度获得微服务的核心价值


为什么这样划分

关键问题

  • 拆不拆? 团队≥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/1Authorization: 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-微服务 | 02-微服务架构划分与协作:最小最佳实践 | ➡️ 03-项目完整启动指南