关于限流神器之-Guava RateLimiter 实战

最近在做一个功能,需要对并发量大的路径设置限流,防止因为并发太大,导致服务器宕机,老大给我在网上搜了限流神器之-Guava RateLimiter 实战,我进去看了下https://www.cnblogs.com/guxiong/p/11024289.html,我们项目是springboot项目,结合前面大佬的博客,我在自己项目上实现了限流,接下来分享下,我实现过程中遇到的问题

这个是我改了以后的代码,源代码请从上面链接去原博客那里看

首先按照大佬的方式,创建了四个类,限流实体类

public class RateLimit {
    private String rateLimitId;
    /**
     * 限流路径,支持通配符,示例 /user/**
     */
    private String limitPath;
    /**
     * 每秒限流频率
     */
    private Integer permitsPerSecond;
    /**
     * 限流等待超时时间,单位s
     */
    private Integer permitsTimeOut;
    /**
     * 排序
     */private Integer orderNo;
    /**
     * 最大线程数
     */
    private Integer maxThread;
    /**
     * 创建时间
     */
    private Date gmtCreate;
   //get、set略
}

封装了一个限流信息类RateLimitInfo(我在创建这个类的时候,一直不明白RateLimitVo是个什么,以为还要再创建一个vo类,但是阅读后面的代码后,发现这个RateLimitVo就是RateLimit,这里替换成RateLimit后面的代码都是能执行的,可能博主当时有创建了一个vo类来区分什么东西吧,这里不太懂博主的意思,至少换成RateLimit类,后面执行是没问题的,而且博主的博客中也没出现RateLimitVo类)

/**
 * @描述: 限流信息
 */
public class RateLimitInfo {

    private RateLimiter rateLimiter;

    private RateLimit rateLimit;

    private long lastUpdateTime;

    public RateLimitInfo(RateLimiter rateLimiter, RateLimit rateLimit, long lastUpdateTime) {
        this.rateLimiter = rateLimiter;
        this.rateLimit = rateLimit;
        this.lastUpdateTime = lastUpdateTime;
    }
    //get、set略
}

 定义限流策略RateLimitStrategist(这里,原博主是从数据库查询配置的限流路径和限流配置,因为我的项目只需要配置一个路径,所有将从数据库查询的代码注释,换成了在初始化时手动新建一个实体类,并且设置好超时时间,路径名,最大线程数,和每秒限流数)其实可以同步注释,线面的每5分钟同步一次数据库配置的限流配置,但是我没注释,准备以后换成数据库配置。

import com.google.common.util.concurrent.RateLimiter;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class RateLimitStrategist {

    private PathMatcher pathMatcher = new AntPathMatcher();

    private final Map<String, RateLimitInfo> limiterMap = new LinkedHashMap<>();

    private final Map<String, Semaphore> threadMap = new LinkedHashMap<>();

    public RateLimitStrategist(){
        init();
    }

    /**
     * 更新频率,意为后台配置路径后5分钟生效
     */
    private static final long UPDATE_RATE = 1000*60*5;

    private long lastUpdateTime = 0;

    public void init() {
        limiterMap.clear();
        threadMap.clear();
        RateLimit limit = new RateLimit(); // 因为只限流一个路径,没从数据库中查,初始化时,手动加入
        limit.setLimitPath("/api/app/moyujiafei/abc");
        limit.setMaxThread(10);
        limit.setPermitsTimeOut(5);
        limit.setPermitsPerSecond(10);

       // List<RateLimit> rateLimitVos = rateLimitManager.findListForPriority(); //查询数据库中配置的路径信息,需要自己实现
        List<RateLimit> rateLimitVos = new ArrayList<>();
        rateLimitVos.add(limit);
        if(!CollectionUtils.isNotEmpty(rateLimitVos)) {
            return;
        }
        for (RateLimit rateLimit : rateLimitVos) {
            RateLimiter rateLimiter = RateLimiter.create(rateLimit.getPermitsPerSecond());
            limiterMap.put(rateLimit.getLimitPath(), new RateLimitInfo(rateLimiter, rateLimit, System.currentTimeMillis()));
            threadMap.put(rateLimit.getLimitPath(), new Semaphore(rateLimit.getMaxThread(), true));
        }
        lastUpdateTime = System.currentTimeMillis();
    }

    public boolean tryAcquire(String requestUri) {
        //目前设置5分钟更新一次
        if(System.currentTimeMillis() - lastUpdateTime > UPDATE_RATE) {
            synchronized (this) {
                if(System.currentTimeMillis() - lastUpdateTime > UPDATE_RATE) {
                    init();
                }
            }
        }

        for (Map.Entry<String, RateLimitInfo> entry : limiterMap.entrySet()) {
            if(!pathMatcher.match(entry.getKey(), requestUri)) {
                continue;
            }
            RateLimitInfo rateLimitInfo = entry.getValue();
            RateLimit rateLimitVo = rateLimitInfo.getRateLimit();
            RateLimiter rateLimiter = rateLimitInfo.getRateLimiter();
            boolean concurrentFlag = rateLimiter.tryAcquire(1, rateLimitVo.getPermitsTimeOut(), TimeUnit.SECONDS);
            if(!concurrentFlag) { //验证失败,直接返回
                return concurrentFlag;
            } else {
                if(threadMap.get(requestUri).availablePermits() != 0) { //当前路径对应剩余可执行线程数不为0
                    try {
                        //申请可执行线程
                        threadMap.get(requestUri).acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return true;
                } else {
                    return false;
                }
            }
        }
        return true;
    }

    public void setLastUpdateTime(long lastUpdateTime) {
        this.lastUpdateTime = lastUpdateTime;
    }

    /**
     * 释放路径对应的线程数
     * @param requestURI
     */
    public void releaseSemaphore(String requestURI) {
        if(null != threadMap.get(requestURI)) {
            threadMap.get(requestURI).release();
        }
    }
}

定义拦截器RateLimitFilter,在拦截器中调用限流策略,这里我将创建RateLimitStrategist的方式改了,原文是通过实例工厂来实现的,博客并未给出工厂的代码,看起来像是通过反射获得的,这里为了方便使用单例模式直接new出对象(因为使用的springboot,所以配置filter,我是直接通过注解的方式来的,有关xml配置可以去看原博主的博客,或者自己搜索)同时使用 @WebFilter 和 @Component,Spring Boot将会自动注册过滤器,不管写成什么拦截地址,过滤器注册的地址都是 “/*”(这个有点坑,目前不知道怎么解决)





import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @描述: 限流过滤器,配置后生效
 */
@Component
@Order //过滤器加载的顺序 默认Integer.MAXVALUE
@WebFilter(urlPatterns = "/api/app/moyujiafei/*",filterName = "wholeFilter")
public class RateLimitFilter implements Filter {

    private RateLimitStrategist rateLimitStrategist;

    private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitFilter.class);

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        if(rateLimitStrategist == null) {
            rateLimitStrategist = new RateLimitStrategist();
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        if(rateLimitStrategist == null) {
            rateLimitStrategist = new RateLimitStrategist();
        }
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        String requestURI = req.getRequestURI();
        String contextPath = req.getContextPath();
        if(StringUtils.isNotBlank(contextPath)) {
            requestURI = StringUtils.substring(requestURI, contextPath.length());
        }
        if(!rateLimitStrategist.tryAcquire(requestURI)) {
            res.setContentType("text/html;charset=UTF-8");
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            response.getWriter().write("当前服务器繁忙,请稍后再试!");
            LOGGER.info(requestURI + "路径请求服务器繁忙,请稍后再试");
        } else {
            try {
                chain.doFilter(request, response);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                rateLimitStrategist.releaseSemaphore(requestURI);
            }
        }

    }

    @Override
    public void destroy() {

    }
}

原博主的代码写的很好,只用根据自身情况修改一下限流策略里面的代码就可以了,我现在只是在使用时整理了一下问题,这个东西还是值得后面花时间学习的!

我导入的包如下

        <dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>18.0</version>
		</dependency>

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值