在高并发场景下,接口限流是保障系统稳定性的重要手段。常见的限流算法有漏桶算法、令牌桶算法等,而单机模式的限流方案在分布式集群环境下往往失效。本文将介绍如何利用 redisson 结合 redis 实现分布式环境下的接口限流,确保集群中所有节点的流量控制保持一致。
分布式限流的核心挑战
在单机系统中,我们可以通过本地缓存(如 guava 的 ratelimiter)实现限流,但在分布式集群环境下,这种方案会遇到两个核心问题:
- 集群节点间的限流状态不共享,导致整体流量超过预期阈值
- 无法保证同一用户 / ip 的请求在不同节点上被统一限制
因此,分布式限流需要一个「中心化的状态存储」来记录流量数据,而 redis 凭借其高并发特性和分布式特性,成为了理想的选择。
基于 redisson 的分布式限流设计思路
核心原理是通过 redis 记录每个用户对接口的访问频率,利用分布式锁实现并发控制,具体设计如下:
唯一标识用户与接口 为了避免限制 a 用户时影响 b 用户,需要为每个用户 + 接口组合生成唯一的「限流键」。
一般为用户:使用
token + 接口路径
+用户的id基于 redis 的访问频率记录 每次请求到来时,通过 redisson 操作 redis 记录访问时间,并检查单位时间内的访问次数是否超过阈值。
aop 无侵入式拦截 通过自定义注解 + spring aop 拦截需要限流的接口,在请求到达时执行限流逻辑,不侵入业务代码。
自动过期的限流状态 为 redis 中的限流键设置过期时间,避免长期存储无效数据,同时确保超过限制时间后自动允许用户再次访问。
实现步骤
引入依赖
在 pom.xml
中添加 redisson 和 aop 依赖
<!-- redisson 分布式工具 --> <dependency> <groupid>org.redisson</groupid> <artifactid>redisson-spring-boot-starter</artifactid> <version>3.23.3</version> </dependency> <!-- spring aop --> <dependency> <groupid>org.springframework.boot</groupid> <artifactid>spring-boot-starter-aop</artifactid> </dependency>
定义限流注解
创建 @norepeatsubmit
注解,用于标记需要限流的接口,并支持自定义限流参数:
@target(elementtype.method) @retention(retentionpolicy.runtime) public @interface norepeatsubmit { /** * 设置请求锁定时间(秒) */ int locktime() default 5; }
实现限流切面
通过 aop 拦截 @norepeatsubmit
注解的方法,使用 redisson 操作 redis 实现限流逻辑:
aspect @component public class repeatsubmitaspect { private static final logger log = loggerfactory.getlogger(repeatsubmitaspect.class); @resource private redissonclient redissonclient; @pointcut("@annotation(com.example.demo.config.norepeatsubmit)") public void pointcut() { } @around("pointcut()") public object around(proceedingjoinpoint pjp) throws throwable { methodsignature signature = (methodsignature) pjp.getsignature(); method method = signature.getmethod(); norepeatsubmit annotation = method.getannotation(norepeatsubmit.class); servletrequestattributes attributes = (servletrequestattributes) requestcontextholder.getrequestattributes(); if (attributes == null) { throw new illegalargumentexception("无法获取请求信息"); } httpservletrequest request = attributes.getrequest(); string token = request.getheader("token"); string path = request.getservletpath(); if (token == null || token.isempty()) { throw new illegalargumentexception("缺少token请求头"); } // 使用token+path作为锁的key string key = "repeat_submit:" + token + ":" + path; rlock lock = redissonclient.getlock(key); // 尝试获取锁,等待0秒,自动释放时间由注解指定 boolean issuccess = false; issuccess = lock.trylock(0, annotation.locktime(), timeunit.seconds); if (issuccess) { log.info("获取锁成功: {}", key); // 执行目标方法 return pjp.proceed(); } else { log.info("重复请求,获取锁失败: {}", key); return result.fail("请勿重复提交请求"); } } }
测试
@restcontroller @requestmapping("/api/order") public class ordercontroller { @postmapping("/create") @norepeatsubmit(locktime = 10) // 设置5秒内不允许重复提交 public result createorder() { // 模拟订单创建过程 try { thread.sleep(2000); // 模拟业务处理耗时2秒 } catch (interruptedexception e) { e.printstacktrace(); } return result.success("订单创建成功"); } }
限制之后
这里还可以增加更多的逻辑,比如限制次数等等。
核心逻辑说明
- 用户唯一标识生成 通过
getuniqueuserkey
方法获取用户标识:已登录用户用token
,未登录用户用ip
,确保不同用户的限流互不干扰。 - 限流键设计 限流键格式为
rate_limit:用户标识:接口路径
,例如rate_limit:test_token:/api/order/submit
,精确控制「用户 + 接口」的访问频率。 - 分布式锁的作用 由于 redis 的
incr
操作虽然原子,但在高并发下可能出现「读取 - 判断 - 更新」的竞态条件,因此通过 redisson 分布式锁确保计数逻辑的原子性。 - 自动过期机制 每个限流键都设置了与时间窗口相同的过期时间,避免 redis 中存储大量无效数据,同时确保时间窗口结束后自动重置计数。 由于 redis 的
incr
操作虽然原子,但在高并发下可能出现「读取 - 判断 - 更新」的竞态条件,因此通过 redisson 分布式锁确保计数逻辑的原子性。 - 自动过期机制 每个限流键都设置了与时间窗口相同的过期时间,避免 redis 中存储大量无效数据,同时确保时间窗口结束后自动重置计数。
到此这篇关于基于redisson实现分布式系统下的接口限流的文章就介绍到这了,更多相关redisson接口限流内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论