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.结果验证

 请求两次之后会抛出自定的异常

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