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

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.xdnf.cn/news/15506.html

相关文章:

  • Vue加密文章密码 VuePress
  • 数据结构 双向链表(1)
  • 基于Matlab的四旋翼无人机动力学PID控制仿真
  • PyTorch 参数初始化详解:从理论到实践
  • ZYNQ Petalinux系统FLASH固化终极指南:创新多分区与双系统切换实战
  • 如何区分Bug是前端问题还是后端问题?
  • UE5多人MOBA+GAS 24、创建属性UI(一)
  • 插板式系统的“生命线“:EtherCAT分布式供电该如何实现?
  • 第13章 AB实验平台的建设
  • 解锁高效Excel技能:摆脱鼠标,快速编辑单元格
  • 凯伦股份融合复合瓦:新时代可焊接物理防腐金属屋面系统方案
  • Mysql练习
  • Linux命令大全
  • 第五章 管道工程 5.4 管道安全质量控制
  • 设计一款用于捕捉动态产品视频的摄像机器人
  • 元宇宙经济:虚实融合引发经济新变革
  • 前端学习7:CSS过渡与动画--补间动画 (Transition) vs 关键帧动画 (Animation)
  • Linux切换到Jenkins用户解决Jenkins Host key verification failed
  • 工业相机GigE数据接口的优势及应用
  • 以太网供电与自愈网络对音视频系统的益处
  • 重学前端006 --- 响应式网页设计 CSS 弹性盒子
  • ssl相关命令生成证书
  • 阿里云 RabbitMQ 可观测性最佳实践
  • 蓝光三维扫描技术:手机闪光灯模块全尺寸3D检测的高效解决方案
  • 逆功率检测设备防逆流解决方案守护电网安全
  • 智能体架构深度解构:一次用户请求的完整旅程
  • MyBatis 之分页四式传参与聚合、主键操作全解
  • MySQL学习——面试版
  • Navicat操作指南:MySQL数据库配置与Todo应用部署
  • 从零开始的云计算生活——番外3,LVS+KeepAlived+Nginx高可用实现方案