Sentinel 官方限流与幂等融合企业级完整方案

一、前言

前面两篇文章我们分别实现了Redis+AOP 幂等防重自定义 Redis 限流 + 幂等双层防护

但在微服务、分布式生产环境中,自研 Redis 限流存在明显短板:

  1. 无可视化控制台,规则改代码必须重启服务;
  2. 缺少熔断、降级、热点参数限流、系统自适应保护;
  3. 无法全链路流量观测、实时监控、动态流控规则。

阿里 Sentinel 是微服务生态标准流量防护组件,内置本地限流、热点限流、熔断降级、线程隔离、系统保护,支持 Nacos 动态推送规则。

本文实现Sentinel 官方限流框架 + Redis Token 幂等深度融合,形成企业标准三层防护链路:

Sentinel 限流熔断 → AOP 幂等校验 → 业务逻辑

  • Sentinel:保护服务,拦截洪峰、恶意刷、下游故障雪崩
  • Redis 幂等 Token:保护数据,拦截重复提交、网关 / 消息重试

二、融合架构设计

2.1 执行顺序(绝对不能颠倒)

  1. 请求进入接口,先执行@SentinelResource拦截,触发 Sentinel 本地限流、热点限流、熔断降级;流量超限直接走 blockHandler 快速返回,不进入后续逻辑;
  2. 流量校验通过后,进入自定义@IdempotentAOP 切面,校验一次性幂等 Token;Token 不存在直接拦截;
  3. 两层校验全部放行,执行业务代码;
  4. 业务异常场景:主动清理 Redis 幂等 Token,允许用户正常重试。

2.2 分层防护体系

层级 能力 技术实现 解决问题
流量防护层 限流、熔断、降级、热点参数控流 Sentinel 官方框架 高并发打垮服务、下游雪崩、单用户疯狂刷接口
请求防重层 一次性令牌防重复提交 Redis+Lua 原子校验 + AOP 重复下单、重复扣款、MQ 重复消费
数据兜底层 数据库唯一索引 / 乐观锁 MySQL 约束 极端并发下最终数据一致性兜底

2.3 技术依赖版本

<!-- SpringBoot Redis 幂等依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

<!-- Sentinel 核心依赖 SpringCloud Alibaba -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
    <version>2022.0.0.0</version>
</dependency>
<!-- Sentinel控制台传输依赖 -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
</dependency>

三、前置复用基础代码(幂等模块,与前文通用)

3.1 幂等注解 @Idempotent

import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Idempotent {
    // 重复提交提示
    String message() default "请勿重复提交操作";
    // Token过期时间 单位秒
    long expire() default 600;
}

3.2 Redis 幂等工具类(Lua 原子校验,解决查询删除非原子并发漏洞)

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;

@Component
public class IdempotentUtil {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    private static final String TOKEN_KEY_PREFIX = "idempotent:token:";

    // Lua脚本:get+del原子执行,避免并发穿透
    private static final String CHECK_DEL_SCRIPT = """
            local tokenKey = KEYS[1]
            local exist = redis.call('GET', tokenKey)
            if exist then
                redis.call('DEL', tokenKey)
                return 1
            end
            return 0
            """;

    /** 生成幂等Token */
    public String generateToken(long expireSecond) {
        String token = java.util.UUID.randomUUID().toString().replace("-", "");
        String key = TOKEN_KEY_PREFIX + token;
        redisTemplate.opsForValue().set(key, System.currentTimeMillis(), expireSecond, java.util.concurrent.TimeUnit.SECONDS);
        return token;
    }

    /** 校验Token,存在则删除返回true,不存在返回false */
    public boolean validToken(String token) {
        if (!StringUtils.hasText(token)) return false;
        String key = TOKEN_KEY_PREFIX + token;
        DefaultRedisScript<Long> script = new DefaultRedisScript<>(CHECK_DEL_SCRIPT, Long.class);
        Long result = redisTemplate.execute(script, Collections.singletonList(key));
        return Long.valueOf(1).equals(result);
    }

    /** 业务异常时主动清除Token,支持重试 */
    public void clearToken(String token) {
        if (StringUtils.hasText(token)) {
            redisTemplate.delete(TOKEN_KEY_PREFIX + token);
        }
    }
}

3.3 幂等 AOP 切面(Sentinel 放行后才执行)

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

@Aspect
@Component
public class IdempotentAspect {
    @Resource
    private IdempotentUtil idempotentUtil;
    private static final String TOKEN_HEADER = "idempotent-token";

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, Idempotent idempotent) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String token = request.getHeader(TOKEN_HEADER);
        boolean pass = idempotentUtil.validToken(token);
        if (!pass) {
            return Result.fail(idempotent.message());
        }
        try {
            // 执行业务
            return point.proceed();
        } catch (Exception e) {
            // 业务异常,清理token,允许用户重新提交
            idempotentUtil.clearToken(token);
            throw e;
        }
    }
}

3.4 统一返回体 Result

public class Result<T> {
    private Integer code;
    private String msg;
    private T data;

    public static <T> Result<T> fail(String msg) {
        Result<T> r = new Result<>();
        r.setCode(500);
        r.setMsg(msg);
        return r;
    }
    public static <T> Result<T> success(T data) {
        Result<T> r = new Result<>();
        r.setCode(200);
        r.setMsg("操作成功");
        r.setData(data);
        return r;
    }
    // getter/setter省略
}

四、Sentinel 限流融合核心实现

4.1 Sentinel 资源注解规范

使用@SentinelResource标记接口资源,配置:

  • value:资源名(控制台配置限流规则的唯一标识)
  • blockHandler:限流 / 熔断兜底方法
  • fallback:业务异常降级方法

4.2 Controller 完整示例(Sentinel + 幂等双注解共存)

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;

@RestController
@RequestMapping("/api/order")
public class OrderController {
    @Resource
    private IdempotentUtil idempotentUtil;

    // 获取幂等Token接口(无需限流,轻量接口)
    @GetMapping("/getToken")
    public Result<String> getToken() {
        String token = idempotentUtil.generateToken(600);
        return Result.success(token);
    }

    /**
     * 下单核心接口:Sentinel限流 + 幂等双重防护
     * 1. @SentinelResource:Sentinel拦截QPS、热点参数、熔断
     * 2. @Idempotent:拦截重复提交
     */
    @PostMapping("/submit")
    @SentinelResource(value = "submitOrder", blockHandler = "blockHandler", fallback = "fallback")
    @Idempotent(message = "订单请勿重复提交", expire = 600)
    public Result<String> submitOrder() {
        // 模拟下单业务逻辑
        return Result.success("订单创建成功");
    }

    // Sentinel限流/熔断兜底方法(参数必须和原方法一致,最后追加BlockException)
    public Result<String> blockHandler(BlockException ex) {
        return Result.fail("当前访问人数过多,请稍后重试");
    }

    // 业务异常降级兜底
    public Result<String> fallback() {
        return Result.fail("服务暂时繁忙,请稍后再提交");
    }
}

五、Sentinel 控制台配置规则(生产标准配置)

5.1 流控规则(普通 QPS 限流)

资源名:submitOrder

阈值类型:QPS

单机阈值:5(每秒最多 5 次请求)

流控效果:快速失败

5.2 热点参数限流(精细化单用户防刷,推荐)

如果接口入参携带userId,配置热点规则:

参数索引:0(userId 参数下标)

单机阈值:3(单个用户每秒最多 3 次下单)

限流时长:2 秒

解决同一用户恶意刷下单接口问题。

5.3 熔断降级规则(防止下游服务雪崩)

统计时长:10 秒

熔断触发阈值:异常比例 50%

熔断时长:3 秒

下游支付、库存服务大量报错时,自动切断调用,保护上游。

六、完整调用链路演示

  1. 前端页面加载,调用 /api/order/getToken 获取一次性 Token;

  2. 提交下单请求,Header 携带 idempotent-token:xxxx

  3. 第一层拦截:Sentinel

    @SentinelResource
    
    • QPS 超限 / 热点参数超限:执行 blockHandler 返回繁忙,流程终止;
    • 流量正常,放行进入下一层;
  4. 第二层拦截:

    @Idempotent
    

    AOP 切面

    • Token 不存在:返回重复提交,流程终止;
    • Token 校验通过并删除,执行业务;
  5. 业务正常执行:返回成功;

  6. 业务抛出异常:自动清除 Redis Token,用户可重新提交。

七、融合方案核心优势对比自研 Redis 限流

7.1 Sentinel 优势

  1. 动态规则:控制台修改限流阈值、熔断时长,无需重启服务;
  2. 多维度防护:QPS 限流、热点参数、线程隔离、系统自适应保护;
  3. 实时监控:QPS、阻塞数、异常率可视化面板;
  4. 集群流控:结合 Nacos 实现分布式全局限流;
  5. 完善降级策略:异常降级、超时降级、返回静态兜底数据。

7.2 幂等 Token 优势

  1. 解决 Sentinel 无法处理的重复提交、网络重试、MQ 重复消费
  2. Redis 分布式全局防重,集群多实例统一生效;
  3. Lua 原子操作,高并发无穿透漏洞。

7.3 二者互补,无功能重叠

  • Sentinel 管流量多少,防服务崩溃;
  • 幂等 Token 管请求是否重复,防脏数据;

八、生产环境进阶优化方案

8.1 网关层前置 Sentinel 防护(推荐分层)

Spring Cloud Gateway 整合 Sentinel,在网关提前拦截超限流量,避免无效请求转发至业务服务,减少资源损耗。

8.2 幂等 Token 过期时间差异化

  • 支付、下单接口:10 分钟过期
  • 表单普通提交:5 分钟过期
  • 秒杀短时效操作:1 分钟过期

8.3 Sentinel 与分布式锁组合高并发交易

秒杀、库存扣减场景:

Sentinel 热点限流 → 幂等 Token 防重复下单 → Redisson 分布式锁防超卖 → 数据库最终兜底。

8.4 Redis 故障降级兼容

增加 Redis 健康检测,Redis 不可用时临时关闭幂等校验,仅保留 Sentinel 流量防护,保证服务不宕机。

8.5 统一全局异常处理整合 Sentinel

全局异常捕获BlockException、业务异常,统一返回标准化 JSON,避免页面出现原生异常堆栈。

九、高频生产问题答疑

Q1:为什么不用 Sentinel 做重复请求防重?

A:Sentinel 仅做流量频率控制,只限制单位时间访问次数,无法识别完全相同的重复提交请求。用户一秒点击 3 次,Sentinel 允许 3 次流量,但业务上只允许 1 次下单,必须依赖 Token 幂等。

Q2:Sentinel 本地限流是否存在集群流量不均?

A:单机 Sentinel 仅本地计数,多实例集群总流量会放大;高并发分布式场景可开启 Sentinel 集群流控,结合 Nacos 统一计数。

Q3:幂等 Token 删除失败会不会导致永久无法提交?

A:设置固定过期时间兜底,即使代码异常未删除,超时后 Token 自动失效,不会永久拦截用户。

Q4:Sentinel blockHandler 与 AOP 执行顺序谁先谁后?

A:@SentinelResource基于拦截器实现,优先级高于 Spring AOP;必然先限流校验,再进入幂等切面,符合防护逻辑。

十、总结

  1. Sentinel 限流 + Redis Token 幂等是微服务项目标准企业级流量 + 数据双重防护方案;
  2. 执行顺序固定:Sentinel 流量拦截在前,幂等防重在后;
  3. Sentinel 解决系统稳定性,幂等解决数据一致性,二者缺一不可;
  4. 代码完全兼容前两篇文章的幂等模块,无缝接入现有项目,控制台可视化运维,适配生产集群、秒杀、支付、表单提交全场景。
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐