Redis(三) Java集成Redis之接口限流-解決高併發刷單

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

 4.結果驗證

 請求兩次之後會拋出自定的異常

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章