首先限流,其实解决方案有很多,比如通过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 {
@Resource
private RateLimiterService accessLimitService;
private Logger logger = LoggerFactory.getLogger(RateLimiterInterceptor.class);
@Override
public 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;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public 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 {
@Resource
private RateLimiterInterceptor rateLimiterInterceptor;
@Override
public 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 {
//限流key
String 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){
// 获取请求ip
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
String clientIp =ServletUtil.getClientIP(request);
stringBuffer.append(clientIP).append("-");
}else if(rateLimiter.limitType() == LimitType.CLUSTER){
//获取客户端实例id
stringBuffer.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");
}
}