本章主要介绍redis对接口进行限流访问,当接口在高并发的情况下,会对我们的服务器造成一定影响,可通过次案例提供轻微的解决方案
应用场景:用户注册,电商秒杀接口,高并发 ....
实现方案:自定义注解+拦截器+Redis实现限流 (单体和分布式均适用,全局限流)
可根据自己需求通过本文也可直接应用到项目中
Demo:https://gitee.com/Audis/ccl-coding-sso.git
1.创建自定义注解
package com.ccl.coding.sso.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Inherited
@Documented
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {
//标识限制次数
int limit() default 5;
//标识时间
int sec() default 5;
}
2.创建拦截器并通过反射对接口的访问进行限制
package com.ccl.coding.sso.interceptor;
import com.ccl.coding.sso.annotation.AccessLimit;
import com.ccl.coding.sso.exception.AccessLimitException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;
/**
* 创建拦截器类实现HandlerInterceptor 接口
*/
@Slf4j
@Component
public class AccessLimitInterceptor extends HandlerInterceptorAdapter {
@Autowired
private RedisTemplate redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handle)
throws Exception {
//判断handle是否为方法handle
if (handle instanceof HandlerMethod) {
//是的话转为HandleMethod
HandlerMethod handleMethod = (HandlerMethod) handle;
//获取方法
Method method = handleMethod.getMethod();
//判断该方法上是否有自定义注解@AccessLimit
if (!method.isAnnotationPresent(AccessLimit.class)) {
//不存在该注解则放行
return super.preHandle(request, response, handle);
}
//获取自定义注解对象
AccessLimit accessLimit = method.getAnnotation(AccessLimit.class);
if (accessLimit == null) {
//放行
return super.preHandle(request, response, handle);
}
//获取注解属性值
int limit = accessLimit.limit();
int sec = accessLimit.sec();
//可将用户的id加上 限制每一个用户只能访问几次
String key = request.getRequestURI();
log.info("key : {}", key);
//从redis中获取记录
Integer maxLimit = (Integer) redisTemplate.opsForValue().get(key);
if (maxLimit == null) {
//第一次,计数器设置为1,设置redis过期时间
redisTemplate.opsForValue().set(key, 1, sec, TimeUnit.SECONDS);
} else if (maxLimit < limit) {
//计数器加1
redisTemplate.opsForValue().set(key, maxLimit + 1, sec, TimeUnit.SECONDS);
} else {
throw new AccessLimitException("过度访问资源,客官休息一会哦!");
}
}
return super.preHandle(request, response, handle);
}
}
对访问的资源进行拦截
package com.ccl.coding.sso.confing;
import com.ccl.coding.sso.interceptor.AccessLimitInterceptor;
import com.ccl.coding.sso.interceptor.AuthCheckInterceptor;
import com.ccl.coding.sso.interceptor.RequestContextInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* @author CHANG
* @date 2019/7/28 11:01
*/
@Configuration
public class InterceptorConfiguration implements WebMvcConfigurer {
@Autowired
private AuthCheckInterceptor authCheckInterceptor;
@Autowired
private AccessLimitInterceptor accessLimitInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new RequestContextInterceptor()).addPathPatterns("/**");
registry.addInterceptor(authCheckInterceptor).addPathPatterns("/**");
registry.addInterceptor(accessLimitInterceptor).addPathPatterns("/**");
}
}
3.自定义异常
package com.ccl.coding.sso.exception;
/**
* @author CHANG
* @date 2019/7/27 18:06
*/
public class AccessLimitException extends RuntimeException {
protected AccessLimitException() {
super();
}
public AccessLimitException(String message) {
super(message);
}
private AccessLimitException(String message, Throwable ex) {
super(message, ex);
}
public AccessLimitException(Throwable arg0) {
super(arg0);
}
}
异常转换机制
package com.ccl.coding.sso.exception;
import com.ccl.coding.sso.domain.protocol.ApiResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
/**
* @author CHANG
* @date 2019/7/28 11:54
*/
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(value = TokenExpireException.class)
@ResponseBody
public ApiResponse tokenExpireExceptionHandler(HttpServletRequest req, Exception ex) throws Exception {
return ApiResponse.build().tokenError(ex.getMessage());
}
@ExceptionHandler(value = AccessLimitException.class)
@ResponseBody
public ApiResponse accessLimitExceptionHandler(HttpServletRequest req, Exception ex) throws Exception {
return ApiResponse.build().accessLimitError(ex.getMessage());
}
}