
限流是保护后端系统的第一道防线。用 AOP Redis 实现一个自定义限流注解加到接口上就能限流。一、定义注解Target(ElementType.METHOD)Retention(RetentionPolicy.RUNTIME)publicinterfaceRateLimit{/** 限流 key */Stringkey()default;/** 限流维度user / ip / api */Stringtype()defaultuser;/** 窗口内最大请求数 */longmax()default10;/** 窗口大小秒 */longwindow()default1;/** 被限后的提示 */Stringmessage()default请求太频繁请稍后重试;}二、实现 AOPAspectComponentpublicclassRateLimitAspect{AutowiredprivateStringRedisTemplateredisTemplate;Around(annotation(rateLimit))publicObjectaround(ProceedingJoinPointpjp,RateLimitrateLimit)throwsThrowable{StringkeybuildKey(rateLimit);// 滑动窗口限流booleanallowedtryAcquire(key,rateLimit.max(),rateLimit.window());if(!allowed){thrownewBusinessException(429,rateLimit.message());}returnpjp.proceed();}privatebooleantryAcquire(Stringkey,longmax,longwindowSec){longnowSystem.currentTimeMillis();longwindowMswindowSec*1000L;longwindowStartnow-windowMs;// Lua 脚本保证原子性Stringluaredis.call(ZREMRANGEBYSCORE, KEYS[1], 0, ARGV[1]) local count redis.call(ZCARD, KEYS[1]) if count tonumber(ARGV[2]) then redis.call(ZADD, KEYS[1], ARGV[3], ARGV[3]) redis.call(EXPIRE, KEYS[1], ARGV[4]) return 1 else return 0 end;LongresultredisTemplate.execute(newDefaultRedisScript(lua,Long.class),Collections.singletonList(key),String.valueOf(windowStart),String.valueOf(max),String.valueOf(now),String.valueOf(windowSec1));returnLong.valueOf(1).equals(result);}privateStringbuildKey(RateLimitrateLimit){Stringprefixrate:;switch(rateLimit.type()){caseip:HttpServletRequestrequest((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();returnprefixip:getIp(request);caseapi:returnprefixapi:Stream.of(Thread.currentThread().getStackTrace()).filter(s-s.getMethodName().contains($)).findFirst().orElse(newStackTraceElement(,,,0)).getMethodName();default:// type userreturnprefixuser:StpUtil.getLoginIdAsString();}}publicstaticStringgetIp(HttpServletRequestrequest){Stringiprequest.getHeader(X-Forwarded-For);if(ipnull||ip.isEmpty())iprequest.getHeader(X-Real-IP);if(ipnull||ip.isEmpty())iprequest.getRemoteAddr();if(ip!nullip.contains(,))ipip.split(,)[0].trim();returnip;}}三、使用RestControllerRequestMapping(/api)publicclassTestController{GetMapping(/test)RateLimit(keytest,typeip,max5,window10)publicResultVO?test(){returnResultVO.success(成功);}PostMapping(/seckill/{productId})RateLimit(typeuser,max1,window10)publicResultVO?seckill(PathVariableLongproductId){returnResultVO.success(秒杀成功);}}四、异常处理RestControllerAdvicepublicclassRateLimitExceptionHandler{ExceptionHandler(BusinessException.class)publicResultVO?handleBusiness(BusinessExceptione){if(e.getMessage().contains(请求太频繁)){returnResultVO.error(429,e.getMessage());}returnResultVO.error(e.getCode(),e.getMessage());}}五、压测效果# 模拟 20 个并发请求foriin{1..20};docurl-XGET http://localhost:9090/api/testdone# 正常响应{code:200,message:成功}# 被限响应{code:429,message:请求太频繁请稍后重试} 觉得有用的话点赞 关注【张老师技术栈】吧每周更新 Java/Python/MySQL 实战干货不让你白来。