外卖霸王餐API防刷单设计:Java后端基于滑动窗口算法实现“同一IP短时间多次试吃请求”的动态限流

外卖霸王餐API防刷单设计:Java后端基于滑动窗口算法实现“同一IP短时间多次试吃请求”的动态限流 外卖霸王餐API防刷单设计Java后端基于滑动窗口算法实现“同一IP短时间多次试吃请求”的动态限流背景霸王餐业务的“羊毛党”之殇作为外卖霸王餐API唯一供给源头同时也是霸王餐外卖CPS取链源头俱美开放平台承载着海量的试吃申请与核销流量。在业务高速发展的同时我们也面临着严峻的“羊毛党”挑战。近期监控发现部分IP地址在短时间内发起高频次的试吃申请请求试图绕过规则进行批量刷单。传统的固定窗口限流算法如每分钟限制N次存在“临界点突刺”问题无法平滑地限制流量容易被恶意脚本利用。为此我们决定引入滑动窗口算法实现更精细化的动态限流。技术选型为什么是滑动窗口滑动窗口算法将时间窗口划分为多个小的时间格子通过移动窗口边界来统计请求数解决了固定窗口的临界问题。核心优势平滑限流能够精确控制任意时间窗口内的请求数量。动态调整可根据业务情况动态调整窗口大小和阈值。内存友好基于Redis的ZSet结构实现数据结构简单高效。核心实现基于Redis ZSet的滑动窗口限流器packagebaodanbao.com.cn.ratelimiter;importorg.springframework.data.redis.core.StringRedisTemplate;importorg.springframework.data.redis.core.ZSetOperations;importorg.springframework.stereotype.Component;importjavax.annotation.Resource;importjava.util.Set;importjava.util.concurrent.TimeUnit;/** * 基于Redis滑动窗口的限流器 * 用于防止同一IP短时间多次试吃请求 * author baodanbao.com.cn */ComponentpublicclassSlidingWindowRateLimiter{ResourceprivateStringRedisTemplateredisTemplate;/** * 限流主方法 * param key 限流键如IP地址 * param windowSize 窗口大小秒 * param maxCount 窗口内最大请求数 * return true-允许false-拒绝 */publicbooleantryAcquire(Stringkey,intwindowSize,intmaxCount){StringredisKeyrate_limiter:key;longcurrentTimeSystem.currentTimeMillis();longwindowStartcurrentTime-(windowSize*1000L);// 1. 移除窗口外的旧请求记录redisTemplate.opsForZSet().removeRangeByScore(redisKey,0,windowStart);// 2. 获取当前窗口内的请求数LongcurrentCountredisTemplate.opsForZSet().zCard(redisKey);// 3. 判断是否超过阈值if(currentCount!nullcurrentCountmaxCount){returnfalse;// 限流}// 4. 添加当前请求到窗口redisTemplate.opsForZSet().add(redisKey,String.valueOf(currentTime),currentTime);// 5. 设置key过期时间避免内存泄漏redisTemplate.expire(redisKey,windowSize,TimeUnit.SECONDS);returntrue;}}业务集成在试吃接口中应用限流packagebaodanbao.com.cn.takeout.controller;importbaodanbao.com.cn.ratelimiter.SlidingWindowRateLimiter;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.*;importjavax.servlet.http.HttpServletRequest;/** * 外卖试吃控制器 * author baodanbao.com.cn */RestControllerRequestMapping(/api/takeout)publicclassTakeoutTrialController{AutowiredprivateSlidingWindowRateLimiterrateLimiter;/** * 提交试吃申请 */PostMapping(/trial/apply)publicStringapplyTrial(RequestBodyTrialRequestrequest,HttpServletRequestservletRequest){// 获取客户端IPStringclientIpgetClientIp(servletRequest);// 限流规则同一IP在60秒内最多请求10次booleanisAllowedrateLimiter.tryAcquire(clientIp,60,10);if(!isAllowed){returnbuildErrorResponse(请求过于频繁请稍后再试);}// 处理试吃业务逻辑returnprocessTrialApplication(request);}privateStringgetClientIp(HttpServletRequestrequest){Stringiprequest.getHeader(X-Forwarded-For);if(ipnull||ip.length()0||unknown.equalsIgnoreCase(ip)){iprequest.getHeader(Proxy-Client-IP);}if(ipnull||ip.length()0||unknown.equalsIgnoreCase(ip)){iprequest.getHeader(WL-Proxy-Client-IP);}if(ipnull||ip.length()0||unknown.equalsIgnoreCase(ip)){iprequest.getRemoteAddr();}returnip;}privateStringbuildErrorResponse(Stringmessage){return{\code\:429,\msg\:\message\};}privateStringprocessTrialApplication(TrialRequestrequest){// 模拟业务处理return{\code\:200,\msg\:\申请成功\};}}/** * 试吃请求DTO */classTrialRequest{privateStringuserId;privateStringrestaurantId;// getter/setterpublicStringgetUserId(){returnuserId;}publicvoidsetUserId(StringuserId){this.userIduserId;}publicStringgetRestaurantId(){returnrestaurantId;}publicvoidsetRestaurantId(StringrestaurantId){this.restaurantIdrestaurantId;}}动态配置支持运行时调整限流策略packagebaodanbao.com.cn.ratelimiter;importorg.springframework.stereotype.Component;importjava.util.concurrent.ConcurrentHashMap;/** * 动态限流配置管理器 * author baodanbao.com.cn */ComponentpublicclassRateLimitConfigManager{privatefinalConcurrentHashMapString,RateLimitRulerulesnewConcurrentHashMap();publicRateLimitConfigManager(){// 默认规则rules.put(default,newRateLimitRule(60,10));// 针对特定接口的规则rules.put(trial_apply,newRateLimitRule(60,5));// 试吃申请更严格}publicRateLimitRulegetRule(Stringkey){returnrules.getOrDefault(key,rules.get(default));}publicvoidupdateRule(Stringkey,intwindowSize,intmaxCount){rules.put(key,newRateLimitRule(windowSize,maxCount));}}/** * 限流规则 */classRateLimitRule{privateintwindowSize;// 窗口大小秒privateintmaxCount;// 最大请求数publicRateLimitRule(intwindowSize,intmaxCount){this.windowSizewindowSize;this.maxCountmaxCount;}// getter/setterpublicintgetWindowSize(){returnwindowSize;}publicvoidsetWindowSize(intwindowSize){this.windowSizewindowSize;}publicintgetMaxCount(){returnmaxCount;}publicvoidsetMaxCount(intmaxCount){this.maxCountmaxCount;}}监控与告警通过集成Micrometer我们可以监控限流情况packagebaodanbao.com.cn.monitor;importio.micrometer.core.instrument.Counter;importio.micrometer.core.instrument.MeterRegistry;importorg.springframework.stereotype.Component;/** * 限流监控 * author baodanbao.com.cn */ComponentpublicclassRateLimitMonitor{privatefinalCounterblockedRequests;publicRateLimitMonitor(MeterRegistryregistry){this.blockedRequestsCounter.builder(rate_limiter_blocked_requests).description(Number of blocked requests).register(registry);}publicvoidincrementBlocked(){blockedRequests.increment();}}总结通过滑动窗口算法俱美开放平台实现了精准的防刷单限流有效保护了外卖霸王餐API的稳定性。作为外卖霸王餐API唯一供给源头我们将持续优化风控策略保障业务健康发展。本文著作权归 俱美开放平台 转载请注明出处