目录
- 目的
- 分类
- 限流算法
- 计数器
- 令牌桶
- 漏桶
- 比较
- 实例
1. 目的
通过对并发访问和请求进行限速或者一个时间窗口内的请求进行限速来保护系统的可用性,一旦达到限制速率就可以拒绝服务(友好定向到错误页或告知资源没有了),排队或者等待(比如秒杀,评论,下单),降级(返回默认数据)
2. 分类
- 限制总并发数 (数据库连接池,线程池)
- 限制瞬时并发数 (如Nginx的limit_conn模块,用来限制瞬时并发连接数)
- 限制时间窗口内的平均速率 (如Guava的RateLimiter,nginx的limit_req模块,限制每秒平均速率 )
- 其他 (限制远程接口调用速率,限制MQ消费速率,另外,还可以根据网络连接数,网络流量,CPU或内存负载等来限流)
3. 限流算法
常见的限流算法有:计数器、令牌桶、漏桶。
3.1 计数器
描述:
采用计数器实现限流有点简单粗暴,一般我们会限制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。
java实现
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @program: study
* @description: 计数器限流
* @author: chengqj
* @create: 2018-10-19 17:39
**/
public class CountRateLimiter {
private static AtomicInteger count = new AtomicInteger(0);
public static void exec() {
if (count.get() >= 100) {
System.out.println("请求用户过多,请稍后在试!");
} else {
count.incrementAndGet();
try {
//处理核心逻辑
System.out.println("处理核心逻辑");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
} finally {
count.decrementAndGet();
}
}
}
public static void main(String[] args) throws IOException {
for (int i=0;i<200;i++){
new Thread(()->{exec();}).start();
}
System.in.read();
}
3.2 令牌桶算法
描述:
- 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌
- 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝
- 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上
- 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)
3.3 漏桶算法
描述:
- 一个固定容量的漏桶,按照常量固定速率流出水滴
- 如果桶是空的,则不需流出水滴
- 可以以任意速率流入水滴到漏桶
- 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的)
3.4 比较:
- 令牌桶可以接受突发性请求,入口固定,出口可以临时突然变大
- 漏桶算法,入口可以临时变大,出口固定,就是超过限制的请求会堆积
4. 实例
Java实现令牌桶和漏桶算法
import com.google.common.util.concurrent.RateLimiter;
import java.util.concurrent.TimeUnit;
/**
* @program: study
* @description: 限流算法
* @author: chengqj
* @create: 2018-10-19 17:40
**/
public class GuavaRateLimiterDemo {
private int qps;
private int countOfReq;
private RateLimiter rateLimiter;
/**
* 创建漏桶
* @param qps 平均gps
* @param countOfReq 总共请求数
* @return
*/
public GuavaRateLimiterDemo createLeakyBucket(int qps, int countOfReq){
this.countOfReq = countOfReq;
rateLimiter=RateLimiter.create(qps,0,TimeUnit.MILLISECONDS);
return this;
}
/**
* 创建令牌
* @param qps 平均gps
* @param countOfReq 总共请求数
* @return
*/
public GuavaRateLimiterDemo createTokenBucket(int qps, int countOfReq){
this.countOfReq = countOfReq;
rateLimiter=RateLimiter.create(qps);
return this;
}
/**
* 使用限流算法进行控制的方法
*/
private void processRequest(){
long start=System.currentTimeMillis();
for(int i=0;i<countOfReq;i++){
rateLimiter.acquire();//获取令牌 拿不到令牌就会等待
}
long end=System.currentTimeMillis()-start;
System.out.println("处理的请求数量:"+countOfReq+"," +
""+"耗时:"+end+",qps:"+rateLimiter.getRate()+",实际qps:"+
Math.ceil(countOfReq/(end/1000.00)));
}
public void doProcessor() throws InterruptedException {
for(int i=0;i<5;i++){
TimeUnit.SECONDS.sleep(2);
processRequest();
}
}
}
测试
GuavaRateLimiterDemo tokenBucket = new GuavaRateLimiterDemo();
//测试令牌桶
tokenBucket.createTokenBucket(50,100).doProcessor();
//测试漏桶算法
tokenBucket.createLeakyBucket(50,100).doProcessor();