springboot 拦截器 + Guava 开源工具类之 RateLimiter 实现限流

1. 限流

每个服务器都有访问上限,当访问的并发量大过服务器的承受范围的时候,我们就需要考虑限流的方式 确保系统挂掉。

2.限流算法

常见的限流算法有两种:漏桶算法和令牌桶算法。

2.1 漏桶算法

算法思路: 将请求先加入到桶里,漏桶按照一定的速度将发出 处理请求,当请求并发量过大时候,超出桶的容量就会拒绝请求。
在这里插入图片描述

2.2 令牌桶

算法思路: 系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token ,如果桶已经满了就不再加了.新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务.
在这里插入图片描述

RateLimiter

Google开源工具包Guava提供了限流工具类RateLimiter,该类基于令牌桶算法(Token Bucket)来完成限流,非常易于使用.RateLimiter经常用于限制对一些物理资源或者逻辑资源的访问速率.它支持两种获取permits接口,一种是如果拿不到立刻返回false,一种会阻塞等待一段时间看能不能拿到.

RateLimiter主要接口

ateLimiter其实是一个abstract类,但是它提供了几个static方法用于创建RateLimiter:

创建限流器

/**
* 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
* 当请求到来的速度超过了permitsPerSecond,保证每秒只处理permitsPerSecond个请求
* 当这个RateLimiter使用不足(即请求到来速度小于permitsPerSecond),会囤积最多permitsPerSecond个请求
*/
public static RateLimiter create(double permitsPerSecond);

/**
* 创建一个稳定输出令牌的RateLimiter,保证了平均每秒不超过permitsPerSecond个请求
* 还包含一个热身期(warmup period),热身期内,RateLimiter会平滑的将其释放令牌的速率加大,直到起达到最大速率
* 同样,如果RateLimiter在热身期没有足够的请求(unused),则起速率会逐渐降低到冷却状态
* 
* 设计这个的意图是为了满足那种资源提供方需要热身时间,而不是每次访问都能提供稳定速率的服务的情况(比如带缓存服务,需要定期刷新缓存的)
* 参数warmupPeriod和unit决定了其从冷却状态到达最大速率的时间
*/
public static RateLimiter create(double permitsPerSecond, long warmupPeriod, TimeUnit unit);
获取令牌

阻塞
~~java
不带参数表示获取一个令牌.如果没有令牌则一直等待
public double acquire();

public double acquire(int permits);

//不阻塞 尝试获取令牌
~~~java

public boolean tryAcquire();
//尝试获取一个令牌,立即返回
public boolean tryAcquire(int permits);
public boolean tryAcquire(long timeout, TimeUnit unit);
//尝试获取permits个令牌,带超时时间
public boolean tryAcquire(int permits, long timeout, TimeUnit unit);

springboot 拦截器+RateLimiter 实现限流 代码

package com.example.demo.Advice.LimitInterceptor;


import com.example.demo.exception.APIException;
import com.google.common.util.concurrent.RateLimiter;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;

/**
 * Guava 限流器
 * @author linjunbo
 *
 */
@Component
public class LimitInterceptor extends HandlerInterceptorAdapter {
	
	public enum LimitType {
		DROP,	//丢弃
		WAIT	//等待
	}
	
	/**
	 * Guava 开源工具限流工具类
	 * 限流器
	 */
	private RateLimiter limiter;
	
	/**
	 * 限流方式
	 */
	private LimitType limitType = LimitType.DROP;
	
	public LimitInterceptor() {
		this.limiter = RateLimiter.create(1);
	}
	
	/**
	 * @param tps	限流(每秒处理量)
	 * @param limitType
	 */
	public LimitInterceptor(int tps, LimitInterceptor.LimitType limitType) {
		this.limiter = RateLimiter.create(tps);
		this.limitType = limitType;
	}

	/**
	 * @param permitsPerSecond	每秒新增的令牌数
	 * @param limitType	限流类型
	 */
	public LimitInterceptor(double permitsPerSecond, LimitInterceptor.LimitType limitType) {
		this.limiter = RateLimiter.create(permitsPerSecond, 1000, TimeUnit.MILLISECONDS);
		this.limitType = limitType;
	}
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
	    if (limitType.equals(LimitType.DROP)) {
			//尝试获取一个令牌,立即返回
		    if (limiter.tryAcquire()) {

		        return super.preHandle(request, response, handler);
		    }
	    }
//	    else {
//	    	//获取令牌 如果没有令牌则一直等待,返回等待的时间(单位为秒),没有被限流则直接返回0.0:
//			double count = limiter.acquire();
//	    	return super.preHandle(request, response, handler);
//	    }
	    throw new APIException(500,"服务器达到请求达到上限,限流生效");//达到限流后,往页面提示的错误信息。
	}
	
	@Override
	public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
		super.postHandle(request, response, handler, modelAndView);
	}
	
	@Override
	public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
		super.afterCompletion(request, response, handler, ex);
	}
	
	public RateLimiter getLimiter() {
	    return limiter;
	}
	 
	public void setLimiter(RateLimiter limiter) {
	    this.limiter = limiter;
	}

}

@Component
public class LimitFilter implements WebMvcConfigurer {


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    //每秒只能接收100个请求
        registry.addInterceptor(new LimitInterceptor(100,LimitInterceptor.LimitType.DROP))
                .addPathPatterns("/**")
                //忽略拦截
        .excludePathPatterns("/login");

    }
}

使用JMETER 测试

结果如图
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值