ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Redis滑动窗口算法实现接口防刷与SpringBoot集成实战

Redis滑动窗口算法实现接口防刷与SpringBoot集成实战 1. 为什么我们需要接口防刷机制在当今互联网应用中暴力攻击已经成为最常见的安全威胁之一。攻击者通过自动化工具对登录、短信验证码、支付等关键接口发起高频请求可能导致服务器资源耗尽、业务数据泄露或产生大量无效费用。我曾在多个项目中遇到过这类问题最严重的一次是短信接口被刷导致单日损失超过5万元。传统的解决方案如固定时间窗口计数比如每分钟限制100次请求存在明显缺陷。攻击者可以在每分钟的第59秒集中发送100次请求然后在下一分钟的第0秒再次发送100次请求实际上在2秒内完成了200次请求而系统却认为这是合规的。2. 滑动窗口计数算法原理剖析2.1 基本概念与数学模型滑动窗口算法是对固定时间窗口的改进它统计的是动态时间窗口内的请求次数。假设我们设置1分钟的窗口大小和100次的限制当前请求时间戳为T统计区间为[T-60秒, T]内的请求次数如果计数超过100则拒绝请求数学表达式为count sum(requests where timestamp T - window_size) if count limit then reject2.2 Redis实现方案选型Redis是实现滑动窗口计数的理想选择主要考虑以下数据结构ZSET有序集合方案使用时间戳作为score成员可以是请求ID或客户端IP通过ZREMRANGEBYSCORE清理过期记录ZCARD获取当前窗口计数Lua脚本方案保证原子性操作单次往返完成所有操作示例脚本local key KEYS[1] local now tonumber(ARGV[1]) local window tonumber(ARGV[2]) local limit tonumber(ARGV[3]) redis.call(ZREMRANGEBYSCORE, key, 0, now - window) local count redis.call(ZCARD, key) if count limit then return 0 else redis.call(ZADD, key, now, now) redis.call(EXPIRE, key, window) return 1 endToken Bucket方案对比更适合平滑限流实现复杂度较高对突发流量控制不如滑动窗口直接3. SpringBoot集成实战3.1 基础环境搭建首先确保项目中包含Redis依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency配置Redis连接application.ymlspring: redis: host: localhost port: 6379 timeout: 3000ms lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 03.2 自定义注解设计创建限流注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RateLimit { String key() default ; int limit() default 100; int window() default 60; // 秒 String message() default 请求过于频繁; }3.3 切面实现核心逻辑Aspect Component public class RateLimitAspect { Autowired private StringRedisTemplate redisTemplate; Around(annotation(rateLimit)) public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) throws Throwable { String key generateKey(joinPoint, rateLimit); long now System.currentTimeMillis() / 1000; long window rateLimit.window(); long limit rateLimit.limit(); Long count redisTemplate.execute( new RedisCallbackLong() { Override public Long doInRedis(RedisConnection connection) { String script local key KEYS[1]\n local now tonumber(ARGV[1])\n local window tonumber(ARGV[2])\n local limit tonumber(ARGV[3])\n\n redis.call(ZREMRANGEBYSCORE, key, 0, now - window)\n local count redis.call(ZCARD, key)\n if count limit then\n return 0\n else\n redis.call(ZADD, key, now, now)\n redis.call(EXPIRE, key, window)\n return 1\n end; return connection.eval( script.getBytes(), ReturnType.INTEGER, 1, key.getBytes(), String.valueOf(now).getBytes(), String.valueOf(window).getBytes(), String.valueOf(limit).getBytes() ); } } ); if (count null || count 0) { throw new RuntimeException(rateLimit.message()); } return joinPoint.proceed(); } private String generateKey(ProceedingJoinPoint joinPoint, RateLimit rateLimit) { // 实现根据方法参数生成唯一key的逻辑 } }4. 高级优化与生产实践4.1 分布式环境下的挑战在实际生产环境中我们还需要考虑Redis集群方案使用相同的hash tag保证key分配到同一节点考虑使用Redisson的RRateLimiter本地缓存Redis的二级缓存在应用本地使用Guava Cache做第一层过滤减少Redis访问压力限流维度设计IP级别限流用户级别限流需要登录态设备指纹限流4.2 动态配置方案通过Spring Cloud Config或Nacos实现动态调整参数RefreshScope Configuration public class RateLimitConfig { Value(${rate.limit.login:100}) private int loginLimit; // getters... }4.3 监控与告警集成Prometheus监控Bean public MeterRegistryCustomizerMeterRegistry metricsCommonTags() { return registry - registry.config().commonTags( application, your-application-name ); } // 在切面中增加指标采集 Counter.builder(rate_limit.rejected) .tag(method, methodName) .register(meterRegistry) .increment();5. 不同场景的实战配置建议5.1 登录接口防护典型配置RateLimit(key login:#{ip}, limit 5, window 300, message 登录尝试过于频繁) PostMapping(/login) public ResponseEntity? login(RequestBody LoginDTO dto) { // 登录逻辑 }额外建议结合验证码机制失败次数累计触发账户锁定记录审计日志5.2 短信接口防护关键配置RateLimit(key sms:#{phone}, limit 1, window 60) GetMapping(/sms/code) public ResponseEntity? sendSmsCode(RequestParam String phone) { // 发送短信逻辑 }注意事项同一手机号限制IP段限制防止遍历手机号业务规则校验如已注册用户才允许发送5.3 支付接口防护推荐方案RateLimit(key pay:#{#user.id}, limit 10, window 3600) PostMapping(/pay) public ResponseEntity? createPayment(Auth User user, RequestBody PaymentDTO dto) { // 支付逻辑 }增强措施金额阈值二次验证同设备多账户监控交易频率异常检测6. 性能优化与压测数据6.1 Redis性能测试在4核8G的Redis实例上测试结果并发线程数平均响应时间(ms)吞吐量(req/s)502.123,0001003.826,0002007.227,0006.2 优化技巧Pipeline批量操作对于批量接口可以使用pipeline减少网络开销Lua脚本优化避免在Lua中使用循环和复杂计算本地缓存对于全局限制如总QPS可以先检查本地计数器Key设计使用hash tag保证集群环境下key分布在同一节点7. 常见问题排查指南7.1 限流不生效检查清单确认切面被Spring管理是否加了Component检查Redis连接是否正常验证Key生成逻辑是否符合预期检查时间单位是否一致秒/毫秒混淆7.2 Redis内存增长过快解决方案确保设置了EXPIRE增加ZREMRANGEBYSCORE的执行频率限制最大ZSET大小-- 在Lua脚本中添加 local max_size 10000 if count max_size then redis.call(ZREMRANGEBYRANK, key, 0, count - max_size - 1) end7.3 误杀正常用户处理策略实现白名单机制动态调整阈值如夜间放宽限制分级限流策略先警告后限制8. 延伸思考与进阶方案8.1 自适应限流算法可以基于系统负载动态调整限流阈值double load ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); int dynamicLimit (int) (baseLimit * (1 / (load 0.5)));8.2 机器学习异常检测使用统计模型识别异常流量模式收集历史请求数据建立时间序列模型实时计算请求特征偏离度8.3 全链路防护体系完整的接口防护应该包含前端验证码、行为验证网关全局流量控制业务层细粒度规则风控系统复杂规则引擎在实际项目中我通常会先实现基础的滑动窗口限流然后根据业务需求逐步叠加更复杂的防护策略。记住安全防护是一个持续的过程需要定期review和调整策略。
返回列表