springboot使用redis限制并发请求、限流

作者: ʘᴗʘ发布时间:2021-10-31 12:00 浏览量:295 点赞:233 售价:0

应用场景

生产环境中有很多业务场景需要限制API或网站的并发请求书、流量,网上也有很多成熟的框架、算法,但对于小型应用来说,可能引入框架、算法的代码比自己的业务代码都多了,得不偿失,性价比不高。所以简单的限流需求我们完全可以自己通过很少的代码实现。

原理介绍

不讲算法,直接大白话描述:主要是利用redis的incr方法,给每个终端(通过IP判断、user-agent判断等等)设置一个redis的key,过期时间为T,然后每次请求就是用incr加1,并重设这个key的过期时间为T。当这个key对应的value大于L后,判定超过了限流阈值,返回错误。其中,T是时间,L是最大流量,一句话描述就是在T时间内,最多只能访问L次。

限流的代码封装好后,通常是在springboot的Interceptor拦截器中调用。

Redis限流步骤、代码

一、创建自定义注解

创建一个自定义注解,属性有:限流时间窗口、限流次数。当然,你也可以根据自己的业务,定义其他属性,比如限流类型(根据IP限流、根据user-agent限流、根据user限流等等)。代码如下:

package com.coderbbb.blogv2.config.anno;

import com.coderbbb.blogv2.config.mj.AccessLimitType;

import java.lang.annotation.*;

/**
 * @Author: longge93
 * @Date: 2021/6/5 22:41
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {

    /**
     * 时间窗口,单位秒
     * @return
     */
    int time() default 1;

    /**
     * 时间窗口内允许访问的最大次数
     * @return
     */
    int times() default 10;

}

二、创建限流service

在Springboot项目中新建一个AccessLimitService的service,然后编写上面描述的限流算法。其中,使用到的RedisService是作者自己封装的springboot操作redis的库,代码在文章《Springboot整合Redis和redis常用操作演示》中。

package com.coderbbb.blogv2.service;

import com.coderbbb.blogv2.config.BaseCommonConfig;
import com.coderbbb.blogv2.config.BaseRedisKeyConfig;
import com.coderbbb.blogv2.config.anno.AccessLimit;
import com.coderbbb.blogv2.config.mj.AccessLimitType;
import com.coderbbb.blogv2.database.dos.UserDO;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Locale;

/**
 * @author longge93
 */
@Service
public class AccessLimitService {

    @Autowired
    private RedisService redisService;

    public void check(HttpServletRequest request, HandlerMethod handler) throws Exception {

        AccessLimit accessLimit = handler.getMethodAnnotation(AccessLimit.class);
        if (accessLimit == null) {
            return;
        }


        String ip = (String) request.getAttribute("ip");

        String accessLimitKey = BaseRedisKeyConfig.ACCESS_LIMIT_KEY;
        if (accessLimit.limitType() == AccessLimitType.LIMIT_BY_URL) {
            accessLimitKey = accessLimitKey + DigestUtils.md5Hex(ip + request.getRequestURI());
        } else {
            accessLimitKey = accessLimitKey + DigestUtils.md5Hex(ip);
        }

        String v = redisService.getString(accessLimitKey);
        redisService.incr(accessLimitKey);
        if (v == null) {
            redisService.setExpire(accessLimitKey, accessLimit.time());
            return;
        }
        int vInt = Integer.parseInt(v);

        if (vInt >= accessLimit.times()) {
            //开始限流
            throw new Exception("access limit exceed#" + request.getHeader("X-Forwarded-For"));
        }
    }
}

上面的限流算法中,是根据用户终端请求IP来限流的,你可以改造代码,来根据其他终端特征来限流。

三、在Interceptor拦截器中调用限流service

完成上面的步骤后,我们只要在自己的Interceptor拦截器中调用上面的限流service即可,如果流量、并发超过限制,限流service会抛出异常,在拦截器中捕获异常,给用户返回对应的错误信息即可。代码如下:

@Component
public class BaseInterceptor implements HandlerInterceptor {

   
    @Autowired
    private AccessLimitService accessLimitService;
  
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

  
        //真实IP
        String ip = request.getHeader("X-Forwarded-For");
        if (StringUtils.isEmpty(ip)) {
            ip = "127.0.0.1";
        }

        request.setAttribute("ip", ip);

   
        if ((handler instanceof HandlerMethod)) {

            //限流
            try {
                accessLimitService.check(request, (HandlerMethod) handler);
            } catch (Exception e) {
                logger.error(e.getMessage() + "#" + urlFull);
                response.setStatus(500);
                return false;
            }

        }


        return true;
    }
}

其中,自定义的拦截器需要注册到springboot中才会正常工作,关于如何注册拦截器到springboot中,可以参考下图中的代码。

springboot使用redis限制并发请求、限流

四、使用redis限流注解实现限流

在你需要限流的springboot controller method上,标记限流注解,然后多次调用该method,测试能否正常限流。使用代码截图如下:

springboot使用redis限制并发请求、限流

版权声明:《springboot使用redis限制并发请求、限流》为CoderBBB作者「ʘᴗʘ」的原创文章,转载请附上原文出处链接及本声明。

原文链接:https://www.coderbbb.com/articles/29

其它推荐: