开篇:先校准版本坐标
这篇笔记的主体是面试速查用的知识点,但组件选型会过期。所以先放一张版本表,后面的内容都在这个坐标系里:
| 项目 | 当前状态(2026-10) |
|---|---|
| Spring Boot | 4.0(2025-11-20 GA),基于 Spring Framework 7,JDK 17 为基线 |
| Spring Cloud | 2025.1(Oakwood) 为稳定列车,对齐 Boot 4.0;2026.0(Paddington)在里程碑阶段 |
| Spring Cloud Alibaba | 2025.1.0.0,适配 Spring Boot 4 |
| Nacos | 3.x(3.0 起新增 MCP Registry,3.1 跟进新版注册协议) |
| Seata | 2.6.x(Raft 高可用集群、HTTP 过滤器体系) |
| XXL-JOB | 3.x(3.0 升级到 Spring Boot 3 + JDK 17) |
面试遇到「Spring Cloud 五大组件是什么」该怎么答。
那套答案(Eureka + Ribbon + Hystrix + Zuul + Config)是 2018 年前后 Netflix OSS 全家桶的记忆。今天如果当作”当前方案”背出来,是很明显的知识陈旧信号。正确的答法是先说能力分类,再说现在谁来实现:
注册发现 + 配置中心 → Nacos|网关 → Spring Cloud Gateway|负载均衡 → Spring Cloud LoadBalancer|熔断限流降级 → Sentinel 或 Resilience4j|链路追踪 → Micrometer Tracing + OpenTelemetry
能力是稳定的,组件是会更替的。记住前者,后者随时能查。
4.1 Spring Cloud 组件全景
| 能力 | 现在的主流实现 | 上一代(已退场或有变化) | 变化说明 |
|---|---|---|---|
| 注册中心 | Nacos、Consul | Eureka | Eureka 2.0 中止开发,1.x 已停止演进 |
| 配置中心 | Nacos Config、Apollo | Spring Cloud Config Server | Config Server 仍可用,但国内基本被 Nacos 取代 |
| 网关 | Spring Cloud Gateway | Zuul 1 | Zuul 1 基于阻塞 IO,已淘汰 |
| 服务间调用 | RestClient / WebClient / 声明式 @HttpExchange | RestTemplate、OpenFeign | OpenFeign 仍在用,但已不是唯一选择 |
| 负载均衡 | Spring Cloud LoadBalancer | Ribbon | Ribbon 于 2020 年(Ilford / 2020.0.x)被移除 |
| 熔断降级 | Sentinel(国内)/ Resilience4j(Spring 官方默认) | Hystrix | Hystrix 最后版本 1.5.18(2018),已停更 |
| 链路追踪 | Micrometer Tracing + OpenTelemetry | Sleuth | Boot 3 起 Sleuth 不再适用 |
| 分布式事务 | Seata 2.x | —— | 模式不变,能力增强(Raft 集群) |
这张表怎么用:左边是”现在用什么”,右边是”为什么换”。
面试里最能加分的一句是:“Ribbon / Hystrix 解决的问题依然存在,只是实现换了。” 客户端负载均衡、熔断降级、网关路由这些能力不会因为组件退役而消失 —— 相反,Micrometer Tracing 统一到 OpenTelemetry 之后,跨语言链路的兼容性反而比以前更好。
下文对 Ribbon、Hystrix 的策略细节做了保留,因为理解它们的设计思路,才能理解后继者在哪些地方做了取舍。
4.2 注册中心(Nacos vs Eureka)
注册中心的作用:
- 服务注册:保存服务名称、IP、端口等信息
- 服务发现:消费者拉取服务列表,有集群时通过负载均衡选择调用
- 服务监控:服务提供者定期发送心跳,超时无心跳则剔除
Nacos vs Eureka
共同点:
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
区别:
| 维度 | Nacos | Eureka |
|---|---|---|
| 健康检测 | 临时实例心跳 + 非临时实例主动检测 | 仅心跳 |
| 消息推送 | 服务列表变更主动推送,更及时 | 定时拉取 |
| CAP 模式 | 默认 AP,存在非临时实例时 CP | AP |
| 配置中心 | ✅ 支持 | ❌ 不支持 |
| 通信协议 | 2.x 起 gRPC 长连接 | HTTP 短轮询 |
健康检查方式决定故障发现延迟。 心跳模式最慢要几十秒才能摘除一个死实例,主动探测更快。几十秒 × 每秒调用量 = 故障期间被白白浪费的请求数 —— 这是选型时经常被忽略的一项。
Nacos 3.x 的变化
Nacos 3.0 的重心从”纯微服务注册中心”往”AI 时代的服务/工具注册中心”挪了:
- MCP Registry:把 MCP Server 也纳入注册与发现。配合本博客前面几篇 MCP 文章看会更直观 —— 当 Agent 要连多个 MCP Server 时,“去哪儿找有哪些工具”变成了和”服务发现”同构的问题,Nacos 把它当成同一类基础设施来做。
- 安全默认收紧:3.0 起默认开启鉴权,不再是可以裸奔上线就完事的版本。升级时最常见的报错是启动时提示
NACOS_AUTH_IDENTITY_KEY must be set—— 必须先把NACOS_AUTH_IDENTITY_KEY/NACOS_AUTH_IDENTITY_VALUE/NACOS_AUTH_TOKEN三个密钥配好。另外 3.x 早期版本曾有一处部分 API 鉴权范围错误,官方发布了风险说明和修复版本,3.0 系列请升级到已修复的补丁版,不要停在首发版本上。 - Kubernetes 集成增强:与控制面生态的对接更顺。
冷知识但有用:Nacos 3.0 自身要求 Spring Boot 3 + JDK 17。如果你的 Spring Cloud Alibaba 版本还停在 Boot 2.x,Nacos 客户端和服务端会形成版本错配,升级顺序建议是 Boot → Spring Cloud → Spring Cloud Alibaba → Nacos 服务端。
4.3 服务间调用与负载均衡
负载均衡 = 消费者从注册中心拿到多个服务提供者地址后,决定这次请求发给哪个实例。分两种:
- 服务端负载均衡:Nginx、LVS,转发到后端
- 客户端负载均衡:调用方自己持有实例列表并选择,Spring Cloud LoadBalancer 属于这种
服务间调用方式
负载均衡决定”发给谁”,调用方式决定”怎么发”。Java 生态里现役的有四种:
| 方式 | 同步 / 响应式 | 状态 | 说明 |
|---|---|---|---|
| RestClient | 同步 | ✅ 推荐用于新代码 | Spring 6.1 引入,是 RestTemplate 的现代替代 |
| WebClient | 响应式 | ✅ 推荐 | 非阻塞,适合高并发 / 流式场景 |
声明式 HTTP 接口(@HttpExchange) | 取决于底层客户端 | ✅ Spring 原生 | Spring 6.0 引入,写接口 + 注解,框架生成实现 |
| OpenFeign | 同步 | ⚠️ 仍在用,但不再是唯一选择 | Spring Cloud 体系里的老牌方案;新项目可权衡是否改用原生声明式接口 |
| 同步 | 维护模式 | 仍可用,但新代码建议用 RestClient |
声明式调用的写法已经统一得差不多了:
// Spring 原生:定义一个接口,加注解,注入即用
@HttpExchange("/users")
public interface UserClient {
@GetExchange("/{id}")
User getById(@PathVariable Long id);
}
// 注册
@Configuration
class ClientConfig {
@Bean
UserClient userClient(RestClient.Builder builder, LoadBalancedRestClientBuilderCustomizer lb) {
RestClient restClient = builder.baseUrl("http://user-service").build();
return HttpServiceProxyFactory
.builderFor(RestClientAdapter.create(restClient))
.build()
.createClient(UserClient.class);
}
}
关键点:
baseUrl写的是服务名而不是 IP,客户端负载均衡在这里介入,把服务名解析成具体实例。这正好接上下面 Ribbon / LoadBalancer 的内容 —— 调用方式和负载均衡是两个独立的选择,可以自由组合。
Ribbon 时代的策略(了解即可)
| 策略 | 说明 |
|---|---|
| WeightedResponseTimeRule | 按权重,响应时间越长权重越小 |
| RandomRule | 随机选择 |
| BestAvailableRule | 忽略短路的,选择并发数较低的 |
| RetryRule | 重试机制 |
| AvailabilityFilteringRule | 先过滤非健康,再选连接数较小的 |
| ZoneAvoidanceRule | 按 Zone 分类,Zone 内轮询(默认) |
Ribbon 已于 2020 年从 Spring Cloud 移除,取而代之的是 Spring Cloud LoadBalancer。
变化不只是换了个名字 —— 策略集大幅收缩了。上表那些策略都是 Ribbon 时代的产物,Spring Cloud LoadBalancer 开箱只提供轮询(RoundRobin)和随机(Random)两种。
现在怎么做负载均衡
| 需求 | 做法 |
|---|---|
| 默认均匀分发 | RoundRobinLoadBalancer,开箱即用 |
| 随机 | RandomLoadBalancer |
| 按实例权重(Nacos 控制台配的权重) | 自定义 ReactorServiceInstanceLoadBalancer,读 ServiceInstance 的 weight 元数据 |
| 同机房 / 灰度 / 金丝雀 | 自定义选择器,基于 metadata 过滤后再做轮询 |
// 自定义加权轮询的骨架:注册成 LoadBalancerClientConfiguration 里的 Bean
public class WeightedLoadBalancer implements ReactorServiceInstanceLoadBalancer {
private final ObjectProvider<ServiceInstanceListSupplier> supplier;
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
return supplier.getIfAvailable().get()
.next()
.map(instances -> new DefaultResponse(pickByWeight(instances)));
}
}
这个收缩其实反映了设计取向的变化:负载均衡策略本身不该那么复杂,真正的保护应该交给熔断、限流和超时。Ribbon 时代那种”按响应时间加权”的策略,在有了熔断器之后价值就没那么大了。
换句话说:复杂流量治理从”负载均衡器”这一层,下沉到了”独立治理组件 + 网关”这一层。
4.4 服务雪崩与熔断降级
服务雪崩:分布式系统中某个基础服务故障 → 故障像多米诺骨牌沿调用链蔓延 → 大面积瘫痪。
服务降级
| 方式 | 说明 |
|---|---|
| 返回兜底数据 | 超时或异常时返回预设默认值 |
| 开关降级 | 配置中心动态关闭非核心功能 |
| 读写降级 | 暂停非核心写入,限制只读缓存 |
| 限流降级 | 直接拒绝多余请求 |
降级和熔断的区别常被问:熔断是”切断”,按状态机自动判断;降级是”兜底”,是熔断之后返回什么。 熔断是机制,降级是策略。
熔断器状态机(通用模型)
三步走:关闭(CLOSED) → 开启(OPEN) → 半开(HALF_OPEN)
- CLOSED:正常放行,统计失败率
- OPEN:直接拒绝,走降级逻辑,同时开一个冷却计时器
- HALF_OPEN:冷却结束,放少量探测请求进来 —— 成功则回到 CLOSED,失败则继续 OPEN
这套模型是通用的,Hystrix、Sentinel、Resilience4j 都是同一套。变的是触发条件的默认值和可配置粒度。
现在用哪个
| 方案 | 生态位置 | 特点 | 适合 |
|---|---|---|---|
| Sentinel | 阿里开源,国内主流 | 控制台可视化、规则动态下发、限流 + 熔断 + 热点参数限流一体 | 已有 Nacos 生态、需要动态规则和可视化 |
| Resilience4j | Spring Cloud Circuit Breaker 的默认实现 | 函数式、轻量,组合式装饰器 | 追求和 Spring Boot 原生集成、不想引入额外控制台 |
| Hystrix | 已停更 | —— | 只作为理解历史用 |
两个版本细节值得记:
- Sentinel 的默认熔断条件是:20 秒统计窗口内,异常比例超过 50% 且请求数 ≥ 5 才熔断,熔断时长默认 5 秒。它还支持慢调用比例作为熔断条件 —— Hystrix 没有这个能力。
- Resilience4j 2.4.0 专门提供了
resilience4j-spring-boot4模块来支持 Spring Boot 4。升 Boot 4 时别只升主依赖,这个模块必须一起换。另外,Sentinel 也覆盖限流(见 4.5)。国内实践中通常限流和熔断都用 Sentinel 一套搞定,不再像 Hystrix 时代那样分两个组件。
一个容易踩的坑:熔断配置要匹配真实失败模式
熔断器只看”失败率”,但真实故障分两类:
- 快速失败(下游直接拒绝连接):失败率立刻飙升,熔断器秒开 ✅
- 慢响应(下游不报错,只是变慢):失败率可能一直是 0,但线程全被占满,雪崩照样发生 ❌
第二类必须靠”慢调用比例”或超时来兜底。 只配异常比例的熔断,对慢故障是无效的 —— 这是个面试和实战都常被问到的点。
4.5 限流
限流实现方法
| 层面 | 方案 | 算法 |
|---|---|---|
| Tomcat | 设置最大连接数 / 最大线程数 | 计数 |
| Nginx | limit_req / limit_conn | 漏桶(请求排队、匀速处理) |
| 网关 | Spring Cloud Gateway RequestRateLimiter | 令牌桶(可应对突发) |
| 应用层 | Sentinel / Resilience4j RateLimiter | 滑动窗口 / 令牌桶 |
| 分布式 | Redis + Lua | 滑动窗口 / 令牌桶(多实例共享计数) |
漏桶 vs 令牌桶(面试高频)
| 漏桶(Leaky Bucket) | 令牌桶(Token Bucket) | |
|---|---|---|
| 速率 | 恒定流出速率 | 恒定生成令牌,允许积攒 |
| 突发流量 | 超出速率就直接丢或排队 | 允许短时突发(桶里攒了令牌) |
| 典型实现 | Nginx limit_req | Gateway、Guava RateLimiter |
一句话:漏桶平滑,令牌桶容忍突发。 面向外部用户的网关一般用令牌桶(用户点一下按钮就来一波),保护下游内部服务一般用漏桶或滑动窗口(下游必须匀速才不垮)。
分布式限流
单机限流的问题很明显:10 个实例,每个配 100 QPS,总量就成了 1000 QPS,而你想保护的下游可能只扛得住 300。
标准做法是 Redis + Lua 做共享计数,Lua 保证”读 + 判断 + 写”的原子性:
-- 滑动窗口限流的简化示意(key 按秒切片)
local current = redis.call('INCR', KEYS[1])
if current == 1 then
redis.call('EXPIRE', KEYS[1], ARGV[1])
end
if current > tonumber(ARGV[2]) then
return 0
end
return 1
代价是每次请求多一次 Redis 往返。所以常见的折中是 “单机限流保底 + 网关分布式限流兜底”:单机限流处理突发,网关做全局总量控制。
4.6 CAP 与 BASE
CAP 定理:一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance),三个指标无法同时满足。
注意一个常见误述:P 在分布式系统里是必须的(网络分区是物理事实,不是你能选的),所以真正的选择是 分区发生时,要 C 还是要 A。说”三选二”是不准确的。
BASE 理论:对 CAP 的解决思路。
| 思想 | 说明 |
|---|---|
| Basically Available(基本可用) | 故障时允许损失部分可用性,保证核心可用 |
| Soft State(软状态) | 允许出现中间状态(临时不一致) |
| Eventually Consistent(最终一致性) | 不保证强一致,但最终达到数据一致 |
解决分布式事务的思想
| 思想 | 模式 | 做法 |
|---|---|---|
| 最终一致(AP) | 各分支分别执行提交,不一致再恢复 | 异步、高性能 |
| 强一致(CP) | 各分支执行完等待彼此结果,统一提交或回滚 | 同步、低性能 |
4.7 分布式事务(Seata / Saga / Outbox)
Seata 架构
三个核心角色:
| 角色 | 全称 | 职责 |
|---|---|---|
| TC | Transaction Coordinator(事务协调者) | 维护全局和分支事务状态,协调提交或回滚 |
| TM | Transaction Manager(事务管理器) | 定义全局事务范围,发起提交或回滚 |
| RM | Resource Manager(资源管理器) | 管理分支事务资源,注册分支、报告状态、执行提交回滚 |
Seata 三种模式
| 模式 | CAP | 原理 | 性能 | 侵入性 |
|---|---|---|---|---|
| XA | CP | 各分支互相等待,保证强一致 | 差 | 低 |
| AT | AP | 基于 undo log 实现,一阶段提交 + 二阶段异步补偿 | 好 | 最低 |
| TCC | AP | 需人工编码 Try/Confirm/Cancel | 较好 | 最高 |
AT 是最常用的:业务代码几乎无感,代价是依赖 undo log,且不是所有 SQL 都能被正确解析。
TCC 只在资金类场景值得用 —— 它要求你为每个操作写三个方法,还要处理空回滚、悬挂、幂等三个经典问题,实现成本远高于 AT。
Seata 2.x 的演进
- 2.0 起 TCC 架构重构,并引入 Raft 高可用集群(TC 不再是单点)
- 2.6 增加 HTTP 过滤器体系、Raft 集群能力增强、更多数据库兼容(SQL Server、达梦等)
- 配置注册从早期的 Nacos/Consul/ZK 组合,逐步收敛到与 Nacos 的深度集成
Saga:无中心协调器的补偿模式
Seata 的 Saga 模式(以及自研 Saga)走的是另一条路:一串本地事务 + 每一步配一个补偿动作,没有全局锁。
1. 订单服务:创建订单(本地事务) → 发 OrderCreated
2. 库存服务:扣减库存(本地事务) → 发 InventoryReserved
3. 支付服务:发起扣款(本地事务) → 发 PaymentCompleted
4. 订单服务:订单状态改为「已确认」
任何一步失败,就沿反方向发补偿事件。
这是补偿,不是回滚。 中间状态对外可见,所以业务上必须能接受中间态,每一步的补偿也必须幂等(补偿可能执行多次)。
细节展开见另开的一篇:《微服务该怎么拆》第 7.4 节。
MQ 解决分布式事务:Transactional Outbox(事务性发件箱)
A 服务写数据时,在同一事务内把”要发消息”这件事也落库,再由后台进程或 CDC 投递 —— 异步,性能最好。
⚠️ 常见错误写法:直接
mq.send()是不能和数据库事务绑定的。消息发出去了、事务回滚了,就会不一致。// ❌ 错误:最后一行不在事务里,中间挂掉就丢事件 orderRepo.save(order); mq.send("OrderCreated", order);
标准做法是把消息先写进自己库里的 outbox 表(和业务数据同一个本地事务):
@Transactional
void createOrder(Order order) {
orderRepo.save(order);
outboxRepo.save(new OutboxEvent("OrderCreated", toJson(order))); // 同一事务
}
再由后台进程轮询、或 CDC(Debezium 之类订阅 binlog)投递到 MQ。
配套要求是消费端必须幂等(见 4.8)—— 因为 MQ 的投递语义是 at-least-once,重复消费是常态而非异常。
详细展开(为什么不能用 Saga 跳过这一步、去重表怎么写)见另开的一篇:《微服务该怎么拆》第七节。
Spring Modulith 内置了这套能力 —— 如果你的服务还处在模块化单体阶段,它能把”事件 + outbox”做成一等公民。
横向对比
| 方案 | 一致性 | 性能 | 侵入性 | 适用 |
|---|---|---|---|---|
| XA | 强一致 | 差 | 低 | 传统银行核心、强一致要求 |
| Seata AT | 最终一致 | 好 | 最低 | 大多数业务的主力方案 |
| TCC | 最终一致(较强) | 较好 | 最高 | 资金、库存扣减等高价值操作 |
| Saga | 最终一致 | 最好 | 中 | 长流程、跨多服务 |
| Outbox + 事件 | 最终一致 | 好 | 中 | 只需要”通知”,不要求同步返回 |
选型的一句话:能不做分布式事务就不做 —— 先把服务拆对,让跨服务写变成一件稀有的事(详见《微服务该怎么拆》)。分布式事务是拆错的补偿,不是拆对的奖赏。
4.8 接口幂等性
幂等:对同一操作执行一次和执行多次,产生的影响完全相同。
为什么必须幂等
分布式系统里消息投递至少一次(at-least-once),超时无法区分”对方没收到”和”对方收到了但响应丢了”,所以重试是唯一选择,而重试就意味着可能重复。
重复消费是常态,不是异常。幂等不是优化,是必需品。
RESTful API 幂等性
| 请求方式 | 幂等性 |
|---|---|
| GET | ✅ 天然幂等 |
| POST | ❌ 新增操作,不幂等(除非带幂等键) |
| PUT | ✅(绝对值更新)/ ❌(增量更新) |
| DELETE | ✅ 根据唯一值删除,幂等 |
| PATCH | ❌ 通常是增量,不幂等 |
方案一:Token + Redis
处理业务的同时删除 token,下次调用时没有 token 就不会执行逻辑。
适用的其实是前端重复提交(下单按钮连点),因为服务端能拿到一个”一次性凭证”。
方案二:分布式锁
public void saveOrder(Item item) throws InterruptedException {
RLock lock = redissonClient.getLock("heimalock");
boolean isLock = lock.tryLock(10, TimeUnit.SECONDS);
try {
if (!isLock) {
log.info("下单操作获取锁失败, order:{}", item);
throw new RuntimeException("新增或修改失败");
}
// 下单操作
} finally {
lock.unlock();
}
}
这个例子只解决了并发同时到达的情况。重复投递可能间隔几分钟甚至几小时,锁早就释放了 —— 所以它不能替代幂等。
方案三:唯一索引 / 去重表(最通用)
实际生产中最常用的就是这一种 —— 前两种方案在消息消费做幂等的场景下都不适用:消费者拿不到”token”,重复投递也不受锁保护。
做法是给每个操作一个业务唯一键,插一张去重表,靠数据库的唯一约束拦住重复:
-- event_id 上有唯一索引
INSERT INTO processed_events (event_id) VALUES (?);
-- 唯一键冲突 → 说明处理过了,直接返回成功
为什么推荐它:不依赖应用逻辑正确、不依赖 Redis 可用性,一致性由数据库保证。代价是多一张表和一次写入。
方案四:状态机 / 版本号
有些场景没有天然的业务唯一键,但状态本身带约束:
- 状态机:只允许合法跃迁(已支付的订单不能再支付)。重复事件打过来时,因为状态已经变了,操作自然不生效。
- 乐观锁:更新时带上
version,版本不匹配就失败。适合”并发更新同一条记录”。
怎么选
| 方案 | 适用 | 不适用 |
|---|---|---|
| Token + Redis | 前端重复提交(下单按钮连点) | 消息消费幂等 |
| 分布式锁 | 并发写同一条记录 | 消息消费幂等(重复投递时间跨度大) |
| 唯一索引 / 去重表 | 消息消费幂等、接口重试 | 无业务唯一键的场景 |
| 状态机 / 乐观锁 | 有生命周期或版本号的实体 | 无状态的操作 |
关于幂等为什么是”地基”级别的东西(at-least-once 是物理限制、exactly-once 为什么做不到),见**《微服务该怎么拆》第 7.6 节**。
4.9 分布式任务调度(XXL-JOB)
分布式任务调度 = 把分散的、批量的后台定时任务纳入统一管理调度平台,动态分配到多个计算节点执行。
核心架构
| 组件 | 职责 |
|---|---|
| 调度中心(Scheduler) | 任务配置、触发、分片分配、状态监控 |
| 执行器(Executor/Worker) | 接收指令,执行业务逻辑,上报结果 |
| 存储层(Storage) | 持久化任务状态和日志 |
XXL-JOB 3.x 的变化
- 3.0(2025 年初):整体升级到 Spring Boot 3 + JDK 17,老版本 v2.4.x 的数据模型和通信协议保持兼容,可以平滑升级(但建议按官方说明调整
xxl_job_log的索引)。 - 3.3.x:补了优雅停机(停机时不再粗暴中断正在跑的任务)、Docker Compose 一键部署,以及一系列健壮性修复。
- 调度逻辑本身没变 —— 中心化调度 + 执行器注册,和 2.x 是同一套模型。
XXL-JOB 路由策略
路由策略:当同一任务部署了多个执行器时,调度中心决定把任务发给哪台机器的规则。
| 策略 | 说明 |
|---|---|
| 第一个 / 最后一个 | 固定一台,调试时用 |
| 轮询 | 依次分发,最常用 |
| 随机 | 随机选一台 |
| 一致性 HASH | 按参数固定落到某台,适合有本地缓存的场景 |
| 最不经常使用 / 最近最久未使用 | 按使用频率选 |
| 故障转移 | 失败后自动切换下一台 |
| 忙碌转移 | 跳过正在忙的执行器 |
| 分片广播 | 见下 |
大数据量任务处理 — 分片广播
轮询 vs 分片广播:轮询是把”整个任务”轮流交给不同机器;分片广播是把”一个大任务”拆成多份,所有机器同时并行处理。
分片广播:部署多个实例,路由策略选分片广播,按取模方式分摊到各实例。执行器代码里通过 ShardingUtil.getShardingVo() 拿到当前分片序号和总数,只处理属于自己的那部分数据。
典型场景:全量用户对账、历史数据清理。注意分片是逻辑切分,不是数据隔离 —— 某个实例挂了,它负责的那片数据这一轮就没人处理,需要有补跑机制。
4.10 可观测性(新增)
拆成微服务之后,“一次请求到底经过了哪几个服务、卡在哪一跳” 从一个 debug 断点的问题,变成了一个跨服务关联的问题。这一层没做好,拆分就是负收益。
三支柱
| 支柱 | 关键点 | Java 生态 |
|---|---|---|
| 日志 Logs | 结构化 + trace ID 贯穿 | Logback + MDC,trace ID 由 Tracing 注入 |
| 指标 Metrics | RED 方法:Rate / Errors / Duration | Micrometer + Prometheus |
| 链路追踪 Traces | 一次请求的完整调用图与每跳耗时 | Micrometer Tracing + OpenTelemetry |
trace ID 是分水岭。 没有它,你手上只是一堆服务各自的日志文件;有了它,才能把一次故障的所有证据串成一条线。
为什么是 OpenTelemetry
Micrometer Tracing 的底层抽象可以接 Brave 或 OTel。选 OTel 的理由是跨语言:Java 服务追踪一个 Go 服务、一个 Python 服务,用的协议和上下文传播格式是统一的。
这是少数值得”提前站队”的选型 —— 一旦链路数据格式统一,后面用什么后端(Jaeger / Tempo / 商业 APM)都可以换。
一个实际建议
先别急着上全量采样。 高 QPS 服务做 100% 采样,trace 存储成本会先于收益爆炸。常见策略是:错误请求 + 慢请求全采,正常请求低比例采样。
4.11 服务网格、Kubernetes 与 AI 时代的治理(新增)
什么时候会从 SDK 治理转向服务网格
| 阶段 | 治理方式 | 痛点 |
|---|---|---|
| 服务少(< 20) | 每个服务引 SDK(Sentinel、Resilience4j) | 无 |
| 服务多(几十上百) | SDK 仍然是主力,但…… | 多语言栈要各写一套;治理逻辑升级要所有服务跟着发版 |
| 服务很多 + 多语言 | 治理下沉到 sidecar(Istio / Linkerd) | 多一层代理,延迟和运维复杂度上升 |
服务网格的核心思路:把超时、重试、熔断、mTLS、流量灰度这些能力从业务进程里搬到 sidecar 代理,业务代码零侵入。控制面统一下发规则,所有服务的治理行为一致。
但不要过早引入。 服务数量和语言栈都不复杂时,服务网格带来的复杂度(代理生命周期、资源开销、调试链路变长)远大于收益。它解决的是”治理逻辑和业务代码耦合导致升级困难”这个规模化问题。
Kubernetes 带来的变化
容器化之后,很多”服务发现”的活被 Kubernetes 的 Service / DNS 接走了一部分:
- 同一集群内,调用方可以直接按 Service 名访问,不必再经过注册中心的客户端负载均衡
- 但跨集群、跨云、混合部署(虚拟机 + 容器)的场景,仍然需要 Nacos 这类独立注册中心
- 健康检查也从应用层心跳,部分转移到了 kubelet 的 liveness / readiness probe
所以国内主流形态通常是 Kubernetes + Nacos 并存:K8s 管调度和集群内发现,Nacos 管跨环境的服务注册与配置。这不是重复造轮子,是两种维度的发现。
MCP Registry:注册中心的下一类”实例”
Nacos 3.0 新增的 MCP Registry 值得单独提一句,因为它说明注册中心这个抽象正在扩张。
传统注册中心注册的是”服务实例”(某个 IP:Port 上跑着一个能响应 HTTP 的进程)。当 Agent 要调用多个 MCP Server 时,需要回答的问题结构上完全一样:有哪些工具可用?它们在哪儿?哪个还活着?
graph LR
A["传统服务发现"] --> B["服务名 → 实例列表 → 选一个调用"]
C["MCP Registry"] --> D["工具名 → MCP Server 列表 → 选一个调用"]
- 服务端注册(Server 启动时上报自己提供哪些 tools / resources)
- 客户端发现(Agent 运行时按需拉取工具清单)
- 健康与权限(沿用 Nacos 已有的鉴权体系)
这个变化对面试的意义:如果你在做 AI 应用方向,被问到”服务发现”时能顺着说到 “Agent 时代工具发现是同构问题”,会比只背 Nacos vs Eureka 有说服力得多。本博客前面几篇 MCP 文章讲的是协议层,这里补的是基础设施层。
4.12 面试速查:高频问题的现代答案
| 问题 | 一句话答案 | 展开见 |
|---|---|---|
| Spring Cloud 五大组件? | 按能力答,不按 Netflix 组件名答 | 开篇 |
| 注册中心选型? | 国内 Nacos,国外 Consul;Eureka 已停止演进 | 4.2 |
| 服务间怎么调用? | 新代码用 RestClient / WebClient / @HttpExchange,OpenFeign 已非唯一选择 | 4.3 |
| 负载均衡有哪些策略? | LoadBalancer 开箱只有轮询和随机,加权/灰度要自定义 | 4.3 |
| 熔断和降级区别? | 熔断是切断机制,降级是兜底策略 | 4.4 |
| 熔断器状态机? | CLOSED → OPEN → HALF_OPEN,三种实现通用 | 4.4 |
| Sentinel 和 Resilience4j 怎么选? | 要控制台和动态规则选 Sentinel,要原生集成选 Resilience4j | 4.4 |
| 漏桶和令牌桶区别? | 漏桶匀速,令牌桶容忍突发 | 4.5 |
| 分布式限流怎么做? | Redis + Lua 原子计数,网关兜底 | 4.5 |
| 分布式事务有哪些方案? | XA / AT / TCC / Saga / Outbox,能不做就不做 | 4.7 |
| 为什么要有 Outbox? | 双写问题:写库和发消息不在一个事务里 | 4.7 |
| 幂等怎么做? | 消息消费用去重表(唯一索引),前端提交用 token | 4.8 |
| 为什么做不到 exactly-once? | 超时无法区分”没收到”和”响应丢了”,只能 at-least-once | 4.8 |
| 链路追踪用什么? | Micrometer Tracing + OpenTelemetry,Sleuth 已淘汰 | 4.10 |
| 什么时候用服务网格? | 服务多且多语言,SDK 治理成了负担时 | 4.11 |
| MCP 和注册中心什么关系? | MCP Registry 把”工具发现”做成了同构的服务发现 | 4.11 |