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

RRateLimiter的使用

Redisson 的 RRateLimiter 是一个分布式限流器,用于在分布式系统中控制访问速率。

基本用法

导入jar

        <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>

配置类

package com.qfedu.ratelimit.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissonConfig {@Beanpublic RedissonClient getRedisson() {Config config = new Config();config.useSingleServer().setAddress("redis://127.0.0.1:6379").setRetryInterval(5000).setTimeout(10000).setConnectTimeout(10000);return Redisson.create(config);}
}

通过控制器测试

@RestController
public class TestController {@Resourceprivate RedissonClient redissonClient;@GetMapping("/test")public String test() {// 根据key获取限流器实例RRateLimiter rateLimiter = redissonClient.getRateLimiter("limit:1");// 初始化限流器,本例表示5秒内产生3个许可     rateLimiter.trySetRate(RateType.OVERALL, 3, 5, RateIntervalUnit.SECONDS);// 尝试获取许可,获取到返回true,否则返回falseboolean ret1 = rateLimiter.tryAcquire(1);System.out.println(ret1);boolean ret2 = rateLimiter.tryAcquire(1);System.out.println(ret2);boolean ret3 = rateLimiter.tryAcquire(1);System.out.println(ret3);// 第四次获取许可,返回false,表示不能访问boolean ret4 = rateLimiter.tryAcquire(1);System.out.println(ret4);return "success";}}

关于trySetRate的说明

其中trySetRaet()的第一个参数表示RateType有两个值:

  • RateType.OVERALL: 全局限流(所有实例共享)

  • RateType.PER_CLIENT: 按客户端限流(需要配置客户端标识)

第二个参数表示限流的最大次数

第三个参数表示限流的时间间隔

第四个参数表示限流的时间间隔的单位

通过查看源码,其内部执行了如下的lua脚本

redis.call('hsetnx', KEYS[1], 'rate', ARGV[1]);
redis.call('hsetnx', KEYS[1], 'interval', ARGV[2]);
return redis.call('hsetnx', KEYS[1], 'type', ARGV[3]);

其内部使用hsetnx,如果对应的key和field存在,则不会创建field

通过自定义注解和AOP进行封装

工具类

package com.qfedu.ratelimit.aspect;import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;public class IpAddressUtil {/*** 获取客户端真实IP地址*/public static String getClientIpAddress() {ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String[] headers = {"X-Forwarded-For","X-Real-IP","Proxy-Client-IP","WL-Proxy-Client-IP","HTTP_CLIENT_IP","HTTP_X_FORWARDED_FOR"};// 1. 检查各种代理头信息for (String header : headers) {String ip = request.getHeader(header);if (isValidIp(ip)) {return getFirstIp(ip);}}// 2. 直接获取远程地址String remoteAddr = request.getRemoteAddr();if (isValidIp(remoteAddr)) {return remoteAddr;}throw new RuntimeException("无法获取远程ip地址");}/*** 验证IP地址是否有效*/private static boolean isValidIp(String ip) {return ip != null &&!ip.isEmpty() &&!"unknown".equalsIgnoreCase(ip) &&!"0:0:0:0:0:0:0:1".equals(ip) &&!"127.0.0.1".equals(ip);}/*** 处理多个IP的情况(如X-Forwarded-For: client, proxy1, proxy2)*/private static String getFirstIp(String ip) {if (ip.contains(",")) {String[] ips = ip.split(",");for (String singleIp : ips) {String trimmedIp = singleIp.trim();if (isValidIp(trimmedIp)) {return trimmedIp;}}}return ip.trim();}
}
package com.qfedu.ratelimit.aspect;import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;public class TokenUtils {/*** 通过token获取用户id** @return*/public static Integer getUidFromToken() {// 动态获取HttpServletRequest对象ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String token = request.getHeader("Authorization");
//        if (token == null || token.isEmpty()) {
//            throw new RuntimeException("请重新登录");
//        }
//        Claims claims = JwtUtils.parseJWT(token);
//        Object uid = claims.get("uid");
//        return Integer.valueOf(uid.toString());// 本例为了方便测试,写死返回的用户idreturn 123;}
}

枚举

package com.qfedu.ratelimit.aspect;public enum RateLimitTypeEnum {TYPE_IP(1, "根据IP限流"),TYPE_USER(2, "根据用户限流");private int type;private String desc;RateLimitTypeEnum(int type, String desc) {this.type = type;this.desc = desc;}
}

自定义注解

package com.qfedu.ratelimit.aspect;import org.redisson.api.RateIntervalUnit;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {RateLimitTypeEnum type() default RateLimitTypeEnum.TYPE_USER;// String key() default "";long rate() default 10;long interval() default 60;RateIntervalUnit unit() default RateIntervalUnit.SECONDS;String message() default "请求过于频繁,请稍后再试";
}

切面类

package com.qfedu.ratelimit.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;@Aspect
@Component
public class RateLimitAspect {@Autowiredprivate RedissonClient redissonClient;@Around("@annotation(rateLimit)")public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable {String suffix = null;if (rateLimit.type() == RateLimitTypeEnum.TYPE_IP) {suffix = IpAddressUtil.getClientIpAddress();} else if (rateLimit.type() == RateLimitTypeEnum.TYPE_USER) {suffix = TokenUtils.getUidFromToken().toString();}String key = "rate:limit:" + suffix;RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);rateLimiter.trySetRate(RateType.OVERALL,rateLimit.rate(),rateLimit.interval(),rateLimit.unit());if (!rateLimiter.tryAcquire()) {throw new RuntimeException(rateLimit.message());}return joinPoint.proceed();}}

测试控制层方法

    @RateLimit(type = RateLimitTypeEnum.TYPE_USER, rate = 10, interval = 60)@GetMapping("/test2")public String test2() {return "success";}

根据配置,执行到第11次时,报错

RRateLimiter使用的限流算法

RRateLimiter 使用的是令牌桶算法(Token Bucket Algorithm)

令牌桶算法的工作机制:

  1. 令牌生成:系统以固定速率向桶中添加令牌

  2. 令牌消耗:请求需要获取令牌才能被处理

  3. 桶容量:桶有最大容量,防止令牌无限累积

  4. 限流逻辑:当桶中有足够令牌时请求通过,否则被限流

令牌桶 vs 漏桶算法

特性令牌桶算法漏桶算法
突发流量允许一定程度的突发严格限制,平滑输出
灵活性更灵活,可应对突发更严格,输出恒定
实现复杂度相对简单相对复杂
适用场景API限流、流量控制网络流量整形

漏桶算法(Leaky Bucket Algorithm)核心概念:
漏桶算法是一种流量整形算法,它以恒定的速率处理请求,无论输入流量多么不规则。

工作机制:
请求到达,请求以任意速率进入桶中,相当于水流入,水量增加
恒定处理,以固定速率从桶中取出请求处理,相当于水流出,水以恒定速率从底部漏出
溢出拒绝:当桶满时,新请求被丢弃或拒绝,类似水满则溢

http://www.dtcms.com/a/414964.html

相关文章:

  • 做网站html整合资源加强全市网站建设
  • 使用opencv来识别信用卡的号码
  • 【rabbitmq 高级特性】全面详解RabbitMQ重试机制
  • 在飞腾D2000/8平台下ubuntu内核添加WX1860和WX1820的驱动
  • docker相关进程的作用
  • 建设的网站如何让用户注册宁波发布最新通报
  • [LVGL] 中国象棋
  • 通过XShell使用Git三板斧
  • 【Git】远程操作 + 给命令配置别名 + 标签管理
  • 教学网站建设计划免费ppt下载网站
  • 给客户做网站需要提供看电视剧免费的网站
  • 分治法找到数组中出现次数超过一半的元素
  • C语言入门知识点(12.回调函数与qsort函数的模拟与实现)
  • 徐州网站客户如何做网站的seo优化
  • 岳阳网站建设哪家好海北高端网站建设公司
  • MyBatis中如何实现数据封装
  • Http 常见的状态码
  • [论文阅读] 人工智能 | 突破AI大模型算力瓶颈:下一代计算范式的三大演进路径探索
  • 杭州高端设计网站建设dede旅游网站
  • 网站建设绪论江苏天宇建设集团官方网站
  • 开发知识点-Python-virtualenv
  • 网站如何做线下的市场推广网站开发工程师证
  • 珠海市网站建设的公司软件开发与设计
  • 好紧张,第一次接吻是一种什么感觉
  • 学做网站看什么书网站定制分享
  • 基于机器学习的心血管疾病智能预测系统
  • 上海专业的网站公多语网站wordpress子站点
  • vscode和cursor中引入prettierrc进行格式化
  • 常见算法实现系列01 - 排序算法
  • 做兼职去什么网站wordpress 预加载动画