当前位置: 首页 > news >正文

Redisson 分布式限流 + 分布式幂等完整实现

以下代码均基于 Redisson 原生 API,底层全部通过 Lua 脚本保证原子性,可直接接入 Spring Boot 生产项目。

一、分布式限流实现

提供两种生产最常用的限流方案,分别对应QPS 速率控制并发数控制两类场景。

1. 令牌桶限流(RRateLimiter,接口 QPS 限流首选)

基于令牌桶算法实现,原子化完成令牌生成与消耗,支持平滑限流、应对突发流量,是接口级限流的最优方案。

适用场景

接口 QPS 限制、用户级调用频次控制、第三方接口调用限流。

工程化封装(注解 + AOP)
第一步:自定义限流注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ApiRateLimiter { // 限流业务key String key(); // 限流模式:OVERALL=全局总限流;PER_CLIENT=每个客户端独立限流 RateType mode() default RateType.OVERALL; // 单位时间内生成的令牌总数 long rate() default 10; // 时间单位 RateIntervalUnit timeUnit() default RateIntervalUnit.SECONDS; // 单次请求消耗令牌数 int permits() default 1; // 限流提示语 String msg() default "系统繁忙,请稍后重试"; }
第二步:AOP 切面实现
@Aspect @Component public class RateLimiterAspect { @Resource private RedissonClient redissonClient; @Around("@annotation(limiter)") public Object around(ProceedingJoinPoint joinPoint, ApiRateLimiter limiter) throws Throwable { // 组装限流key,按用户限流可自行拼接userId String limitKey = "limit:" + limiter.key(); RRateLimiter rateLimiter = redissonClient.getRateLimiter(limitKey); // 幂等初始化限流器,重复调用不覆盖配置 rateLimiter.trySetRate(limiter.mode(), limiter.rate(), 1, limiter.timeUnit()); // 原子获取令牌,成功则执行业务 boolean allow = rateLimiter.tryAcquire(limiter.permits()); if (!allow) { throw new BusinessException(limiter.msg()); } return joinPoint.proceed(); } }
业务使用示例
// 示例1:全局接口限流,每秒最多20次下单请求 @ApiRateLimiter(key = "order:create", rate = 20, msg = "下单人数过多,请稍后重试") public String createOrder(OrderDTO dto) { // 下单业务逻辑 return "ORD" + System.currentTimeMillis(); } // 示例2:按用户限流,每个用户每分钟最多调用5次查询接口 // 注:按用户限流需将userId拼入key,可在AOP中从上下文获取userId动态拼接 @ApiRateLimiter(key = "user:query:order", rate = 5, timeUnit = RateIntervalUnit.MINUTES) public List<OrderVO> queryOrderList(Long userId) { // 查询业务逻辑 return Collections.emptyList(); }
关键说明
  • RateType.OVERALL:集群所有节点共享总速率,适合全局接口限流。
  • 按用户 / 租户限流:将用户 ID 拼入限流 key,每个用户对应独立限流器,实现隔离。
  • 令牌桶容量默认等于速率值,支持突发流量一次性消耗存量令牌。

2. 并发数限流(RSemaphore 信号量)

基于分布式信号量,控制同时持有许可的线程总数,即同一时间最多有多少个线程执行指定业务。

适用场景

限制并发执行的任务数(如导出、批量处理)、保护下游资源(数据库、第三方接口)。

工具类封装
@Component public class ConcurrentLimitUtil { @Resource private RedissonClient redissonClient; /** * 尝试获取并发许可 * @param resourceKey 资源标识 * @param maxConcurrent 最大并发数 * @return true=获取成功,false=并发已满 */ public boolean tryAcquire(String resourceKey, int maxConcurrent) { RSemaphore semaphore = redissonClient.getSemaphore("concurrent:" + resourceKey); semaphore.trySetPermits(maxConcurrent); return semaphore.tryAcquire(); } /** * 释放并发许可,必须在finally中调用 */ public void release(String resourceKey) { redissonClient.getSemaphore("concurrent:" + resourceKey).release(); } }
业务使用示例
public void exportOrderData() { String resourceKey = "export:order"; // 限制同时最多3个导出任务 if (!concurrentLimitUtil.tryAcquire(resourceKey, 3)) { throw new BusinessException("当前导出任务过多,请稍后再试"); } try { // 执行导出业务 doExportExcel(); } finally { // 必须释放许可,防止泄露 concurrentLimitUtil.release(resourceKey); } }

二、分布式幂等实现

提供两种主流幂等方案,分别对应前端表单防重后端业务防重两类场景。

1. 令牌式幂等(表单 / 接口防重复提交)

前端进入页面前申请唯一令牌,提交时携带令牌,服务端原子校验并删除令牌,校验通过才执行业务。

适用场景

表单重复提交、按钮重复点击、前端接口防重。

工具类封装
@Component public class IdempotentTokenService { @Resource private RedissonClient redissonClient; private static final String TOKEN_PREFIX = "idempotent:token:"; private static final long TOKEN_EXPIRE_MINUTES = 5; /** * 生成幂等令牌 * @param bizId 业务标识(如用户ID),隔离不同主体 * @return 令牌字符串 */ public String generateToken(String bizId) { String token = UUID.randomUUID().toString().replace("-", ""); RBucket<String> bucket = redissonClient.getBucket(TOKEN_PREFIX + bizId); bucket.set(token, TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES); return token; } /** * 原子校验并删除令牌 * @return true=校验通过,false=令牌无效/已使用 */ public boolean checkAndRemove(String bizId, String token) { String key = TOKEN_PREFIX + bizId; Long result = redissonClient.getScript().eval( RScript.Mode.READ_WRITE, "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('DEL', KEYS[1]) else return 0 end", RScript.ReturnType.INTEGER, Collections.singletonList(key), token ); return result != null && result == 1; } }
业务使用流程
// 1. 前端进入页面时,获取令牌 @GetMapping("/token/get") public Result<String> getToken(@RequestParam Long userId) { return Result.success(idempotentTokenService.generateToken(userId.toString())); } // 2. 提交接口校验令牌 @PostMapping("/order/submit") public Result<String> submitOrder( @RequestHeader("idempotent-token") String token, @RequestBody OrderDTO dto ) { boolean valid = idempotentTokenService.checkAndRemove(dto.getUserId().toString(), token); if (!valid) { return Result.fail("请勿重复提交"); } // 执行业务 String orderNo = orderService.createOrder(dto); return Result.success(orderNo); }

2. 业务唯一键幂等(后端回调 / 消息防重)

以业务唯一标识作为幂等键,原子加锁并保留有效期,有效期内相同业务号直接拒绝重复处理。

适用场景

支付回调、MQ 消息重复消费、接口重试防重、重复下单控制。

工具类封装
@Component public class BizIdempotentService { @Resource private RedissonClient redissonClient; private static final String KEY_PREFIX = "idempotent:biz:"; private static final long DEFAULT_EXPIRE_MINUTES = 30; /** * 尝试获取幂等锁 * @param bizType 业务类型(如PAY_CALLBACK、ORDER_CREATE) * @param bizNo 业务唯一单号 * @param expireMinutes 幂等有效期 * @return true=首次请求,false=重复请求 */ public boolean tryIdempotent(String bizType, String bizNo, long expireMinutes) { String lockKey = KEY_PREFIX + bizType + ":" + bizNo; RLock lock = redissonClient.getLock(lockKey); try { // 等待时间为0,立即返回结果;持有时间为幂等有效期 return lock.tryLock(0, expireMinutes, TimeUnit.MINUTES); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } } public boolean tryIdempotent(String bizType, String bizNo) { return tryIdempotent(bizType, bizNo, DEFAULT_EXPIRE_MINUTES); } /** * 手动释放幂等锁(业务处理失败时调用,允许后续重试) */ public void releaseIdempotent(String bizType, String bizNo) { RLock lock = redissonClient.getLock(KEY_PREFIX + bizType + ":" + bizNo); if (lock.isHeldByCurrentThread()) { lock.unlock(); } } }
业务使用示例(支付回调)
public void handlePayCallback(PayCallbackDTO callback) { // 支付流水号作为唯一键,幂等有效期30分钟 boolean isFirst = bizIdempotentService.tryIdempotent("PAY_CALLBACK", callback.getPayNo(), 30); if (!isFirst) { log.info("重复支付回调,直接忽略,流水号:{}", callback.getPayNo()); return; } try { // 执行业务:更新订单状态、记账、发货等 updateOrderPayStatus(callback.getOrderNo()); doFinancialAccounting(callback); } catch (Exception e) { // 业务处理失败,释放幂等锁,允许后续重试 bizIdempotentService.releaseIdempotent("PAY_CALLBACK", callback.getPayNo()); throw e; } }

生产避坑要点

  1. 幂等与分布式锁的区别:分布式锁执行完立即释放,控制并发;幂等锁执行后保留有效期,控制重复请求,二者不可混用。
  2. 令牌校验必须原子:禁止先 GET 判断再 DEL 删除,并发下会出现重复通过,必须用 Lua 脚本原子执行。
  3. 信号量必须释放:并发限流的许可必须在 finally 中释放,否则许可泄露会导致后续请求永远无法获取。
  4. 幂等键唯一性:业务幂等的唯一单号必须全局唯一,建议前缀 + 业务类型 + 业务号组合,避免不同业务冲突。
http://www.jsqmd.com/news/1154080/

相关文章:

  • Windows Server 2015 AD 域控 新功能新变化
  • 实测才敢推!2026年最值得拥有的专业AI论文工具
  • 如何高效使用网盘直链下载助手:免费提升下载体验的实用指南
  • 抖音下载器V2.0:批量下载抖音视频、图集、合集、音乐的终极解决方案
  • 六自由度隔振平台:精密场景下的全维度振动控制解决方案
  • Windows热键冲突终极排查指南:Hotkey Detective完整使用教程
  • 写好 README 和注释:让你的爬虫代码不再是一次性的
  • 机械工程论文降AI工具免费推荐:2026年机械工程毕业论文AIGC超标4.8元亲测完整方案
  • MCP3551与PIC18LF2525高精度ADC系统设计与优化
  • 江西省吉安市安福县钱山乡四层别墅电梯落地:老乡口碑转介绍,山顶观景房定制全铝合金观光家用电梯
  • STM32H7 串口USART中断通信例程
  • 抖音批量下载终极指南:免费开源工具助你高效管理内容
  • 免费查AI率工具推荐:中英文AI率一键检测
  • 即触 AI PPT“保留原文”核心能力深度评测
  • 从零开始学大模型:企业级知识图谱治理五大关卡,小白也能收藏学习!
  • Kafka消费者原理与实践
  • 字节跳动社招数据:AI/大模型岗位占比34.6%!收藏这份岗位分类指南,小白也能进大厂学大模型!
  • 一键搞定完整网页截图:Chrome扩展让你轻松保存任何长页面内容
  • WorkshopDL:跨平台游戏玩家的创意工坊解决方案
  • 【电脑本地运行】 OpenClaw,Windows、Mac 两套安装渠道实操(含安装包)
  • ​​​​​​​如何从零开始搭建一个高稳定的微信机器人?
  • 盘锦新车贴车衣前,车漆检查别省
  • [实战指南] 质量管理是什么?2026年制造业数字化质量控制与图纸处理全流程解析
  • 抖音批量下载终极指南:如何免费获取创作者完整作品库
  • 【大数据毕业设计】基于机器学习的京东教辅图书销量预测系统的设计与实现 基于多维数据统计的京东教辅市场分析系统(源码+文档+远程调试,全bao定制等)
  • Python版本怎么选?3.12还是3.13,我整理了各版本发布时间和选型建议
  • Windows重装报错这样试试
  • 2026最新:Python/Java快速接入微信API实现自动化
  • 数字资产授权合规治理:品牌内容资产的安全护城河
  • 深度解析:如何高效构建抖音批量下载自动化工作流