第六篇:可靠性篇 — Sentinel 熔断限流与 Seata 分布式事务

目标 :构建高可用、高可靠的微服务系统
学习时长:2~3 周


目录

  1. 微服务可靠性挑战
  2. [Sentinel 概述](#Sentinel 概述)
  3. 流量控制(限流)
  4. 熔断降级
  5. 系统自适应保护
  6. [Sentinel 与 OpenFeign 集成](#Sentinel 与 OpenFeign 集成)
  7. Resilience4j(对比)
  8. 分布式事务问题
  9. [Seata 分布式事务框架](#Seata 分布式事务框架)
  10. [Seata AT 模式实战](#Seata AT 模式实战)
  11. [TCC 模式](#TCC 模式)
  12. [Saga 模式](#Saga 模式)
  13. 链路追踪
  14. 面试高频题

1. 微服务可靠性挑战

复制代码
微服务 A → 微服务 B → 微服务 C

如果 C 超时 → B 等待 C 的线程被耗尽 → B 也超时 → A 等待 B 的线程被耗尽
→ 整个链路雪崩!

这就是"级联故障"(Cascading Failure)

可靠性核心手段

手段 目标 实现
限流 控制流入速率,保护服务不被压垮 Sentinel、Gateway
熔断 下游故障时快速失败,防止级联 Sentinel、Resilience4j
降级 提供备用逻辑(缓存值、默认值) Feign Fallback
隔离 限制并发(线程池/信号量) Sentinel、Hystrix
重试 临时故障自动重试 Spring Retry、Feign
超时 设置超时,不无限等待 Feign 超时配置

2. Sentinel 概述

阿里巴巴开源,以流量 为切入点,从限流、熔断降级、系统自适应保护多个维度保护服务。

核心概念

复制代码
资源(Resource):被保护的对象(Controller 方法、Service 方法、Feign 调用)
规则(Rule):针对资源配置的流控/熔断规则

引入依赖

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
yaml 复制代码
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8080  # Sentinel 控制台地址
        port: 8719                 # 与控制台通信端口
      eager: true                  # 立即连接控制台(否则首次请求后才连)

3. 流量控制(限流)

注解方式

java 复制代码
@Service
public class OrderService {

    @SentinelResource(
        value = "createOrder",                   // 资源名
        blockHandler = "createOrderBlocked",     // 触发限流/熔断时的处理方法
        fallback = "createOrderFallback"         // 业务异常时的降级方法
    )
    public Order createOrder(Long userId, Long productId) {
        // 正常业务逻辑
        return doCreateOrder(userId, productId);
    }

    // 限流处理(必须同包、同返回类型、参数列表末尾加 BlockException)
    public Order createOrderBlocked(Long userId, Long productId, BlockException ex) {
        log.warn("下单请求被限流: userId={}, rule={}", userId, ex.getRule());
        throw new BusinessException("下单频率过高,请稍后再试");
    }

    // 降级处理(业务异常时触发)
    public Order createOrderFallback(Long userId, Long productId, Throwable t) {
        log.error("下单失败,触发降级: {}", t.getMessage());
        throw new BusinessException("下单服务异常,请稍后重试");
    }
}

规则配置(推荐通过 Sentinel 控制台)

java 复制代码
// 也可以通过代码配置(不推荐,不灵活)
@PostConstruct
public void initRules() {
    List<FlowRule> rules = new ArrayList<>();
    FlowRule rule = new FlowRule();
    rule.setResource("createOrder");
    rule.setGrade(RuleConstant.FLOW_GRADE_QPS);  // 按 QPS 限流
    rule.setCount(10);                             // 每秒最多10次
    rules.add(rule);
    FlowRuleManager.loadRules(rules);
}

流控模式

模式 说明
直接(默认) 达到阈值直接拒绝
关联 当关联资源达到阈值,限制本资源(写操作高时限制读操作)
链路 只统计来自特定入口的调用

流控效果

效果 说明
快速失败(默认) 直接抛出 FlowException
Warm Up(预热) 启动时慢慢增大阈值,防止冷启动被打垮
排队等待 请求排队,超过最大等待时间才拒绝(令牌桶)

4. 熔断降级

熔断状态机

复制代码
CLOSED(正常)─────────────────────→ OPEN(熔断,快速失败)
  (慢调用比例/错误率/异常数超阈值)          ↓
         ↑                         (休眠窗口结束)
         │                                 ↓
         └──────── HALF-OPEN(半开,放少量探测请求)
              (探测成功 → CLOSED)
              (探测失败 → 重新 OPEN)

熔断策略

java 复制代码
@PostConstruct
public void initDegradeRules() {
    List<DegradeRule> rules = new ArrayList<>();
    DegradeRule rule = new DegradeRule();
    rule.setResource("getUserById");

    // 策略1:慢调用比例熔断
    // 统计窗口内慢调用(>1秒)比例超过60%,触发熔断5秒
    rule.setGrade(RuleConstant.DEGRADE_GRADE_RT);
    rule.setCount(1000);       // 慢调用阈值(ms)
    rule.setSlowRatioThreshold(0.6);  // 慢调用比例60%
    rule.setStatIntervalMs(10000);    // 统计窗口10秒
    rule.setTimeWindow(5);            // 熔断持续5秒

    // 策略2:异常比例熔断
    // rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
    // rule.setCount(0.5);  // 异常比例50%

    // 策略3:异常数熔断
    // rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);
    // rule.setCount(10);   // 10次异常

    rules.add(rule);
    DegradeRuleManager.loadRules(rules);
}

5. 系统自适应保护

系统级别的整体保护,防止系统过载:

java 复制代码
// 基于系统负载(Load)、CPU 使用率、平均 RT、QPS 等指标综合判断
SystemRule rule = new SystemRule();
rule.setHighestSystemLoad(4.0);  // Load 超过4.0时保护(CPU核数*2.5)
rule.setAvgRt(200);              // 平均响应时间超过200ms
rule.setMaxThread(200);          // 并发线程数超过200
rule.setQps(1000);               // QPS 超过1000
SystemRuleManager.loadRules(List.of(rule));

6. Sentinel 与 OpenFeign 集成

yaml 复制代码
feign:
  sentinel:
    enabled: true  # 开启 Feign 的 Sentinel 支持
java 复制代码
@FeignClient(
    name = "user-service",
    fallback = UserFallback.class    // Feign + Sentinel 降级
)
public interface UserFeignClient {
    @GetMapping("/api/users/{id}")
    Result<User> getUserById(@PathVariable("id") Long id);
}

// 当 user-service 触发熔断/限流时,自动调用此降级
@Component
public class UserFallback implements UserFeignClient {
    @Override
    public Result<User> getUserById(Long id) {
        return Result.fail(503, "用户服务熔断中,请稍后重试");
    }
}

7. Resilience4j(对比)

Spring Cloud 2020 之后推荐 Resilience4j 替代 Hystrix:

xml 复制代码
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
java 复制代码
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
@RateLimiter(name = "userService")
@Retry(name = "userService")
public User getUserById(Long id) {
    return userFeignClient.getUserById(id).getData();
}

public User getUserFallback(Long id, Throwable t) {
    return new User();  // 降级返回默认用户
}
yaml 复制代码
resilience4j:
  circuitbreaker:
    instances:
      userService:
        sliding-window-type: COUNT_BASED       # 基于请求次数
        sliding-window-size: 10                # 统计最近10次请求
        failure-rate-threshold: 50             # 50%失败率触发熔断
        wait-duration-in-open-state: 10s       # 熔断10秒
        permitted-number-of-calls-in-half-open-state: 3  # 半开状态放3个探测

8. 分布式事务问题

复制代码
用户下单(order-service 本地事务)
    ↓ Feign 调用
扣减库存(product-service 本地事务)
    ↓ Feign 调用
扣减余额(account-service 本地事务)

问题:三个操作跨越三个服务(三个数据库),如何保证原子性?
→ 一步失败时,已成功的步骤如何回滚?

分布式事务解决方案

方案 一致性 侵入性 性能 适用场景
Seata AT 最终一致 无侵入 中 通用,推荐
Seata TCC 强一致 高侵入 高 资金类
Saga 最终一致 中 高 长事务、跨组织
本地消息表 最终一致 中 高 异步场景
MQ 事务消息 最终一致 低 高 消息驱动场景

9. Seata 分布式事务框架

核心角色

复制代码
TC(Transaction Coordinator):Seata Server,全局事务协调者
TM(Transaction Manager):@GlobalTransactional 发起者(业务服务)
RM(Resource Manager):参与者(各微服务,持有本地数据库)

流程:
1. TM 向 TC 申请开启全局事务(得到 XID)
2. TM 调用各 RM(通过 Feign)
3. 每个 RM 执行本地事务,向 TC 注册分支事务
4. 所有 RM 成功 → TM 通知 TC 提交全局事务 → 各 RM 提交
5. 任一 RM 失败 → TM 通知 TC 回滚 → 各 RM 回滚(AT 模式通过 undo_log 回滚)

启动 Seata Server

bash 复制代码
docker run -d -p 8091:8091 -p 7091:7091 \
  -e STORE_MODE=db \
  -e STORE_DB_URL=jdbc:mysql://mysql:3306/seata \
  seataio/seata-server:latest

10. Seata AT 模式实战

引入依赖

xml 复制代码
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
yaml 复制代码
seata:
  registry:
    type: nacos
    nacos:
      server-addr: localhost:8848
  tx-service-group: my_tx_group  # 事务分组(需与 Seata Server 配置对应)
  service:
    vgroup-mapping:
      my_tx_group: default
sql 复制代码
-- 每个参与事务的数据库中添加 undo_log 表(AT 模式必须)
CREATE TABLE undo_log (
    id           bigint AUTO_INCREMENT PRIMARY KEY,
    branch_id    bigint NOT NULL,
    xid          varchar(128) NOT NULL,
    context      varchar(128) NOT NULL,
    rollback_info longblob NOT NULL,
    log_status   int NOT NULL,
    log_created  datetime NOT NULL,
    log_modified datetime NOT NULL,
    UNIQUE KEY ux_undo_log (xid, branch_id)
);

使用 @GlobalTransactional

java 复制代码
@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final ProductFeignClient productFeignClient;
    private final AccountFeignClient accountFeignClient;

    /**
     * @GlobalTransactional 开启全局事务
     * 内部任何步骤失败(包括 Feign 调用),所有操作自动回滚!
     */
    @GlobalTransactional(
        name = "create-order-tx",          // 全局事务名(便于日志追踪)
        rollbackFor = Exception.class,     // 遇到任何异常都回滚
        timeoutMills = 30000               // 全局事务超时30秒
    )
    public Order createOrder(CreateOrderDTO dto) {
        // 1. 创建本地订单(自动加入全局事务)
        Order order = orderRepository.save(buildOrder(dto));

        // 2. 扣减库存(Feign 调用 product-service,也在全局事务中)
        productFeignClient.deductStock(dto.getProductId(), dto.getQuantity());

        // 3. 扣减余额(Feign 调用 account-service,也在全局事务中)
        accountFeignClient.deductBalance(dto.getUserId(), order.getTotalAmount());

        // 任一步骤抛异常 → Seata 协调所有服务回滚
        return order;
    }
}

AT 模式原理(两阶段提交)

复制代码
Phase 1(执行):
  RM 执行本地事务,生成 undo_log(记录前镜像 before image 和后镜像 after image)
  本地事务提交,但 undo_log 不删除

Phase 2(提交/回滚):
  提交:TC 通知各 RM 删除 undo_log,释放锁(正常路径)
  回滚:TC 通知各 RM 根据 undo_log 生成反向 SQL 执行,恢复数据

11. TCC 模式

TCC = Try(尝试)- Confirm(确认)- Cancel(取消)

java 复制代码
// 每个微服务需实现三个方法
@LocalTCC
public interface StockService {

    // Try:预留资源(锁定库存,但不立即扣减)
    @TwoPhaseBusinessAction(name = "deductStock",
                            commitMethod = "confirm",
                            rollbackMethod = "cancel")
    boolean tryDeductStock(BusinessActionContext ctx,
                           @BusinessActionContextParameter("productId") Long productId,
                           @BusinessActionContextParameter("quantity") Integer quantity);

    // Confirm:确认扣减(实际执行)
    boolean confirm(BusinessActionContext ctx);

    // Cancel:取消,归还预留资源
    boolean cancel(BusinessActionContext ctx);
}

TCC vs AT:

  • TCC 侵入性高,每个服务需实现三个方法
  • TCC 不依赖 undo_log,性能更好
  • TCC 适合金融、强一致场景
  • AT 侵入性低,仅加一个注解,适合普通业务

12. Saga 模式

适合长流程(如电商履约:下单→支付→发货→签收):

复制代码
正向流程(成功路径):
  下单 → 支付 → 锁库存 → 发货

补偿流程(某步失败时,反向补偿):
  如果"发货"失败:
    取消发货 → 解锁库存 → 退款 → 取消订单

关键:每个步骤都需要对应的"补偿操作"

13. 链路追踪

xml 复制代码
<!-- 分布式链路追踪 -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>
yaml 复制代码
management:
  tracing:
    sampling:
      probability: 1.0  # 100% 采样(生产环境建议 0.1=10%)
  zipkin:
    tracing:
      endpoint: http://zipkin:9411/api/v2/spans
复制代码
访问 http://localhost:9411(Zipkin UI)
查看完整调用链路:Gateway → order-service → user-service → product-service
每个 Span 包含:耗时、异常、服务名
通过 TraceId 串联整条链路

14. 面试高频题

Q1:Sentinel 和 Hystrix 的区别?

Hystrix:线程隔离(每个资源一个线程池),开销大,已停更;Sentinel:基于 QPS 和并发线程数控制,无线程池开销,实时 Dashboard,规则可动态修改,国内主流。

Q2:熔断器的三种状态是什么?

CLOSED(正常,请求通过)→ OPEN(熔断,快速失败,不访问后端)→ HALF-OPEN(半开,放少量请求试探)→ 成功则回 CLOSED,失败则重新 OPEN。

Q3:Seata AT 模式的原理?

基于两阶段提交,但 Phase 1 直接提交本地事务(不锁)并记录 undo_log;Phase 2 成功则删 undo_log,失败则用 undo_log 执行反向 SQL 回滚。相比传统 2PC,减少了全局锁持有时间。

Q4:分布式事务的解决方案有哪些?如何选择?

①Seata AT(无侵入,推荐通用场景);②TCC(侵入高,适合强一致资金场景);③Saga(适合长事务,最终一致);④本地消息表+MQ(异步场景,最终一致,性能好)。优先选 Seata AT,对性能有极致要求才考虑 TCC。

Q5:什么是幂等性?分布式系统为什么需要它?

幂等性:同一请求执行多次与执行一次结果相同。网络重传、Feign 重试、消息队列重消费都会导致重复调用,需要通过唯一索引、Token 机制、状态机等保证幂等。

Q6:链路追踪的 TraceId 是如何传递的?

每个服务接收请求时,若 Header 中有 TraceId 则继承,否则生成新 TraceId;调用下游时将 TraceId 放入请求 Header(Feign 自动传递)。Micrometer/Brave 通过 MDC 将 TraceId 注入日志,实现日志串联。


上一篇:05_网关篇


附录:六篇学习路径

复制代码
阶段一(1周):基础篇 → 理解微服务概念,搭建多模块项目
阶段二(1周):注册中心 → Eureka Server 搭建,服务注册发现
阶段三(1周):通信篇 → OpenFeign 跨服务调用,负载均衡
阶段四(1周):配置中心 → Nacos Config 集中配置,热更新
阶段五(1周):网关篇 → Gateway 路由,全局鉴权,限流
阶段六(2周):可靠性 → Sentinel 熔断,Seata 分布式事务
篇章 Demo 服务 核心演示
基础篇 多模块项目骨架 服务分层、父 POM
注册中心篇 eureka-server + 各服务 注册发现、心跳
通信篇 order-service Feign 跨服务调用、Fallback
配置中心篇 Nacos Config 热更新、多环境
网关篇 gateway-service 路由、过滤、CORS
可靠性篇 Sentinel 规则 + Seata 熔断、分布式事务

15. 专家级:Sentinel 规则持久化(生产必备)

知识点:为什么规则需要持久化

Sentinel 的流控/熔断规则默认存储在内存中:

text 复制代码
问题:服务重启后,所有在控制台配置的规则全部丢失!
      → 生产环境不可接受

解决:规则持久化到 Nacos,服务启动时自动加载,
      控制台修改后自动推送给所有实例

知识点:接入 Nacos 规则持久化

xml 复制代码
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
yaml 复制代码
spring:
  cloud:
    sentinel:
      datasource:
        # 流量控制规则
        flow-rules:
          nacos:
            server-addr: ${spring.cloud.nacos.discovery.server-addr}
            namespace: ${spring.cloud.nacos.discovery.namespace}
            group-id: SENTINEL_GROUP
            data-id: ${spring.application.name}-flow-rules
            data-type: json
            rule-type: flow

        # 熔断降级规则
        degrade-rules:
          nacos:
            server-addr: ${spring.cloud.nacos.discovery.server-addr}
            namespace: ${spring.cloud.nacos.discovery.namespace}
            group-id: SENTINEL_GROUP
            data-id: ${spring.application.name}-degrade-rules
            data-type: json
            rule-type: degrade

        # 系统保护规则
        system-rules:
          nacos:
            server-addr: ${spring.cloud.nacos.discovery.server-addr}
            group-id: SENTINEL_GROUP
            data-id: ${spring.application.name}-system-rules
            data-type: json
            rule-type: system

在 Nacos 中创建流控规则(JSON 格式):

json 复制代码
[
  {
    "resource": "createOrder",
    "grade": 1,
    "count": 100,
    "strategy": 0,
    "controlBehavior": 0,
    "clusterMode": false
  }
]

字段说明:

  • grade: 1=QPS限流, 0=并发线程数限流
  • count: 阈值(100 QPS)
  • strategy: 0=直接, 1=关联, 2=链路
  • controlBehavior: 0=快速失败, 1=预热, 2=排队

16. 专家级:本地消息表 + MQ 实现最终一致性

知识点:为什么需要本地消息表

Seata AT 对数据库有侵入(需要 undo_log 表),且存在全局锁影响性能。对于异步场景 (下单→发通知→更新积分),可以用更轻量的本地消息表 + MQ方案。

核心思路:利用本地事务的原子性,将"业务操作"和"记录消息"放在同一个本地事务中,再由定时任务将消息发送到 MQ。

text 复制代码
问题:跨服务操作(订单+积分)如何保证原子性?

传统方案(有问题):
  1. 本地创建订单(commit)
  2. 发送 MQ 消息(可能失败!消息丢失,积分不更新)

本地消息表方案:
  1. 同一事务:创建订单 + 写消息记录(status=PENDING)→ 一起提交
  2. 定时任务:扫描 PENDING 消息 → 发送到 MQ → 更新 status=SENT
  3. 消费者:处理消息 → 更新积分 → 发送确认 ACK
  4. 定时任务:SENT超时未确认 → 重发(幂等保证)

保证:订单创建 ↔ 消息记录 是原子的(同一事务)
      消息最终一定会被处理(至少一次,配合幂等)

实现代码:

java 复制代码
@Entity
@Table(name = "t_local_message")
@Data
public class LocalMessage {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String messageId;        // 唯一ID(幂等Key)
    private String topic;            // MQ Topic
    private String content;          // 消息内容(JSON)
    private Integer status;          // 0=PENDING 1=SENT 2=CONFIRMED
    private Integer retryCount;      // 重试次数
    private LocalDateTime createTime;
    private LocalDateTime nextRetryTime;
}

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final LocalMessageRepository messageRepository;
    private final ObjectMapper objectMapper;

    /**
     * 创建订单 + 记录消息(同一事务保证原子性)
     */
    @Transactional
    public Order createOrder(CreateOrderDTO dto) {
        // 1. 创建订单
        Order order = orderRepository.save(buildOrder(dto));

        // 2. 同一事务中写入本地消息(代替直接发 MQ)
        LocalMessage message = new LocalMessage();
        message.setMessageId(UUID.randomUUID().toString());
        message.setTopic("order.created");
        message.setContent(objectMapper.writeValueAsString(
            new OrderCreatedEvent(order.getId(), dto.getUserId(), order.getTotalAmount())));
        message.setStatus(0);  // PENDING
        message.setRetryCount(0);
        message.setNextRetryTime(LocalDateTime.now().plusSeconds(10));
        messageRepository.save(message);

        return order;
    }
}

// 定时发送器(独立任务)
@Component
@RequiredArgsConstructor
@Slf4j
public class LocalMessageSender {

    private final LocalMessageRepository messageRepository;
    private final RabbitTemplate rabbitTemplate;

    @Scheduled(fixedDelay = 5000)  // 每5秒扫描一次
    @Transactional
    public void sendPendingMessages() {
        List<LocalMessage> pending = messageRepository.findPendingMessages(
            LocalDateTime.now(), 3);  // 未超过3次重试的PENDING消息

        for (LocalMessage msg : pending) {
            try {
                rabbitTemplate.convertAndSend(msg.getTopic(), msg.getContent());
                msg.setStatus(1);  // SENT
                log.info("消息发送成功: {}", msg.getMessageId());
            } catch (Exception e) {
                msg.setRetryCount(msg.getRetryCount() + 1);
                msg.setNextRetryTime(LocalDateTime.now().plusSeconds(
                    30L * msg.getRetryCount()));  // 指数退避
                log.error("消息发送失败,将重试: {}", msg.getMessageId());
            }
            messageRepository.save(msg);
        }
    }
}

17. 专家级面试追问

Q:Seata AT 模式有哪些性能问题?如何优化?

AT 模式的性能瓶颈:①全局锁(写操作需要获取全局行锁,影响并发);②undo_log 存储开销(每次写操作生成 before/after image)。优化:①高并发写场景考虑 TCC;②undo_log 定期清理(Seata 内置清理机制);③缩短事务超时时间,减少锁持有时间。

Q:分布式事务和消息队列各适用什么场景?

Seata(强一致):资金转账、库存扣减等需要即时一致的场景;消息队列(最终一致):发送通知、更新积分、刷新缓存等允许短暂不一致的场景。原则:能用最终一致就不用强一致(性能更好)。

Q:Sentinel 的"热点参数限流"是什么?

对方法的某个参数值进行独立限流。如 getProduct(productId) 中,某个爆款商品(productId=1001)的访问量极大,可以单独对 productId=1001 配置更严格的限流(如10QPS),其他商品继续享受100QPS的通用限流,防止热点击穿。

Q:如何设计幂等性?

①数据库唯一索引:重复插入时触发唯一键冲突;②Token 机制:提交前获取一次性 Token,服务端验证 Token 有效且未使用;③状态机:订单只能从"待支付"→"已支付",重复支付直接返回成功;④Redis SETNX:原子操作标记请求已处理,TTL 过期后允许重试。


18. Resilience4j 完整实战(Spring Cloud 推荐)

知识点 1:Resilience4j 五大模块

text 复制代码
1. CircuitBreaker(断路器):熔断,防止级联故障
2. RateLimiter(限速器):限制调用频率
3. Retry(重试):临时故障自动重试
4. TimeLimiter(超时限制):限制调用时间
5. Bulkhead(舱壁):限制并发,资源隔离

与 Sentinel 对比:
  Sentinel:国产,控制台丰富,规则动态推送,国内生态好
  Resilience4j:Spring Cloud 官方推荐,轻量,响应式友好,无额外组件依赖
  推荐:国内项目用 Sentinel,出海/开源项目用 Resilience4j

知识点 2:完整配置示例

xml 复制代码
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
    <version>2.2.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
yaml 复制代码
resilience4j:
  # 断路器
  circuitbreaker:
    instances:
      userService:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 10
        failure-rate-threshold: 50
        slow-call-duration-threshold: 1s
        slow-call-rate-threshold: 80
        wait-duration-in-open-state: 30s
        permitted-number-of-calls-in-half-open-state: 3
        record-exceptions:
          - java.io.IOException
          - java.net.SocketTimeoutException
          - com.example.exception.RemoteServiceException

  # 重试
  retry:
    instances:
      userService:
        max-attempts: 3                    # 最多重试3次(含第一次)
        wait-duration: 500ms              # 重试间隔
        retry-exceptions:
          - java.io.IOException
          - java.net.SocketTimeoutException
        ignore-exceptions:
          - com.example.exception.BusinessException  # 业务异常不重试

  # 限速器
  ratelimiter:
    instances:
      userService:
        limit-for-period: 100             # 每个周期内最多100次调用
        limit-refresh-period: 1s          # 周期1秒
        timeout-duration: 100ms          # 等待令牌的超时时间

  # 超时
  timelimiter:
    instances:
      userService:
        timeout-duration: 3s             # 3秒超时

  # 并发限制(舱壁)
  bulkhead:
    instances:
      userService:
        max-concurrent-calls: 20         # 最大并发20
        max-wait-duration: 100ms         # 等待进入的超时时间
java 复制代码
@Service
@RequiredArgsConstructor
@Slf4j
public class UserClientService {

    private final UserFeignClient userFeignClient;

    // 叠加多个注解(执行顺序:Bulkhead → TimeLimiter → CircuitBreaker → Retry)
    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    @RateLimiter(name = "userService", fallbackMethod = "rateLimitFallback")
    @Retry(name = "userService")
    @Bulkhead(name = "userService", fallbackMethod = "bulkheadFallback")
    public UserDTO getUser(Long userId) {
        return userFeignClient.getUserById(userId).getData();
    }

    // 断路器降级方法(参数列表与原方法相同,末尾加 Throwable)
    public UserDTO getUserFallback(Long userId, Throwable cause) {
        log.error("获取用户信息失败(断路器): userId={}, cause={}", userId, cause.getMessage());
        // 可以返回缓存数据、默认值,或抛出友好异常
        return new UserDTO(userId, "用户信息暂时不可用", null);
    }

    public UserDTO rateLimitFallback(Long userId, RequestNotPermitted cause) {
        throw new BusinessException(429, "请求过于频繁,请稍后再试");
    }

    public UserDTO bulkheadFallback(Long userId, BulkheadFullException cause) {
        throw new BusinessException(503, "服务繁忙,请稍后再试");
    }
}

19. 微服务可观测性(三大支柱)

知识点:Logging + Metrics + Tracing

text 复制代码
可观测性三大支柱:

1. Logs(日志):
   单次事件的详细记录
   通过 TraceId 将分散日志串联成请求链
   工具:ELK(Elasticsearch + Logback + Kibana)

2. Metrics(指标):
   随时间变化的数字(QPS、延迟、错误率、JVM内存)
   用于监控告警、容量规划
   工具:Prometheus + Grafana

3. Traces(链路):
   一次请求在多个服务间的完整调用链路
   定位慢接口、排查异常
   工具:SkyWalking / Zipkin / Jaeger

三者的配合使用:

text 复制代码
告警触发:Prometheus 检测到 order-service P99 > 2 秒
    ↓
查看指标:Grafana 面板,确认哪个接口慢
    ↓
查看链路:SkyWalking,用接口 URL 过滤,找到慢的 Span
    ↓
查看日志:通过 TraceId 在 Kibana 中搜索完整请求日志
    ↓
定位问题:某条 SQL 执行 1.8 秒 → 加索引

知识点:Micrometer Tracing 配置(Spring Boot 3.x)

java 复制代码
// 自动透传 TraceId 的 Feign 拦截器(Micrometer 已内置)
// 无需额外代码,自动将 TraceId 注入 Feign 请求 Header

// 手动为异步任务传递 TraceId
@Service
@RequiredArgsConstructor
public class OrderService {

    private final Tracer tracer;  // io.micrometer.tracing.Tracer

    @Async
    public void asyncProcessOrder(Long orderId) {
        // 创建子 Span(关联到当前 Trace)
        Span span = tracer.nextSpan()
            .name("async-process-order")
            .tag("orderId", String.valueOf(orderId))
            .start();

        try (Tracer.SpanInScope ws = tracer.withSpan(span)) {
            // 此 Span 内的日志会自动携带 TraceId
            log.info("异步处理订单: {}", orderId);
            doProcess(orderId);
        } catch (Exception e) {
            span.error(e);
            throw e;
        } finally {
            span.end();
        }
    }
}

20. 幂等性完整实现方案

知识点 1:为什么微服务必须考虑幂等性

text 复制代码
导致重复调用的场景:
  1. Feign 重试(临时网络问题,超时后重试)
  2. 消息队列重消费(Consumer 处理后 ACK 丢失)
  3. 用户重复点击(前端防抖未做好)
  4. 服务降级触发补偿(Saga 补偿可能重复执行)

幂等定义:f(f(x)) = f(x)
  同一请求执行一次和多次,结果完全相同
  不会创建重复数据,不会重复扣款

知识点 2:四种幂等实现方案

java 复制代码
// ===== 方案1:数据库唯一索引(最简单可靠)=====
// 适合:创建类操作(创建订单、注册用户)
@Entity
@Table(
    name = "t_order",
    uniqueConstraints = @UniqueConstraint(
        name = "uk_request_id",
        columnNames = "request_id"  // 客户端传来的幂等键
    )
)
public class Order {
    @Column(name = "request_id", unique = true)
    private String requestId;  // 客户端生成的 UUID,同一操作用同一个 UUID
}

@Service
public class OrderService {
    @Transactional
    public Order createOrder(CreateOrderRequest req) {
        try {
            return orderRepository.save(buildOrder(req));
        } catch (DataIntegrityViolationException e) {
            // 唯一键冲突 = 重复请求,返回已创建的订单
            return orderRepository.findByRequestId(req.getRequestId())
                .orElseThrow(() -> new RuntimeException("幂等查询失败"));
        }
    }
}

// ===== 方案2:Redis SETNX Token 机制(最通用)=====
// 适合:需要短时间内防重的场景
@Service
@RequiredArgsConstructor
public class IdempotentService {

    private final StringRedisTemplate redis;

    /**
     * 生成幂等 Token(前端在表单展示时调用,获取一次性 Token)
     */
    public String generateToken(String businessType, String userId) {
        String token = UUID.randomUUID().toString().replace("-", "");
        String key = "idempotent:token:" + businessType + ":" + userId;
        redis.opsForValue().set(key, token, 10, TimeUnit.MINUTES);
        return token;
    }

    /**
     * 验证并消费 Token(实际提交时调用,Token 只能用一次)
     * 返回 true 表示是合法的首次请求,false 表示重复请求
     */
    public boolean validateAndConsume(String businessType, String userId, String token) {
        String key = "idempotent:token:" + businessType + ":" + userId;
        // Lua 脚本保证原子性(取值 + 比较 + 删除,三步原子)
        String script = """
            local stored = redis.call('GET', KEYS[1])
            if stored == ARGV[1] then
                redis.call('DEL', KEYS[1])
                return 1
            else
                return 0
            end
            """;
        Long result = redis.execute(
            new DefaultRedisScript<>(script, Long.class),
            List.of(key), token);
        return Long.valueOf(1L).equals(result);
    }
}

// AOP 切面(配合 @Idempotent 注解自动处理)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    String business();
}

@Aspect
@Component
@RequiredArgsConstructor
public class IdempotentAspect {

    private final IdempotentService idempotentService;

    @Around("@annotation(idempotent)")
    public Object doIdempotent(ProceedingJoinPoint point, Idempotent idempotent)
            throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes)
            RequestContextHolder.getRequestAttributes()).getRequest();

        String token = request.getHeader("X-Idempotent-Token");
        String userId = UserContext.get().getId().toString();

        if (token != null &&
            !idempotentService.validateAndConsume(idempotent.business(), userId, token)) {
            throw new BusinessException(409, "请勿重复提交");
        }

        return point.proceed();
    }
}

// 使用
@PostMapping("/api/orders")
@Idempotent(business = "create-order")
public Result<Order> createOrder(@RequestBody CreateOrderRequest req) { ... }
相关推荐
ShineWinsu1 小时前
对于Redis:string类型的解析
java·c++·redis·分布式·缓存·面试·string
仍然.5 小时前
RabbitMQ---可靠性传输
网络·分布式·rabbitmq
灯澜忆梦7 小时前
【RabbitMQ #13】 | 延迟消息
分布式·rabbitmq·ruby
青山木8 小时前
秒杀系统设计(二):数据正确性——防超卖、分布式锁与一人一单
分布式·后端·mysql·中间件·架构
weixin_4241836117 小时前
开放—封闭原则实战(下):基于策略的流程编排与编译期多态
c++·分布式·系统架构
frjc1 天前
分布式事务:场景与选型决策表
分布式
GISMagic1 天前
5.《Designing Data-Intensive Applications》:系统设计与分布式能力提炼
分布式·ai coding
需要8261 天前
分布式 ID 生成:雪花算法、号段模式与时钟回拨
分布式·算法
灯澜忆梦1 天前
【RabbitMQ #11】 | 消费者可靠性
分布式·rabbitmq·ruby
仍然.1 天前
RabbitMQ---事务和消息分发
分布式·rabbitmq