限流从概念到实现

并发数限流
并发数限流限制的是同一时刻的并发数,所以不考虑线程安全的话,我们只要用一个int变量就能实现,伪代码如下:

int maxRequest=100;
int nowRequest=0;

public void request(){
if(nowRequest>=maxRequest){
return ;
}
nowRequest++;
//调用接口
try{
invokeXXX();
}finally{
nowRequest--;
}
}
显然,上述实现会有线程安全的问题,最直接的做法是加锁:

int maxRequest=100;
int nowRequest=0;

public void request(){
if(nowRequest>=maxRequest){
return ;
}
synchronized(this){
if(nowRequest>=maxRequest){
return ;
}
nowRequest++;
}

//调用接口
try{
     invokeXXX();
}finally{
    synchronized(this){
        nowRequest--;
    }
}

}
当然也可以用AtomicInteger实现:

int maxRequest=100;
AtomicInteger nowRequest=new AtomicInteger(0);

public void request(){
for(;;){
int currentReq=nowRequest.get();
if(currentReq>=maxRequest){
return;
}
if(nowRequest.compareAndSet(currentReq,currentReq+1)){
break;
}
}

//调用接口
try{
     invokeXXX();
}finally{
    nowRequest.decrementAndGet();
}

}
熟悉JDK并发包的同学会说干嘛这么麻烦,这不就是信号量(Semaphore)做的事情吗? 对的,其实最简单的方法就是用信号量来实现:

int maxRequest=100;
Semaphore reqSemaphore = new Semaphore(maxRequest);

public void request(){
if(!reqSemaphore.tryAcquire()){
return ;
}

//调用接口
try{
     invokeXXX();
}finally{
   reqSemaphore.release();
}

}
条条大路通罗马,并发数限流比较简单,一般来说用信号量就好。

QPS限流
QPS限流限制的是一段时间内(一般指1秒)的请求个数。

计数器法
最简单的做法用一个int型的count变量做计数器:请求前计数器+1,如超过阈值并且与第一个请求的间隔还在1s内,则限流。

伪代码如下:

int maxQps=100;
int count;
long timeStamp=System.currentTimeMillis();
long interval=1000;

public synchronized boolean grant(){
long now=System.currentTimeMillis();
if(now<timeStamp+interval){
count++;
return count<maxQps;
}else{
timeStamp=now;
count=1;
return true;
}
}
该种方法实现起来很简单,但其实是有临界问题的,假如在第一秒的后500ms来了100个请求,第2秒的前500ms来了100个请求,那在这1秒内其实最大QPS为200。如下图:

计数器法会有临界问题,主要还是统计的精度太低,这点可以通过滑动窗口算法解决

滑动窗口
我们用一个长度为10的数组表示1秒内的QPS请求,数组每个元素对应了相应100ms内的请求数。用一个 sum 变量代码当前1s的请求数。同时每隔100ms将淘汰过期的值。

伪代码如下:

int maxQps=100;
AtomicInteger[] count=new AtomicInteger[10];
long timeStamp=System.currentTimeMillis();
long interval=1000;
AtomicInteger sum;
volatile int index;

public void init(){
for(int i=0;i<count.length;i++){
count[i]=new AtomicInteger(0);
}
sum=new AtomicInteger(0);
}

public synchronized boolean grant(){
count[index].incrementAndGet();
return sum.incrementAndGet()<maxQps;
}

//每100ms执行一次
public void run(){
index=(index+1)%count.length;
int val=count[index].getAndSet(0);
sum.addAndGet(-val);
}
滑动窗口的窗口越小,则精度越高,相应的资源消耗也更高。

漏桶算法
漏桶算法思路是,有一个固定大小的桶,水(请求)忽快忽慢的进入到漏桶里,漏桶以一定的速度出水。当桶满了之后会发生溢出。

在 维基百科 上可以看到,漏桶算法有两种实现,一种是 as a meter ,另一种是 as a queue 。 网上大多数文章都没有提到其有两种实现,且对这两种概念混乱。

As a meter
第一种实现是和令牌桶等价的,只是表述角度不同。

伪代码如下:

long timeStamp=System.currentTimeMillis();//上一次调用grant的时间
int bucketSize=100;//桶大小
int rate=10;//每ms流出多少请求
int count;//目前的水量

public synchronized boolean grant(){
long now = System.currentTimeMillis();
if(now>timeStamp){
count = Math.max(0,count-(now-timeStamp)*rate);
timeStamp = now;
}

if(count+1<=bucketSize){
    count++;
    return true;
}else{
    return false;
}

}
该种实现允许一段时间内的突发流量,比如初始时桶中没有水,这时1ms内来了100个请求,这100个请求是不会被限流的,但之后每ms最多只能接受10个请求(比如下1ms又来了100个请求,那其中90个请求是会被限流的)。

其达到的效果和令牌桶一样。
As a queue
第二种实现是用一个队列实现,当请求到来时如果队列没满则加入到队列中,否则拒绝掉新的请求。同时会以恒定的速率从队列中取出请求执行。

伪代码如下:

Queue<Request> queue=new LinkedBlockingQueue(100);
int gap;
int rate;

public synchronized boolean grant(Request req){
if(!queue.offer(req)){return false;}
}

// 单独线程执行
void consume(){
while(true){
for(int i=0;i<rate;i++){
//执行请求
Request req=queue.poll();
if(req==null){break;}
req.doRequest();
}
Thread.sleep(gap);
}
}
对于该种算法,固定的限定了请求的速度,不允许流量突发的情况。

比如初始时桶是空的,这时1ms内来了100个请求,那只有前10个会被接受,其他的会被拒绝掉。注意与上文中 as a meter 实现的区别。

不过,当桶的大小等于每个ticket流出的水大小时,第二种漏桶算法和第一种漏桶算法是等价的。也就是说, as a queue 是 as a meter 的一种特殊实现。如果你没有理解这句话,你可以再看看上面 as a meter 的伪代码,当 bucketSize==rate 时,请求速度就是恒定的,不允许突发流量。

令牌桶算法
令牌桶算法的思想就是,桶中最多有N个令牌,会以一定速率往桶中加令牌,每个请求都需要从令牌桶中取出相应的令牌才能放行,如果桶中没有令牌则被限流。

令牌桶算法与上文的漏桶算法 as a meter 实现是等价的,能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。伪代码:

int token;
int bucketSize;
int rate;
long timeStamp=System.currentTimeMillis();

public synchronized boolean grant(){
long now=System.currentTimeMillis();
if(now>timeStamp){
token=Math.max(bucketSize,token+(timeStamp-now)*rate);
timeStamp=now;
}
if(token>0){
token--;
return true;
}else{
return false;
}

}
漏桶算法两种实现和令牌桶算法的对比
as a meter 的漏桶算法和令牌桶算法是一样的,只是思想角度有所不同。

as a queue 的漏桶算法能强行限制数据的传输速率,而令牌桶和 as a meter 漏桶则 能够在限制数据的平均传输速率的同时还允许某种程度的突发传输。

一般业界用的比较多的是令牌桶算法,像guava中的 RateLimiter 就是基于令牌桶算法实现的。当然不同的业务场景会有不同的需要,具体的选择还是要结合场景。

End
本文介绍了后端系统中常用的限流算法,对于每种算法都有对应的伪代码,结合伪代码理解起来应该不难。

原文地址:http://blog.51cto.com/14158311/2345716

时间: 2024-08-29 10:36:03

限流从概念到实现的相关文章

SpringCloud(8)----zuul权限校验、接口限流

项目代码GitHub地址:https://github.com/yudiandemingzi/spring-cloud-study 一.权限校验搭建 正常项目开发时,权限校验可以考虑JWT和springSecurity结合进行权限校验,这个后期会总结,这里做个基于ZuulFilter过滤器进行一个简单的权限校验过滤. 对于组件zuul中,其实带有权限认证的功能,那就是ZuulFilter过滤器.ZuulFilter是Zuul中核心组件,通过继承该抽象类,覆写几个关键方法达到自定义调度请求的作用

Spring Cloud Alibaba 之 Sentinel 限流规则和控制台实例

这一节我们通过一个简单的实例,学习Sentinel的基本应用. 一.Sentinel 限流核心概念 在学习Sentinel的具体应用之前,我们先来了解一下Sentinel中两个核心的概念,资源和规则. 资源 资源 是 Sentinel 中的核心概念之一.既然是限流,或者系统保护,那么是针对什么做限流?保护的是什么?就是我们所说的资源. 其实 Sentinel 对资源的定义,和并发编程中 Synchronized的使用很类似,这里的资源,可以是服务里的方法,也可以是一段代码. 规则 定义了资源之后

快速入门系列--WCF--06并发限流、可靠会话和队列服务

这部分将介绍一些相对深入的知识点,包括通过并发限流来保证服务的可用性,通过可靠会话机制保证会话信息的可靠性,通过队列服务来解耦客户端和服务端,提高系统的可服务数量并可以起到削峰的作用,最后还会对之前的事务知识做一定补充. 对于WCF服务来说,其寄宿在一个资源有限的环境中,为了实现服务性能最大化,需要提高其吞吐量即服务的并发性.然而在不进行流量控制的情况下,并发量过多,会使整个服务由于资源耗尽而崩溃.因此为相对平衡的并发数和系统可用性,需要设计一个闸门(Throttling)控制并发的数量. 由于

流量调整和限流技术

在早期的计算机领域,限流技术(time limiting)被用作控制网络接口收发通信数据的速率. 可以用来优化性能,减少延迟和提高带宽等. 现在在互联网领域,也借鉴了这个概念, 用来为服务控制请求的速率, 如果双十一的限流, 12306的抢票等. 即使在细粒度的软件架构中,也有类似的概念. 两种常用算法 令牌桶(Token Bucket)和漏桶(leaky bucket)是 最常用的两种限流的算法. 漏桶算法 它的主要目的是控制数据注入到网络的速率,平滑网络上的突发流量.漏桶算法提供了一种机制,

高并发限流算法

开篇 在高并发系统中,有很多手段来保护系统,如缓存.降级和限流等. 缓存:让数据尽早进入缓存,离程序近一点,不要大量频繁的访问DB,可提供系统访问速度和增大系统处理能力. 降级:当服务出问题或者影响到核心流程的性能,需要将服务暂时屏蔽掉,待高峰期过去或问题解决后再启用. 然后,有些场景不能用缓存和降级来解决.比如电商的双十一,用户的购买,下单等行为,是涉及到大量写操作,而且是核心链路,无法降级的. 限流:通过把并发访问/请求进行限速或者一定时间窗口内的请求限制在一定范围内来保护系统,一旦达到限制

谈谈高并发系统的限流

开涛大神在博客中说过:在开发高并发系统时有三把利器用来保护系统:缓存.降级和限流.本文结合作者的一些经验介绍限流的相关概念.算法和常规的实现方式. 缓存 缓存比较好理解,在大型高并发系统中,如果没有缓存数据库将分分钟被爆,系统也会瞬间瘫痪.使用缓存不单单能够提升系统访问速度.提高并发访问量,也是保护数据库.保护系统的有效方式.大型网站一般主要是"读",缓存的使用很容易被想到.在大型"写"系统中,缓存也常常扮演者非常重要的角色.比如累积一些数据批量写入,内存里面的缓存

限流和降级(下) | 如何打造平台稳定性能力(二)

摘要: 上一期我们谈到了阿里巴巴早期是通过通过在 Nginx 上实现的扩展组件TMD(taobao missile defense淘宝×××防御系统)实现了接入层限流的主要工作,TMD系统可通过域名类限流.cookie限流.黑名单以及一些安全策略等很好的实现了在接入层的限流措施. 上一期我们谈到了阿里巴巴早期是通过通过在 Nginx 上实现的扩展组件TMD(taobao missile defense淘宝×××防御系统)实现了接入层限流的主要工作,TMD系统可通过域名类限流.cookie限流.黑

架构设计之服务限流

限流可以认为服务降级的一种,限流就是限制系统的输入和输出流量已达到保护系统的目的.一般来说系统的吞吐量是可以被测算的,为了保证系统的稳定运行,一旦达到的需要限制的阈值,就需要限制流量并采取一些措施以完成限制流量的目的.比如:延迟处理,拒绝处理,或者部分拒绝处理等等. v服务限流概念 在介绍限流概念之前,我们先来聊聊身边有哪些限流,如果有在帝都的码农估计对限流是最深有感触的,帝都但凡开个XXX会议,各大地铁站都会限流. 每年的双11都是剁手族的天堂,11月11号0点0几秒的时候,下面这些场景或许你

「技术干货」Pontus-用友云限流服务

在我们讨论系统稳定性的时候,其实核心的关键词就是容量规划,如何做好业务容量与系统性能的评估,是保障系统稳定性的关键.对于系统性能的评估,需要我们具备自动化工具来对系统进行性能压测,探测系统在实际业务场景下的基线数据,这是我们进行系统资源配置的基础,也是在应对流量增长时进行弹性扩容的依据.在我们做好容量规划的前提下,在实际业务场景下,我们还是不可避免的会面对不确定的系统压力,在面对突发不确定流量的情况下,我们最担心的就是系统的"雪崩".就像突然爆发的车流让道路交通瘫痪一样,我们的系统在突