1. 为什么需要分布式限流想象一下双十一零点抢购的场景数百万用户同时点击立即购买按钮如果没有任何防护措施服务器会在瞬间被海量请求淹没。这就是分布式限流技术存在的意义——它像交通信号灯一样控制着请求的流量节奏。我在电商平台工作期间曾遇到过凌晨促销活动导致MySQL数据库CPU飙升至100%的情况。当时采用的简单计数器限流方案在流量洪峰面前完全失效。后来通过引入滑动窗口算法配合Redis的原子操作才真正解决了这个问题。分布式限流与传统单机限流的本质区别在于状态共享。当你的服务部署在10台服务器上每台机器单独计数会导致整体限流失效。比如设置每分钟1000次请求限制10台机器各自计数就可能实际放行10000次请求。Redisson提供的分布式数据结构正是为此而生它能确保所有服务节点基于同一套计数规则。2. 滑动窗口算法深度解析2.1 从固定窗口到滑动窗口的进化固定窗口算法就像老式电表每月1号准时清零。假设设置每分钟最多100次请求在00:59秒接收90次请求在01:00秒又接收90次请求 虽然两个半分钟都未超限但实际在60秒内处理了180次请求——这就是著名的临界问题。滑动窗口算法将这个固定窗口切分成更细的时间片比如60个1秒的格子统计时只计算最近60秒内的请求数。就像高速公路的区间测速不是看瞬时速度而是计算某段路程的平均速度。2.2 Redisson的滑动窗口实现Redisson通过RList和Lua脚本实现了高效的滑动窗口。核心逻辑是每个请求到达时记录当前时间戳清理早于当前时间-窗口长度的旧记录检查剩余记录数是否超过阈值// Redisson滑动窗口伪代码实现 RListLong window redisson.getList(rateLimiter: apiKey); window.add(System.currentTimeMillis()); // 清理过期记录 long now System.currentTimeMillis(); window.removeIf(timestamp - timestamp now - 60000); if (window.size() 100) { throw new RateLimitException(); }实测发现直接使用List结构在QPS超过5000时性能下降明显。这时应该改用分片计数器方案将大窗口拆分为多个子窗口通过Redis的INCR命令实现原子计数。3. Redisson实战配置指南3.1 基础环境搭建首先在Spring Boot项目中引入依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.23.4/version /dependency配置文件中需要设置Redis连接参数spring: redis: host: redis-cluster.example.com port: 6379 password: yourpassword3.2 滑动窗口限流器实现创建分布式限流服务类Service public class SlidingWindowRateLimiter { Autowired private RedissonClient redissonClient; public boolean tryAcquire(String key, int windowSizeSec, int maxRequests) { String redisKey rate_limit: key; long now System.currentTimeMillis(); long windowMillis windowSizeSec * 1000L; // 使用ZSet存储请求时间戳 RScoredSortedSetLong requestLog redissonClient .getScoredSortedSet(redisKey); // 移除窗口外的旧记录 requestLog.removeRangeByScore(0, true, now - windowMillis, true); // 添加当前请求 requestLog.add(now, now); // 设置键过期时间 requestLog.expire(Duration.ofSeconds(windowSizeSec 1)); return requestLog.size() maxRequests; } }3.3 性能优化技巧在高并发场景下有几点特别需要注意管道化操作将多个Redis命令打包执行本地缓存在JVM层做一级缓存减少Redis访问预热机制活动开始前预先扩容Redis集群我们通过JMeter压测发现优化后的方案在10万QPS下平均延迟从78ms降到了23ms。关键配置参数包括参数名推荐值说明maxRetries3Redis操作重试次数timeout500ms单次操作超时时间slaves至少2个读操作从节点数4. 电商秒杀场景实战4.1 分层限流架构设计真正的秒杀系统需要多层防御前端层按钮置灰、验证码接入层Nginx限流服务层分布式滑动窗口数据层Redis原子计数我们在某次手机新品发售中采用这样的配置第一层Nginx限制单个IP每秒5次请求第二层滑动窗口每分钟100次全局请求第三层库存预扣减采用Redis DECR原子操作4.2 突发流量处理方案遇到流量洪峰时单纯拒绝请求会导致用户体验差。我们设计了柔性降级方案请求排队超出阈值的请求进入RabbitMQ队列流量整形匀速处理队列中的消息动态扩容根据队列长度自动扩容Worker节点关键代码片段RateLimit(limitType LimitType.IP, rate100, interval60) GetMapping(/seckill) public String seckill(RequestParam String itemId) { if (!rateLimiter.tryAcquire()) { // 进入降级流程 mqTemplate.send(seckill-queue, buildMessage(itemId)); return 排队中请稍候...; } return doSeckill(itemId); }4.3 监控与调优使用PrometheusGrafana搭建监控看板重点关注这些指标限流触发频率请求处理延迟P99值Redis内存和CPU使用率某次调优前后对比数据指标优化前优化后超限请求比例12%3.8%平均延迟142ms67msRedis CPU85%45%5. 常见问题排查手册5.1 Redis连接池爆满错误现象大量Could not get a resource from the pool日志 解决方案增加连接池大小spring: redis: lettuce: pool: max-active: 200 max-idle: 50添加连接有效性检测设置合理的超时时间5.2 时间同步问题在K8s环境中遇到过因容器时间不同步导致的限流失效。解决方案所有节点安装NTP服务Redis服务器启用时间同步在代码中加入时间漂移检测逻辑5.3 热点Key问题当某个商品秒杀导致单个Redis Key访问量激增时采用Key分片将rate_limit:item1拆分为多个子Key本地缓存结合Caffeine做JVM级缓存读写分离限流写操作走主节点统计读操作走从节点6. 算法选型建议经过多次压测对比不同场景下的推荐方案精准控制滑动窗口电商支付突发流量令牌桶秒杀系统简单场景固定窗口后台管理平滑输出漏桶算法短信发送最近在测试Redisson的RRateLimiter时发现其底层采用混合模式小时间窗口用滑动窗口算法大时间窗口自动切换为令牌桶 这种设计在保证精度的同时减少了内存消耗。
分布式限流实战 | 从算法原理到Redisson滑动窗口实现
1. 为什么需要分布式限流想象一下双十一零点抢购的场景数百万用户同时点击立即购买按钮如果没有任何防护措施服务器会在瞬间被海量请求淹没。这就是分布式限流技术存在的意义——它像交通信号灯一样控制着请求的流量节奏。我在电商平台工作期间曾遇到过凌晨促销活动导致MySQL数据库CPU飙升至100%的情况。当时采用的简单计数器限流方案在流量洪峰面前完全失效。后来通过引入滑动窗口算法配合Redis的原子操作才真正解决了这个问题。分布式限流与传统单机限流的本质区别在于状态共享。当你的服务部署在10台服务器上每台机器单独计数会导致整体限流失效。比如设置每分钟1000次请求限制10台机器各自计数就可能实际放行10000次请求。Redisson提供的分布式数据结构正是为此而生它能确保所有服务节点基于同一套计数规则。2. 滑动窗口算法深度解析2.1 从固定窗口到滑动窗口的进化固定窗口算法就像老式电表每月1号准时清零。假设设置每分钟最多100次请求在00:59秒接收90次请求在01:00秒又接收90次请求 虽然两个半分钟都未超限但实际在60秒内处理了180次请求——这就是著名的临界问题。滑动窗口算法将这个固定窗口切分成更细的时间片比如60个1秒的格子统计时只计算最近60秒内的请求数。就像高速公路的区间测速不是看瞬时速度而是计算某段路程的平均速度。2.2 Redisson的滑动窗口实现Redisson通过RList和Lua脚本实现了高效的滑动窗口。核心逻辑是每个请求到达时记录当前时间戳清理早于当前时间-窗口长度的旧记录检查剩余记录数是否超过阈值// Redisson滑动窗口伪代码实现 RListLong window redisson.getList(rateLimiter: apiKey); window.add(System.currentTimeMillis()); // 清理过期记录 long now System.currentTimeMillis(); window.removeIf(timestamp - timestamp now - 60000); if (window.size() 100) { throw new RateLimitException(); }实测发现直接使用List结构在QPS超过5000时性能下降明显。这时应该改用分片计数器方案将大窗口拆分为多个子窗口通过Redis的INCR命令实现原子计数。3. Redisson实战配置指南3.1 基础环境搭建首先在Spring Boot项目中引入依赖dependency groupIdorg.redisson/groupId artifactIdredisson-spring-boot-starter/artifactId version3.23.4/version /dependency配置文件中需要设置Redis连接参数spring: redis: host: redis-cluster.example.com port: 6379 password: yourpassword3.2 滑动窗口限流器实现创建分布式限流服务类Service public class SlidingWindowRateLimiter { Autowired private RedissonClient redissonClient; public boolean tryAcquire(String key, int windowSizeSec, int maxRequests) { String redisKey rate_limit: key; long now System.currentTimeMillis(); long windowMillis windowSizeSec * 1000L; // 使用ZSet存储请求时间戳 RScoredSortedSetLong requestLog redissonClient .getScoredSortedSet(redisKey); // 移除窗口外的旧记录 requestLog.removeRangeByScore(0, true, now - windowMillis, true); // 添加当前请求 requestLog.add(now, now); // 设置键过期时间 requestLog.expire(Duration.ofSeconds(windowSizeSec 1)); return requestLog.size() maxRequests; } }3.3 性能优化技巧在高并发场景下有几点特别需要注意管道化操作将多个Redis命令打包执行本地缓存在JVM层做一级缓存减少Redis访问预热机制活动开始前预先扩容Redis集群我们通过JMeter压测发现优化后的方案在10万QPS下平均延迟从78ms降到了23ms。关键配置参数包括参数名推荐值说明maxRetries3Redis操作重试次数timeout500ms单次操作超时时间slaves至少2个读操作从节点数4. 电商秒杀场景实战4.1 分层限流架构设计真正的秒杀系统需要多层防御前端层按钮置灰、验证码接入层Nginx限流服务层分布式滑动窗口数据层Redis原子计数我们在某次手机新品发售中采用这样的配置第一层Nginx限制单个IP每秒5次请求第二层滑动窗口每分钟100次全局请求第三层库存预扣减采用Redis DECR原子操作4.2 突发流量处理方案遇到流量洪峰时单纯拒绝请求会导致用户体验差。我们设计了柔性降级方案请求排队超出阈值的请求进入RabbitMQ队列流量整形匀速处理队列中的消息动态扩容根据队列长度自动扩容Worker节点关键代码片段RateLimit(limitType LimitType.IP, rate100, interval60) GetMapping(/seckill) public String seckill(RequestParam String itemId) { if (!rateLimiter.tryAcquire()) { // 进入降级流程 mqTemplate.send(seckill-queue, buildMessage(itemId)); return 排队中请稍候...; } return doSeckill(itemId); }4.3 监控与调优使用PrometheusGrafana搭建监控看板重点关注这些指标限流触发频率请求处理延迟P99值Redis内存和CPU使用率某次调优前后对比数据指标优化前优化后超限请求比例12%3.8%平均延迟142ms67msRedis CPU85%45%5. 常见问题排查手册5.1 Redis连接池爆满错误现象大量Could not get a resource from the pool日志 解决方案增加连接池大小spring: redis: lettuce: pool: max-active: 200 max-idle: 50添加连接有效性检测设置合理的超时时间5.2 时间同步问题在K8s环境中遇到过因容器时间不同步导致的限流失效。解决方案所有节点安装NTP服务Redis服务器启用时间同步在代码中加入时间漂移检测逻辑5.3 热点Key问题当某个商品秒杀导致单个Redis Key访问量激增时采用Key分片将rate_limit:item1拆分为多个子Key本地缓存结合Caffeine做JVM级缓存读写分离限流写操作走主节点统计读操作走从节点6. 算法选型建议经过多次压测对比不同场景下的推荐方案精准控制滑动窗口电商支付突发流量令牌桶秒杀系统简单场景固定窗口后台管理平滑输出漏桶算法短信发送最近在测试Redisson的RRateLimiter时发现其底层采用混合模式小时间窗口用滑动窗口算法大时间窗口自动切换为令牌桶 这种设计在保证精度的同时减少了内存消耗。