本章主要介紹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());
}
}