首先限流,其实解决方案有很多,比如通过nginx配置,通过gateway网关进行限流,比如Spring Cloud GateWay整合熔断器实现限流
但是以上都是全局的,如何灵活的针对某些接口进行不同级别的限流呢?
方案一:令牌桶
如何需要用到这个方案需要先了解漏桶算法和令牌桶算法的区别
引入依赖
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>18.0</version>
</dependency>
代码块
package com.sb.rateLimiter.service;import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Service;@Service
public class RateLimiterService {/*** 每秒只发出5个令牌*/RateLimiter rateLimiter = RateLimiter.create(5.0);/*** 尝试获取令牌* @return*/public boolean tryAcquire(){return rateLimiter.tryAcquire();}
}
自定义拦截器,在拦截器中实现限流
package com.sb.rateLimiter.interceptor;
import com.sb.rateLimiter.annotation.RateLimiterAnnotation;
import com.sb.rateLimiter.service.RateLimiterService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class RateLimiterInterceptor implements HandlerInterceptor {@Resourceprivate RateLimiterService accessLimitService;private Logger logger = LoggerFactory.getLogger(RateLimiterInterceptor.class);@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}HandlerMethod handlerMethod = (HandlerMethod) handler;RateLimiterAnnotation rateLimiterAnnotation = handlerMethod.getMethod().getAnnotation(RateLimiterAnnotation.class);if(rateLimiterAnnotation == null) {return true;}if (!accessLimitService.tryAcquire()) {logger.info("限流中......");return false;}logger.info("请求成功");return true;}@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}
实现 WebMvcConfigurer 添加自定义拦截器
package com.sb.rateLimiter.config;
import com.sb.rateLimiter.interceptor.RateLimiterInterceptor;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;@SpringBootConfiguration
public class WebConfiguration implements WebMvcConfigurer {@Resourceprivate RateLimiterInterceptor rateLimiterInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(rateLimiterInterceptor).addPathPatterns("/**");}
}
自定义限流注解RateLimiterAnnotation
package com.sb.rateLimiter.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RateLimiterAnnotation {int rateLimiterNumber() default 1;
}
验证
package com.sb.rateLimiter.controller;
import com.sb.rateLimiter.annotation.RateLimiterAnnotation;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/api")
public class RateLimiterController {@RateLimiterAnnotation@RequestMapping(value = "/rateLimiterTest", method = RequestMethod.GET)public void rateLimiterTest() throws Exception {//业务逻辑;}
方案二:RedissonUtils.rateLimiter
这个方案其实和上面差不多都是通过一个注解实现接口限流的,只不过相较于上面的业务处理更加完善
自定义注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RateLimiter {//限流keyString key() default "rate_limit:";//限流时间,单位秒int time() default 60;//限流次数int count()default 100;//限流类型LimitType limitType() default LimitType.DEFAULT
}
限流类型
public enum LimitType {// 默认策略全局限流DEFAULT,//根据请求者IP进行限流IP,//实例限流(集群多后端实例)CLUSTER
切面
@Befone("annotation(rateLimiter)")
public void doBefore(JoinPoint point, RateLimiter rateLimiter)//获取注解上的时间和计数int time = rateLimiter.time();int count = rateLimiter.count();//获取组合键String combineKey=this.getCombineKey(rateLimiter, point);try {//确定速率类型,这个枚举是redisson自带的枚举类RateType rateType = RateType.0VERALL;if(rateLimiter.LimiterType() == LimitType .CLUSTER){rateType = RateType.PRE_CLIENT}// 调用RedissonUtils.rateLimiter方法进行限流处理Long number = RedissonUtils.rateLimiter(combineKey, rateType, count, time);if(number==-1){throw new RuntimeException("请求过于频繁~");};log.info("限制令牌 => {},剩余令牌 => {},緩存key =>'{}'"count,number,combineKey);}catch(Exception e){throw new RnutimeException ("服务器限流异常,请稍后重试")}
}
public String getCombineKey(RateLimiter rateLimiter, JoinPoint point){StringBuilder stringBuffer = new StringBuilder(rateLimiter.key());if(rateLimiter.limitType()== LimitType.IP){// 获取请求ipServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();String clientIp =ServletUtil.getClientIP(request);stringBuffer.append(clientIP).append("-");}else if(rateLimiter.limitType() == LimitType.CLUSTER){//获取客户端实例idstringBuffer.append(RedissonUtils.getClient().getId()).append("-");MethodSignature signature =(MethodSignature)point.getSignature();Method method = signature.getMethod();Class<?>targetClass = method.getDeclaringClass();stringBuffer.append(targetClass.getName()).append(" ").append(method.getName());return stringBuffer.toString();}
}
RedissonUtils的限流方法
/*** redis 限流管理器* @author xxx* @version 1.0* @date xxx*/@Service
public class RedissonUtils{/*** 限流操作** @param key 限流key* @param rateType 限流类型* @param rate 速率* @param rateInterval 速率间隔*/public void rateLimiter(String key, Ratetype rateType,int ratel, int rateInterval) {// 创建限流器RRateLimiter rateLimiter = redisson.getRateLimiter(key);rateLimiter.trySetRate(rateType, rate,rateInterval,RateIntervalUtil.SECONDES);// 获取令牌if (rateLimiter.tryAcquire()){return rateLimiter.availablePermits();} else {return -1 ;}}
}
使用
@RestController
public class TestController {@RateLimiter(time=5,count =2)@GetMapping("/testLimit")public ResultV0<String> test(string name){return ResultV0.ok( data:"success");}
}