微服务架构

一、微服务架构概述

1.1 架构演进:单体 → SOA → 微服务

单体架构是软件系统的起点——所有功能模块打包在一个进程内,部署在一个应用服务器上。早期项目规模小、团队人数少时,单体架构交付效率最高:本地启动即开发,打包即部署。

但随着业务膨胀,单体架构的劣化不可避免:

  • 代码耦合:修改订单模块可能意外影响库存模块,IDE 里一次类引用搜索能跳出几十个调用方,没人敢改。
  • 部署瓶颈:改一行文案也要全量重新部署,回归用例上千条,发布窗口从每周一次拖到每月一次。
  • 扩展困难:热点模块(如秒杀)无法独立扩容,只能整体水平扩展,资源浪费严重。
  • 技术锁定:Java 1.6 升 1.8 全量回归,不敢动。

**SOA(面向服务架构)**试图用 ESB(企业服务总线)解决问题,把系统拆成服务,通过总线通信。但 ESB 本身成了新的单点:所有流量经过总线,总线挂了全站瘫痪;所有服务依赖总线的 XML Schema,版本协调成本极高。SOA 的理念正确但落地太重,属于”治大国如烹小鲜”翻车现场。

微服务架构继承了 SOA 的拆分思想,但去掉了重量的 ESB,改用轻量级通信(HTTP/RPC),强调”去中心化”治理。每个服务独立部署、独立扩展、独立技术选型。Martin Fowler 在 2014 年的文章将微服务推向主流,Spring Cloud、Dubbo、K8s 等生态进一步降低了落地成本。

1
2
3
4
5
6
7
8
9
10
单体架构                        SOA                          微服务架构
┌─────────────────────┐ ┌──────────────┐ ┌──────┐ ┌──────┐ ┌──────┐
│ │ │ ESB │ │Order │ │User │ │Pay │
│ ┌───────┬───────┐ │ │ (智能管道) │ │Svc │ │Svc │ │Svc │
│ │ Order │ User │ │ │ │ └──┬───┘ └──┬───┘ └──┬───┘
│ │ Pay │ Stock │ │ ├──┼──┼──┼──┤ │ │ │
│ └───────┴───────┘ │ │ │ │ │ │ ─────┼───────┼────────┼──── (轻量通信)
│ 单一进程+DB │ │Order User Pay│ ┌──┴──┐ ┌──┴──┐ ┌──┴──┐
└─────────────────────┘ └──────────────┘ │ DB │ │ DB │ │ DB │
└─────┘ └─────┘ └─────┘

1.2 微服务的核心优势

维度 说明
独立部署 单服务发布不需要全量回归,发布时间从”月级”缩到”天级/小时级”
技术异构 推荐服务用 Python 跑模型,订单服务用 Java 保证事务,各取所长
弹性伸缩 大促前只扩容支付和下单服务,不用带着后台管理系统一起扩三倍
故障隔离 评论服务挂了不影响用户浏览商品,这一点单体做不到
组织对齐 服务边界即团队边界,2-pizza team 拥有从开发到运维的完整所有权
可维护性 单个服务代码量可控(1-2 万行),新人两周能上手,不用看半年代码

1.3 微服务的代价(你不会从技术大会 PPT 上看到的)

  • 网络开销:一次本地方法调用变成一次 RPC 调用,延迟从纳秒级升到毫秒级。分布式系统中网络是不可靠的——延迟、丢包、超时,让你的代码必须处处处理”对方挂了怎么办”。
  • 数据一致性:单体的事务 ACID 变成分布式事务 BASE,扣库存和扣余额不在同一个数据库,最终一致性引入大量补偿逻辑和中间状态。”只是拆了两个服务而已,订单状态同步代码写了 2000 行”是真实故事。
  • 运维复杂度:10 个容器 vs 500 个容器,部署流水线、日志采集、链路追踪、监控告警的量级完全不同。K8s + Istio + Prometheus + ELK + Skywalking,光运维链路上的组件比业务服务还多。
  • 调试困境:一个请求经过 8 个服务,每一步都可能有独立的超时/重试策略,定位问题需要全链路追踪。本地启动一个带依赖的服务可能需要 16G 内存。
  • 分布式数据管理:每个服务有自己的数据库,跨服务的联合查询变成 API 聚合。DBA 习惯的 SQL join 不复存在。

1.4 何时用/何时不用微服务

场景 建议
创业初期 / MVP 验证 单体,跑起来再说
团队 < 5 人 单体,拆了没人维护
业务模型未稳定,频繁调整 单体,服务边界难定
性能敏感、低延迟要求 单体或合并部署
团队 > 20 人,多团队并行 微服务,独立迭代
部分模块有独立扩容需求 微服务,精准扩缩容
多种技术栈需求 微服务,技术异构
遗留系统改造 微服务,逐步替换

一句话总结:当单体带来的组织成本大于微服务带来的技术成本时,就是切入微服务的拐点。

1.5 康威定律

“设计系统的组织,其产生的设计等同于这些组织之间的沟通结构。” —— Melvin Conway, 1967

微服务的边界划分不是纯技术决策,而是组织结构决策。如果两个服务分别由不同团队维护,但团队之间需要频繁沟通才能完成一个需求,那这个服务边界大概率划错了。反过来,任何试图绕过组织问题、纯靠技术手段”设计”微服务的行为,最终都会被组织沟通成本反噬。

引申结论:

  • 不要让同一个团队维护超过 3 个核心服务——你无法同时 deep dive 多个领域。
  • 服务拆分要先拆团队——先有人,再划边界,然后写代码。
  • 跨团队 API 必须契约化——接口就是团队间的合同,需要版本管理和兼容性承诺。

二、服务通信

2.1 RPC vs REST vs MQ 对比

微服务间通信主要三种方式,无绝对优劣,只有场景适配:

维度 RPC (Dubbo/gRPC) REST (Spring MVC) MQ (RocketMQ/Kafka)
通信模式 同步(可异步) 同步 异步
传输协议 TCP/HTTP2 HTTP/1.1 或 HTTP/2 TCP(私有协议)
序列化 Protobuf/Hessian 等 JSON/XML 自定义(二进制)
性能 高(长连接+二进制) 中(短连接+文本) 高(批量+顺序写)
耦合度 强(接口依赖) 中(API 契约) 弱(事件驱动)
服务治理 丰富(注册/路由/限流) 需借助网关 依赖 MQ 自身能力
典型场景 内部服务间高频调用 对外 API/跨语言 异步解耦/削峰/事件驱动

选择策略

  • 核心交易链路、对延迟敏感 → RPC(Dubbo/gRPC)
  • 对外开放 API、跨语言调用 → REST
  • 非核心链路、异步解耦 → MQ(详见 消息队列

关于性能:不要一上来就”JSON 慢我要用 Protobuf”。先看你的业务瓶颈在哪——如果整个请求 90% 的时间花在数据库查询上,序列化省下的 5ms 毫无意义。性能优化要遵循阿姆达尔定律:先优化占比大的部分。

2.2 序列化框架对比

本节简要对比,序列化协议及 gRPC 详细实现见 RPC 与异步编程

框架 序列化方式 跨语言 性能 可读性 适用场景
Protobuf 二进制 极高 gRPC,内部高性能
Hessian 二进制 有限 Dubbo 默认
Kryo 二进制 极高 Java 内部高性能
JSON 文本 一般 REST API,跨语言
Fastjson 文本 较高 Java REST 场景

生产建议:

  • 内部服务 → Protobuf + gRPC,强类型契约,编译器保证接口一致性
  • 开放 API → JSON + REST,可读性 > 性能
  • Dubbo 项目 → 默认 Hessian,高并发可换 Protobuf 或 Kryo
  • 避免 Fastjson → 安全漏洞频发,迁移到 Jackson

2.3 服务调用链

一次 RPC 调用在微服务体系下经历的路由远比看上去复杂:

1
2
3
4
5
6
7
8
9
10
11
12
Client

├─ 1. 服务发现 ────────────► 从注册中心获取服务列表
├─ 2. 负载均衡 ────────────► 选定目标节点
├─ 3. 序列化请求 ──────────► 对象 → 字节流
├─ 4. 网络发送 ────────────► TCP/HTTP 传输

Server
├─ 5. 反序列化请求 ────────► 字节流 → 对象
├─ 6. 执行业务逻辑 ────────► 业务处理
├─ 7. 序列化响应 ──────────► 对象 → 字节流
└─ 8. 返回结果

每一步都需要容错保障:

超时控制:每一次 RPC 调用必须设置超时,且超时时间应逐级递减——上游超时 3s,下游就要设 2s,给链路留出传播和重试的 buffer。放任不设超时等于说”这个线程愿意等一辈子”,线程池必炸。

1
2
3
// Dubbo 超时配置(消费端)
@DubboReference(timeout = 3000, retries = 2)
private UserService userService;

重试策略:不是所有失败都应该重试。

  • 幂等写操作 → 可重试(如库存预扣,有幂等键保证)
  • 非幂等写操作 → 禁止重试(如扣款,可能重复扣)
  • 读操作 → 可重试,但需控制次数(v2~3 次),避免雪崩

负载均衡:见第六章,客户端负载均衡(Ribbon/LoadBalancer)在调用前从注册中心获取的实例列表中选一个节点。


1
2
3
4
5
6
7
8
9
# application.yml - Nacos 服务注册
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
namespace: dev # 环境隔离
group: DEFAULT_GROUP
ephemeral: true # AP 模式
1
2
3
4
5
6
7
8
9
10
// 服务调用 + 负载均衡
@Bean
@LoadBalanced // 启用 Ribbon/LoadBalancer 客户端负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}

// 调用时使用服务名,自动从 Nacos 获取实例列表
String result = restTemplate.getForObject(
"http://order-service/orders/1", String.class);

3.4 Eureka

Eureka 是 Netflix 开源的服务发现组件,坚定的 AP 设计——宁可返回过期数据也不拒绝服务。

自我保护模式(Self Preservation Mode):Eureka Server 在 15 分钟内心跳续约率低于 85% 时进入自我保护模式,不再剔除任何实例。这是它 AP 哲学的集中体现:网络分区的另一端可能服务仍然健康,不能盲目剔除。

1
2
3
4
5
6
正常模式:
Eureka Server: "A 心跳超时,剔除" → Consumer 不会再调用 A

自我保护模式:
Eureka Server: "心跳丢失比例过高,可能是网络问题,我不剔除了"
→ Consumer 可能继续调用到不健康的 A(比调不到任何实例要好)

与 Nacos 对比

特性 Eureka Nacos
CAP 模型 AP AP + CP 切换
一致性协议 Peer to Peer 异步复制 Distro (AP) / Raft (CP)
服务健康检查 被动(心跳超时) 主动(TCP/HTTP/MySQL 探测)
配置管理 无(需配合 Spring Config) 内置
自我保护 有(自动触发)
控制台 简单,功能少 功能完善,中文友好
社区活跃度 停止维护(2.x) 持续活跃

结论:新项目建议直接用 Nacos——它同时承担服务发现和配置中心,一个组件顶两个(Nacos = Eureka + Config)。Eureka 2.x 已停止维护,Spring Cloud 官方也推荐 Nacos 作为替代。

3.5 Consul / etcd

特性 Consul etcd
CAP CP CP
协议 Raft Raft
健康检查 内置丰富 需配合(Lease + Watch)
KV 存储 核心能力
多数据中心 原生支持 需自行实现
生态 HashiCorp 生态丰富 K8s 默认存储,运维成熟

选型建议

  • 已用 K8s → 直接用 K8s Service + CoreDNS 做服务发现,etcd 是基础设施层
  • 已用 Spring Cloud → 用 Nacos,与 Spring Cloud 集成最深度
  • 多数据中心/混合云 → 考虑 Consul,原生多数据中心支持
  • 内部基础设施/分布式锁 → etcd,K8s 标配

四、配置中心

4.1 为什么需要配置中心

配置中心解决的远不止”把配置从 jar 包里拿出来”这么简单:

需求 传统方式(application.yml 内置) 配置中心
动态刷新 改配置 → 重新部署 改配置 → 实时生效
版本管理 Git,但和发布耦合 每次变更可追溯/回滚
灰度发布 靠发布系统控制 配置层面精准控制(IP/标签/比例)
权限审计 谁改了不知道 操作人+时间+内容全记录
环境隔离 靠文件名(dev/prod)管理 namespace 物理隔离
配置复用 公共配置每个服务复制一份 公共配置共享,改动同步

4.2 Apollo

Apollo(携程开源)在配置管理领域”出道即巅峰”,其设计理念至今是配置中心的最佳实践。

架构组件

1
2
3
4
5
6
7
8
9
10
11
12
13
┌────────────────┐   ┌──────────────────┐   ┌───────────────────┐
│ Portal │ │ Admin Service │ │ Config Service
│ (管理界面) │ │ (配置修改/发布) │ │ (配置读取/推送) │
│ │ │ │ │ │
│ - 配置编辑 │──►│ - 校验/审计 │──►│ - 客户端拉配置 │
│ - 发布审批 │ │ - 发布/回滚 │ │ - 长轮询推送 │
└────────────────┘ └──────────────────┘ └───────────────────┘
│ │ │
└────────────────────┼────────────────────────┘

┌─────────▼─────────┐
│ MySQL │
└───────────────────┘

发布回滚机制

  • 每一次发布生成一个 ReleaseKey
  • Config Service 对比客户端上报的 ReleaseKey,有变更时推送新配置
  • 发布后发现问题 → 一键回滚到上一版本(本质是发布一个历史版本的快照)
  • 支持灰度发布:可以指定部分实例/IP 先接收新配置,验证通过后全量发布

客户端长轮询:Apollo 客户端不是定时轮询(浪费连接),而是长轮询——客户端发起 HTTP 请求,服务端 hold 住连接(默认 60s),在这期间配置有变更立即返回,没有变更则超时返回空,客户端立即发起下一次长轮询。

1
2
3
4
5
6
7
8
9
10
11
// Apollo 客户端使用
@Value("${timeout:3000}") // 支持热更新(需 @RefreshScope)
private int timeout;

@ApolloConfigChangeListener // 监听配置变更
private void onChange(ConfigChangeEvent event) {
for (String key : event.changedKeys()) {
ConfigChange change = event.getChange(key);
log.info("Config {} changed: {} -> {}", key, change.getOldValue(), change.getNewValue());
}
}

4.3 Nacos Config

Nacos Config 的设计思想:dataId + group + namespace 三级隔离。

1
2
3
Namespace (环境隔离)
└── Group (应用分组)
└── Data ID (具体配置文件)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Data ID 格式: ${prefix}-${spring.profiles.active}.${file-extension}
# 例: order-service-dev.yaml

# bootstrap.yml
spring:
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
namespace: prod # 生产环境
group: ORDER_GROUP # 订单域分组
file-extension: yaml
shared-configs: # 共享配置
- data-id: common.yaml
group: DEFAULT_GROUP
refresh: true

Nacos Config vs Apollo

维度 Nacos Config Apollo
强项 服务发现+配置一体 配置管理专精
灰度发布 支持,功能较简单 支持,功能完善
权限审批 基础 完善(发布审批/操作审计)
版本对比 基础 可视化 diff
客户端语言 Java/Go/Python/Node 主要是 Java
选型建议 已用 Nacos 服务发现 对配置管理要求较高的场景

4.4 热更新原理

Spring Cloud Config + Bus 刷新

Spring Cloud Config 通过 Git 存储配置,配合 Spring Cloud Bus(消息总线)实现广播刷新:

1
2
3
4
5
6
7
8
9
10
11
12
13
开发者 push 配置到 Git


POST /actuator/bus-refresh 到任意一个 Config Client


Spring Cloud Bus 通过 MQ 广播 RefreshRemoteApplicationEvent


所有监听此事件的 Client 执行 ContextRefresher.refresh()


被 @RefreshScope 注解的 Bean 重新初始化,新配置生效

Nacos/Apollo 的推送机制比 Spring Cloud Config 更优雅——不需要主动调用 refresh 端点,客户端长轮询自动感知并推送变更。生产环境建议优先使用 Nacos 或 Apollo 的推送方案,Spring Cloud Config + Bus 适合已部署 RabbitMQ/Kafka 且不希望引入额外组件的场景。


📖 独立文章:API 网关

六、负载均衡

6.1 客户端负载均衡 vs 服务端负载均衡

1
2
3
4
5
6
7
8
9
10
11
12
13
【服务端负载均衡】
Client ──► LVS/Nginx ──► Server A
──► Server B
──► Server C
特点: 请求先到 LB,LB 转发给后端。LB 成为单一入口,需要 HA。

【客户端负载均衡】
Client ──► 1. 从注册中心拉服务列表
──► 2. 本地选一个 Server
──► 3. 直连 Server A

特点: Client 自己做负载均衡,注册中心只在服务列表变更时通知。
省去了中间 LB,但每个 Client 都要引入负载均衡逻辑。

为什么微服务场景推荐客户端负载均衡?

  • 少一跳:Client → Server,省去 LB 中转,延迟更低
  • 无单点:每个 Client 独立决策,LB 挂了不影响已拉取到列表的 Client
  • 更灵活:可以按标签、权重、版本等维度定制路由策略

6.2 Ribbon / Spring Cloud LoadBalancer

Ribbon 是 Netflix 的客户端负载均衡器,Spring Cloud 后来推出了自己的 LoadBalancer 作为替代(Ribbon 已进入维护模式)。

以下以 Ribbon 为例说明策略,Spring Cloud LoadBalancer 原理类似。

负载均衡策略

策略 说明
RoundRobinRule 轮询,按顺序依次选择
RandomRule 随机
WeightedResponseTimeRule 加权,响应越快权重越高
BestAvailableRule 选择并发请求最小的实例
RetryRule 在轮询基础上加重试(超时/失败换下个)
AvailabilityFilteringRule 过滤掉熔断的/高并发的实例
ZoneAvoidanceRule(默认) 复合:先按 Zone 过滤,再轮询
1
2
3
4
5
6
7
8
9
10
// Ribbon 自定义负载均衡规则(全局)
@Bean
public IRule ribbonRule() {
return new WeightedResponseTimeRule();
}

// 针对特定服务
order-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule

6.3 负载均衡算法实现思路

加权轮询:每个实例分配一个固定权重,权重大的实例被选中的概率更大。实现方式——维护一个选择序列:如权重 3:2:1,序列为 [A,A,A,B,B,C],然后在序列上轮询。更优雅的实现是 Nginx 的平滑加权轮询算法,每次选出当前权重最大的实例后减去总权重,保证分布均匀。

最小连接数:实时维护每个实例的活跃连接数(注意不是累计请求数),请求进来时选连接数最小的那个。常用于长连接场景(如 WebSocket 网关),短连接场景差距不大。

一致性哈希:将请求的某个 key(如 userId)哈希映射到哈希环上,顺时针找到最近的服务器节点。优势是同一用户的请求永远落到同一服务器,缓存命中率高;缺点是增加/删除节点时只有相邻节点受影响。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 一致性哈希核心逻辑(简化)
public Server select(List<Server> servers, String key) {
TreeMap<Integer, Server> ring = new TreeMap<>();
for (Server s : servers) {
for (int i = 0; i < VIRTUAL_NODES; i++) {
ring.put(hash(s.getHost() + "#" + i), s);
}
}
int hash = hash(key);
Map.Entry<Integer, Server> entry = ring.ceilingEntry(hash);
if (entry == null) {
entry = ring.firstEntry(); // 超过最大值,取第一个
}
return entry.getValue();
}

生产建议

  • 默认用加权轮询即可,实现简单、分布均匀
  • 实例性能不均匀时用加权,按 CPU 核数/内存比例配权重
  • 有状态服务(如 WebSocket 会话)用一致性哈希,让同一用户固定落到同一实例


八、Spring Cloud 体系

8.1 核心组件全景图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                  ┌─────────────────────────┐
│ Spring Cloud │
│ (分布式系统工具箱) │
└───────────┬─────────────┘

┌────────────┬───────────┼───────────┬────────────┐
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Config │ │ Discovery│ │ Gateway │ │CircuitBr │ │ Bus │
│ Center │ │ (注册中心)│ │ (网关) │ │ (熔断器) │ │ (消息总线)│
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Sleuth │ │ Stream │ │OpenFeign │ │LoadBalan │ │ Config │
│ (链路追踪)│ │ (消息驱动)│ │(声明式调用)│ │ (负载均衡)│ │ (配置) │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘

Netflix OSS 体系(一代目,渐退役):

  • Eureka → 注册中心
  • Ribbon → 客户端负载均衡
  • Hystrix → 熔断器
  • Zuul → 网关
  • Feign → 声明式 HTTP 客户端

Spring Cloud Alibaba 体系(二代目,推荐):

  • Nacos → 注册中心 + 配置中心(取代 Eureka + Config)
  • Sentinel → 流量治理(取代 Hystrix)
  • Seata → 分布式事务
  • RocketMQ → 消息驱动

8.2 Spring Cloud Alibaba 迁移指南

1
2
3
4
5
6
7
原 Netflix 栈                      Alibaba 替代
───────────────── ──────────────────
Eureka ───► Nacos Discovery
Spring Config ───► Nacos Config
Hystrix ───► Sentinel
Ribbon ───► Spring Cloud LoadBalancer (Spring 官方)
Zuul ───► Spring Cloud Gateway (Spring 官方)

迁移重点

  1. Nacos 同时替代 Eureka + Config,一个组件解决两个问题,运维成本减半
  2. Sentinel 比 Hystrix 更灵活——控制台动态下发规则,无需重启,细粒度到参数级别
  3. Spring Cloud Gateway 比 Zuul 性能好,且是官方主推,生态兼容性最优
  4. 保留 Feign(声明式调用)和 Sleuth(链路追踪),它们与底层框架解耦

8.3 Spring Cloud vs K8s Service

这是一个经典争论:”我们用了 K8s,还需要 Spring Cloud 吗?”

能力 Spring Cloud Kubernetes Service
服务发现 Nacos/Eureka CoreDNS + Service
负载均衡 Ribbon/LoadBalancer kube-proxy (iptables/IPVS)
配置管理 Nacos/Apollo/Config ConfigMap/Secret
熔断限流 Sentinel/Hystrix 无原生支持
网关 Spring Cloud Gateway Ingress Controller
服务间通信 Feign/RestTemplate Pod IP 直连
部署方式 虚拟机/Docker 仅 Pod
语言限制 Java(Spring 生态) 无限制
灰度/泳道 丰富(Nacos+Gateway) 需 Istio/Linkerd 配合

结论

  • 纯 Java Spring 团队 → Spring Cloud 全套,上层应用治理能力更强
  • 混合语言多 → K8s Service + Istio,用 Sidecar 透出流量治理
  • 中小团队/快速交付 → Spring Cloud 全套,K8s 只做容器编排
  • 大型企业多语言多团队 → K8s + Istio(Service Mesh),做基础设施层统一治理

两者不互斥——大多数公司是 Spring Cloud(应用层治理)+ K8s(容器编排)混用。


九、微服务治理实践

9.1 服务划分原则:DDD 限界上下文

服务划分是微服务最难的第一步。划得太大 → 退化成分布式单体(Distributed Monolith);划得太小 → 调用链过长、事务协调爆炸、运维崩溃。

DDD 限界上下文(Bounded Context)是微服务拆分的核心方法论

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
电商业务 - 限界上下文划分

┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ 商品上下文 │ │ 订单上下文 │ │ 用户上下文 │
│ │ │ │ │ │
│ - 商品 (SKU) │ │ - 订单 (Order)│ │ - 用户 (User) │
│ - 类目 │ │ - 购物车 │ │ - 地址 │
│ - 库存 │ │ - 支付 │ │ - 积分 │
│ - 价格 │ │ - 物流 │ │ │
└───────┬───────┘ └───────┬───────┘ └───────┬───────┘
│ │ │
└───────────────────┼───────────────────┘
┌───────▼───────┐
│ 营销上下文 │
│ │
│ - 优惠券 │
│ - 活动 │
│ - 推荐 │
└───────────────┘

判断拆分质量的几个指标

  • 两个服务之间是否需要分布式事务?频繁需要 → 可能不该拆分
  • 一个业务变更是否需要同时变更多个服务?几乎每次都连坐 → 边界有问题
  • 两个服务的数据是否需要 join 查询?极其频繁 → 考虑合并

实践口诀

先单体跑通业务 → 识别限界上下文 → 先拆边缘业务练手 → 再拆核心链路
不要一上来就按”一个表一个服务”拆,那是地狱模式。

9.2 灰度发布 / 蓝绿部署

灰度发布(金丝雀发布):新版本只部署少量实例,按条件让部分用户流量打到新版本上。观察新版本无异常后,逐步扩大范围直到 100%。

1
2
3
4
5
6
7
8
9
10
生产环境
┌──────────────────────────────────────────┐
│ 实例 v1.0 (90% 流量) │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐│
│ └────┘ └────┘ └────┘ └────┘ └────┘ └────┘│
│ │
│ 金丝雀实例 v1.1 (10% 流量) │
│ ┌────┐ │
│ └────┘ ← 只放 10% 用户(内部员工/白名单) │
└──────────────────────────────────────────┘

实现方式:

1
2
3
4
5
6
7
# Nacos 元数据灰度路由
spring:
cloud:
nacos:
discovery:
metadata:
version: v1.1 # 金丝雀版本标记
1
2
3
4
5
6
7
8
9
10
11
12
// Gateway 灰度路由
@Bean
public GlobalFilter canaryFilter() {
return (exchange, chain) -> {
String version = exchange.getRequest().getHeaders().getFirst("X-Version");
if ("v1.1".equals(version)) {
// 路由到金丝雀实例
exchange.getAttributes().put("nacos-version", "v1.1");
}
return chain.filter(exchange);
};
}

蓝绿部署:同时维护两套完整环境(蓝 = 当前,绿 = 新版),新版验证通过后流量全部切换到绿。回滚就是切回蓝。

维度 灰度发布 蓝绿部署
资源消耗 小(少数金丝雀节点) 大(两套完整环境)
回滚速度 快(摘流量) 快(切流量)
验证覆盖 渐进式,发现隐性问题更好 全量时才有真实流量
适用场景 日常发布 重大版本/不兼容升级

9.3 全链路压测

微服务架构下的全链路压测不再是”JMeter 打一个接口”那么简单:

核心挑战

  • 如何识别”压测流量”并隔离,避免污染线上数据?
  • 如何让全链路(Gateway → A → B → C → DB)的压测标传递不丢失?
  • 如何让 MQ 的压测消息不被消费者当真?

解决思路

  1. 流量染色:在 Gateway 层为压测流量打标(HTTP Header: X-Stress-Test: true),并在 Dubbo 的 RpcContext、MQ 的 Message Header 中透传
  2. 数据隔离:压测请求使用独立的影子表(如 order → order_shadow)或通过 SQL hint 路由到另一个数据源
  3. 中间件隔离:Message → 独立的压测 Topic;Redis → 独立 DB index
  4. Mock 外部依赖:外部支付/短信等调用在压测时走 Mock 通道

9.4 API 版本管理

微服务不可避免会遇到 API 不兼容升级。不做版本管理 → 所有调用方必须同步升级 → 本质上是分布式单体。

三种常见方案

方案 示例 优点 缺点
URL 版本 /api/v1/orders /api/v2/orders 清晰直观 路由膨胀,代码重复
Header 版本 Accept: version=v1 URL 保持干净 调试不便,不易测试
请求参数版本 /api/orders?version=v1 简单 参数污染,不够 REST

推荐:对外 API 使用 URL 版本(透明、好调试、方便网关路由),内部服务使用 Header 版本(干净),同时遵循以下原则:

  • 向下兼容优先:新增字段不删字段,默认值处理好
  • 同一时刻只维护两个版本:当前版本 + 上一个版本,更老的强制废弃
  • API 契约文档化:用 OpenAPI/Swagger 描述接口,CI 中做兼容性检查

十、总结

微服务的本质不是”拆服务”,而是用组织网络的复杂度换取技术系统的可维护性。拆之前问自己三个问题:

  1. 团队规模够大吗? 小于 20 人,单体可能是最优解。
  2. 业务边界清晰吗? 模型还在迭代期,拆了就要不断地调整边界。
  3. 运维能力跟得上吗? 容器化、监控、链路追踪、CI/CD pipeline 是否就绪?

如果三条都满足,再看本章的组件选型——用 Nacos 做注册+配置,用 Gateway 做入口,用 Sentinel 做流量治理,用 Spring Cloud 做粘合剂。至于 RPC 和 MQ,那是服务通信层的选择(详见 RPC 与异步编程消息队列),而分布式理论如 CAP、BASE、分布式事务等在 分布式系统理论基础 中有详细推导。

微服务不是银弹,它是分布式系统复杂度的某种转移。把问题从”单体内部的耦合”转移到了”服务之间的协调”——你要确保自己真正需要承接后者的复杂度,而不是为了技术潮流做迁移。