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

Redisson实现限流器详解:从原理到实践

什么是限流器?

限流器(Rate Limiter)是一种控制请求频率的机制,用于保护系统免受过多请求的冲击。想象一下,你开了一家餐厅,如果同时涌入1000个客人,厨房肯定忙不过来,这时候就需要"限流"——控制进入餐厅的人数。

限流的常见场景
// 场景1:API接口限流
@RestController
public class UserController {@GetMapping("/api/user/{id}")public User getUser(@PathVariable Long id) {// 每个用户每分钟最多请求100次// 如果超过限制,返回429状态码}
}// 场景2:短信发送限流
public class SmsService {public void sendSms(String phone, String content) {// 每个手机号每分钟最多发送3条短信// 防止短信轰炸}
}// 场景3:登录限流
public class LoginService {public boolean login(String username, String password) {// 每个IP每分钟最多尝试5次登录// 防止暴力破解}
}

限流算法介绍

1. 固定窗口算法
算法原理

固定窗口算法将时间划分为固定大小的窗口(如1分钟),在每个窗口内统计请求次数。当请求次数超过限制时,拒绝后续请求。

public class FixedWindowRateLimiter {// 限制次数:每个窗口最多允许的请求数private final int limit;// 窗口大小:时间窗口的毫秒数private final long windowSize;// 存储每个key对应的窗口信息private final Map<String, Window> windows = new ConcurrentHashMap<>();public FixedWindowRateLimiter(int limit, long windowSize) {this.limit = limit;this.windowSize = windowSize;}public boolean tryAcquire(String key) {long currentTime = System.currentTimeMillis();// 计算当前时间所在的窗口开始时间// 例如:当前时间1234567890,窗口大小60000ms// 窗口开始时间 = 1234567890 - (1234567890 % 60000) = 1234560000long windowStart = currentTime - (currentTime % windowSize);// 获取或创建窗口对象Window window = windows.computeIfAbsent(key, k -> new Window(windowStart));// 检查窗口是否过期if (currentTime >= window.startTime + windowSize) {// 窗口过期,重置窗口window.reset(windowStart);}// 检查是否超过限制if (window.count < limit) {window.count++;return true;}return false;}// 窗口内部类,存储窗口的统计信息private static class Window {long startTime;  // 窗口开始时间int count;      // 当前窗口内的请求计数Window(long startTime) {this.startTime = startTime;this.count = 0;}// 重置窗口void reset(long newStartTime) {this.startTime = newStartTime;this.count = 0;}}
}
固定窗口算法的特点

优点:

  1. 实现简单:逻辑清晰,容易理解和实现
  1. 内存占用少:只需要存储窗口开始时间和计数
  1. 性能好:计算量小,响应速度快

缺点:

  1. 窗口边界效应:在窗口切换时可能出现流量突增
  1. 限流不均匀:无法平滑处理请求分布
2. 滑动窗口算法
算法原理

滑动窗口算法通过维护一个请求时间队列,动态计算当前时间窗口内的请求数量。窗口是"滑动"的,能够更精确地控制请求频率。

public class SlidingWindowRateLimiter {// 限制次数:时间窗口内最多允许的请求数private final int limit;// 窗口大小:时间窗口的毫秒数private final long windowSize;// 存储每个key的请求时间队列private final Map<String, Queue<Long>> requests = new ConcurrentHashMap<>();public SlidingWindowRateLimiter(int limit, long windowSize) {this.limit = limit;this.windowSize = windowSize;}public boolean tryAcquire(String key) {long currentTime = System.currentTimeMillis();// 获取或创建请求时间队列Queue<Long> requestTimes = requests.computeIfAbsent(key, k -> new LinkedList<>());// 清理过期的请求记录// 移除窗口外的请求时间while (!requestTimes.isEmpty() && currentTime - requestTimes.peek() > windowSize) {requestTimes.poll();}// 检查当前窗口内的请求数量是否超过限制if (requestTimes.size() < limit) {// 添加当前请求时间requestTimes.offer(currentTime);return true;}return false;}
}
滑动窗口算法的特点

优点:

  1. 限流均匀:没有窗口边界效应,限流更平滑
  1. 精确控制:能够精确控制任意时间窗口内的请求数量
  1. 实时性好:能够实时反映当前的请求状态

缺点:

  1. 内存占用大:需要存储所有请求时间
  1. 计算复杂度高:每次请求都需要清理过期数据
  1. 性能相对较低:相比固定窗口,计算开销更大
3. 令牌桶算法
算法原理

令牌桶算法维护一个固定容量的令牌桶,以恒定速率向桶中放入令牌。请求需要获取令牌才能被处理,如果桶中没有令牌,则请求被拒绝。

public class TokenBucketRateLimiter {// 桶容量:桶中最多能存放的令牌数private final int capacity;// 令牌填充速率:每秒填充的令牌数private final double refillRate;// 存储每个key对应的令牌桶private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();public TokenBucketRateLimiter(int capacity, double refillRate) {this.capacity = capacity;this.refillRate = refillRate;}public boolean tryAcquire(String key) {long currentTime = System.currentTimeMillis();// 获取或创建令牌桶Bucket bucket = buckets.computeIfAbsent(key, k -> new Bucket(capacity, currentTime));// 计算从上次填充到现在需要填充的令牌数long timePassed = currentTime - bucket.lastRefillTime;// 令牌数 = 时间间隔 * 填充速率 / 1000(转换为秒)double tokensToAdd = timePassed * refillRate / 1000.0;// 更新令牌数量,不能超过桶容量bucket.tokens = Math.min(capacity, bucket.tokens + tokensToAdd);bucket.lastRefillTime = currentTime;// 尝试获取一个令牌if (bucket.tokens >= 1) {bucket.tokens--;return true;}return false;}// 令牌桶内部类private static class Bucket {double tokens;           // 当前令牌数量long lastRefillTime;     // 上次填充时间Bucket(int capacity, long currentTime) {this.tokens = capacity;  // 初始化时桶是满的this.lastRefillTime = currentTime;}}
}
令牌桶算法的特点

优点:

  1. 支持突发流量:桶满时可以处理突发请求
  1. 限流平滑:令牌以恒定速率填充,限流更平滑
  1. 灵活性高:可以调整桶容量和填充速率
  1. 资源利用率高:能够充分利用系统资源

缺点:

  1. 实现复杂:需要维护令牌填充逻辑
  1. 内存占用:需要存储令牌数量和上次填充时间
  1. 参数调优困难:需要合理设置容量和填充速率

Redisson限流器实现

1. Maven依赖
<dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.24.3</version>
</dependency>
2. Redisson配置
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;public class RedissonConfig {public static RedissonClient createRedissonClient() {Config config = new Config();// 单机模式config.useSingleServer().setAddress("redis://localhost:6379").setDatabase(0).setPassword("your_password")  // 如果有密码.setConnectionPoolSize(64).setConnectionMinimumIdleSize(10);// 集群模式(可选)/*config.useClusterServers().addNodeAddress("redis://192.168.1.100:6379").addNodeAddress("redis://192.168.1.101:6379").addNodeAddress("redis://192.168.1.102:6379");*/return Redisson.create(config);}
}
3. 基础限流器使用
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;public class RedissonRateLimiterExample {private final RedissonClient redissonClient;public RedissonRateLimiterExample() {this.redissonClient = RedissonConfig.createRedissonClient();}public void basicRateLimiter() {// 获取限流器RRateLimiter rateLimiter = redissonClient.getRateLimiter("myRateLimiter");// 初始化限流器// 参数说明:// RateType.OVERALL: 全局限流// RateType.PER_CLIENT: 每个客户端限流// 10: 速率(每秒10个请求)// 1: 时间单位(秒)// RateIntervalUnit.SECONDS: 时间单位类型rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);// 尝试获取许可boolean acquired = rateLimiter.tryAcquire(1);if (acquired) {System.out.println("请求被允许");// 执行业务逻辑} else {System.out.println("请求被限流");// 返回限流响应}}public void perUserRateLimiter(String userId) {// 为每个用户创建独立的限流器RRateLimiter userLimiter = redissonClient.getRateLimiter("user:" + userId + ":limiter");// 设置每个用户每分钟最多100次请求userLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.MINUTES);boolean acquired = userLimiter.tryAcquire(1);if (acquired) {System.out.println("用户 " + userId + " 的请求被允许");} else {System.out.println("用户 " + userId + " 的请求被限流");}}
}
4. 高级限流器功能
public class AdvancedRedissonRateLimiter {private final RedissonClient redissonClient;public AdvancedRedissonRateLimiter() {this.redissonClient = RedissonConfig.createRedissonClient();}// 带等待时间的限流器public void rateLimiterWithWait() {RRateLimiter rateLimiter = redissonClient.getRateLimiter("waitLimiter");rateLimiter.trySetRate(RateType.OVERALL, 5, 1, RateIntervalUnit.SECONDS);try {// 等待最多2秒获取许可boolean acquired = rateLimiter.tryAcquire(1, 2, TimeUnit.SECONDS);if (acquired) {System.out.println("成功获取许可");} else {System.out.println("等待超时,请求被限流");}} catch (InterruptedException e) {Thread.currentThread().interrupt();System.out.println("等待被中断");}}// 批量获取许可public void batchAcquire() {RRateLimiter rateLimiter = redissonClient.getRateLimiter("batchLimiter");rateLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.MINUTES);// 尝试一次性获取10个许可boolean acquired = rateLimiter.tryAcquire(10);if (acquired) {System.out.println("成功获取10个许可");} else {System.out.println("许可不足,无法获取10个许可");}}// 获取限流器状态public void getLimiterStatus() {RRateLimiter rateLimiter = redissonClient.getRateLimiter("statusLimiter");rateLimiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);// 获取当前可用许可数long availablePermits = rateLimiter.availablePermits();System.out.println("当前可用许可数: " + availablePermits);// 获取等待队列长度long waitingThreads = rateLimiter.waitingThreads();System.out.println("等待线程数: " + waitingThreads);}
}

实际应用场景

1. Spring Boot集成
import org.springframework.web.bind.annotation.*;
import org.springframework.http.ResponseEntity;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;@RestController
@RequestMapping("/api")
public class ApiController {private final RedissonClient redissonClient;public ApiController(RedissonClient redissonClient) {this.redissonClient = redissonClient;}@GetMapping("/user/{id}")public ResponseEntity<?> getUser(@PathVariable Long id, @RequestHeader("X-User-Id") String userId) {// 创建用户限流器RRateLimiter userLimiter = redissonClient.getRateLimiter("user:" + userId + ":api");userLimiter.trySetRate(RateType.OVERALL, 100, 1, RateIntervalUnit.MINUTES);// 尝试获取许可if (!userLimiter.tryAcquire(1)) {return ResponseEntity.status(429).body("请求过于频繁,请稍后再试");}// 执行业务逻辑User user = userService.getUser(id);return ResponseEntity.ok(user);}@PostMapping("/sms/send")public ResponseEntity<?> sendSms(@RequestBody SmsRequest request) {// 手机号限流RRateLimiter phoneLimiter = redissonClient.getRateLimiter("phone:" + request.getPhone());phoneLimiter.trySetRate(RateType.OVERALL, 3, 1, RateIntervalUnit.MINUTES);if (!phoneLimiter.tryAcquire(1)) {return ResponseEntity.status(429).body("该手机号发送短信过于频繁");}// 发送短信smsService.sendSms(request.getPhone(), request.getContent());return ResponseEntity.ok("短信发送成功");}
}
2. 自定义注解实现
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 {String key() default "";           // 限流键int limit() default 100;           // 限制次数int time() default 60;             // 时间窗口(秒)String message() default "请求过于频繁"; // 限流消息
}// AOP实现
@Component
@Aspect
public class RateLimitAspect {private final RedissonClient redissonClient;public RateLimitAspect(RedissonClient redissonClient) {this.redissonClient = redissonClient;}@Around("@annotation(rateLimit)")public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {// 生成限流键String key = generateKey(point, rateLimit);// 获取限流器RRateLimiter limiter = redissonClient.getRateLimiter(key);limiter.trySetRate(RateType.OVERALL, rateLimit.limit(), rateLimit.time(), RateIntervalUnit.SECONDS);// 尝试获取许可if (!limiter.tryAcquire(1)) {throw new RuntimeException(rateLimit.message());}// 执行业务方法return point.proceed();}private String generateKey(ProceedingJoinPoint point, RateLimit rateLimit) {if (!rateLimit.key().isEmpty()) {return rateLimit.key();}// 自动生成键:类名:方法名String className = point.getTarget().getClass().getSimpleName();String methodName = point.getSignature().getName();return className + ":" + methodName;}
}// 使用注解
@RestController
public class UserController {@RateLimit(limit = 10, time = 60, message = "登录尝试过于频繁")@PostMapping("/login")public ResponseEntity<?> login(@RequestBody LoginRequest request) {// 登录逻辑return ResponseEntity.ok("登录成功");}@RateLimit(key = "#request.phone", limit = 3, time = 60)@PostMapping("/verify")public ResponseEntity<?> verifyPhone(@RequestBody VerifyRequest request) {// 手机验证逻辑return ResponseEntity.ok("验证码发送成功");}
}
3. 分布式限流器
public class DistributedRateLimiter {private final RedissonClient redissonClient;public DistributedRateLimiter(RedissonClient redissonClient) {this.redissonClient = redissonClient;}// IP限流public boolean isIpAllowed(String ip, int limit, int timeWindow) {RRateLimiter ipLimiter = redissonClient.getRateLimiter("ip:" + ip);ipLimiter.trySetRate(RateType.OVERALL, limit, timeWindow, RateIntervalUnit.SECONDS);return ipLimiter.tryAcquire(1);}// 用户限流public boolean isUserAllowed(String userId, int limit, int timeWindow) {RRateLimiter userLimiter = redissonClient.getRateLimiter("user:" + userId);userLimiter.trySetRate(RateType.OVERALL, limit, timeWindow, RateIntervalUnit.SECONDS);return userLimiter.tryAcquire(1);}// 接口限流public boolean isApiAllowed(String apiKey, int limit, int timeWindow) {RRateLimiter apiLimiter = redissonClient.getRateLimiter("api:" + apiKey);apiLimiter.trySetRate(RateType.OVERALL, limit, timeWindow, RateIntervalUnit.SECONDS);return apiLimiter.tryAcquire(1);}// 全局限流public boolean isGlobalAllowed(int limit, int timeWindow) {RRateLimiter globalLimiter = redissonClient.getRateLimiter("global");globalLimiter.trySetRate(RateType.OVERALL, limit, timeWindow, RateIntervalUnit.SECONDS);return globalLimiter.tryAcquire(1);}
}

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

相关文章:

  • HTML 入门教程:从零开始学习网页开发基础
  • 前端知识:浏览器工作原理与开发者工具知识笔记
  • WIN10系统优化篇(一)
  • Leetcode 02 java
  • IDEA报错“资源找不到”?重启就好了!!?
  • 使用Dify构建HR智能助理,深度集成大模型应用,赋能HR招聘管理全流程,dify相关工作流全开源。
  • 城市蓝影.
  • 服务注册nacos和OpenFerign(用于封装跨服务之间的调用方法)
  • kubernetes学习笔记(一)
  • 数据结构 双向链表(2)--双向链表的实现
  • 黄仁勋链博会演讲实录:脱掉皮衣,穿上唐装,中文开场
  • 完善评论发布功能
  • PHP面向对象编程:类与对象的基础概念与实践
  • 从0到1搭建Lazada账号矩阵:自养号测评的精细化养号全攻略
  • Linux 定时器应用示例
  • 功能测试和回归测试
  • C# WPF后台设置控件样式失效的解决方法
  • 【Vue】tailwindcss + ant-design-vue + vue-cropper 图片裁剪功能(解决遇到的坑)
  • 从规模到效率:大模型三大定律与Chinchilla定律详解
  • 实现通讯录人员选择
  • IKE学习笔记
  • Java强化:多线程及线程池
  • 从电子管到CPU
  • 基于MATLAB的决策树DT的数据分类预测方法应用
  • Android CameraX使用
  • [析]Deep reinforcement learning for drone navigation using sensor data
  • CClink IEF Basic设备数据 保存到MySQL数据库项目案例
  • 高德地图MCP服务使用案例
  • 解锁数据交换的魔法工具——Protocol Buffers
  • 矿业自动化破壁者:EtherCAT转PROFIBUS DP网关的井下实战