第六篇:可靠性篇 — 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) { ... }
相关推荐
rustfs1 小时前
MinIO 国产平替,RustFS 发布 Beta 版本啦
分布式·docker·云原生·rust·开源
Mr_sst3 小时前
文件上传并发控制:为什么选Redisson可过期信号量?(避坑指南)
网络·数据库·redis·分布式·安全架构
深念Y3 小时前
当加密遇见分布式:Web3、去中心化与元宇宙的底层逻辑
分布式·web3·去中心化·区块链·元宇宙·加密·价值
运维老司机3 小时前
Kafka 单节点部署(Docker Compose + 数据持久化)
分布式·docker·kafka
byoass3 小时前
企业云盘全文检索实战:Elasticsearch集成与分布式搜索
网络·分布式·安全·elasticsearch·云计算·全文检索
JAVA面经实录9174 小时前
如何选择适合项目的「限流 / 熔断 / 降级」方案
java·spring·kafka·sentinel·guava
Volunteer Technology5 小时前
Elasticsearch分布式原理
大数据·分布式·elasticsearch
Java开发的小李12 小时前
SpringBoot + Redis 实现分布式 Session 共享(解决多实例登录状态丢失问题)
spring boot·redis·分布式
tsyjjOvO14 小时前
分布式事务 Seata 与链路追踪 SkyWalking 全解析
分布式·skywalking