http://jinnianshilongnian.iteye.com/blog/2305117

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">在开发高并发系统时有三把利器用来保护系统:缓存、降级和限流。缓存的目的是提升系统访问速度和增大系统能处理的容量,可谓是抗高并发流量的银弹;而降级是当服务出问题或者影响到核心流程的性能则需要暂时屏蔽掉,待高峰或者问题解决后再打开;而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求量,即限流。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限流的目的是通过对并发访问</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如</span>nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的</span>limit_conn<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如</span>Guava<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的</span>RateLimiter<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、</span>nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的</span>limit_req<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制</span>MQ<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的消费速率。另外还可以根据网络连接数、网络流量、</span>CPU<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">或内存负载等来限流。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">先有缓存这个银弹,后有限流来应对</span>618<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、双十一高并发流量,在处理高并发问题上可以说是如虎添翼,不用担心瞬间流量导致系统挂掉或雪崩,最终做到有损服务而不是不服务;限流需要评估好,不可乱用,否则会正常流量出现一些奇怪的问题而导致用户抱怨。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">在实际应用时也不要太纠结算法问题,因为一些限流算法实现是一样的只是描述不一样;具体使用哪种限流技术还是要根据实际场景来选择,不要一味去找最佳模式,白猫黑猫能解决问题的就是好猫。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">因在实际工作中遇到过许多人来问如何进行限流,因此本文会详细介绍各种限流手段。那么接下来我们从限流算法、应用级限流、分布式限流、接入层限流来详细学习下限流技术手段。</span>

&nbsp;

##
限流算法

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">常见的限流算法有:令牌桶、漏桶。计数器也可以进行粗暴限流实现。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">令牌桶算法</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">令牌桶算法是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌。令牌桶算法的描述如下:</span>
  • 假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌;

  • 桶中最多存放b个令牌,当桶满时,新添加的令牌被丢弃或拒绝;

  • 当一个n个字节大小的数据包到达,将从桶中删除n个令牌,接着数据包被发送到网络上;

  • 如果桶中的令牌不足n个,则不会删除令牌,且该数据包将被限流(要么丢弃,要么缓冲区等待)。

     

     

    漏桶算法

    漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:

  • 一个固定容量的漏桶,按照常量固定速率流出水滴;

  • 如果桶是空的,则不需流出水滴;

  • 可以以任意速率流入水滴到漏桶;

  • 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。

     

    令牌桶和漏桶对比:

  • 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;

  • 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;

  • 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;

  • 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;

  • 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;

  • 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。

     

    另外有时候我们还使用计数器来进行限流,主要用来限制总并发数,比如数据库连接池、线程池、秒杀的并发数;只要全局总请求数或者一定时间段的总请求数设定的阀值则进行限流,是简单粗暴的总数量限流,而不是平均速率限流。

     

    到此基本的算法就介绍完了,接下来我们首先看看应用级限流。

     

     

##
应用级限流

**<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 20px;">&nbsp;</span>**

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限流总并发</span>/****<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">连接</span>/****<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求数</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对于一个应用系统来说一定会有极限并发</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求数,即总有一个</span>TPS/QPS<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">阀值,如果超了阀值则系统就会不响应用户请求或响应的非常慢,因此我们最好进行过载保护,防止大量请求涌入击垮系统。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">如果你使用过</span>Tomcat<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,其</span>Connector <span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">其中一种配置有如下几个参数:</span>

acceptCount<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:如果</span>Tomcat<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;</span>

maxConnections<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:</span> <span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">瞬时最大连接数,超出的会排队等待;</span>

maxThreads<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:</span>Tomcat<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数则可能会僵死。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">详细的配置请参考官方文档。另外如</span>Mysql<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">(如</span>max_connections<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)、</span>Redis<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">(如</span>tcp-backlog<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)都会有类似的限制连接数的配置。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限流总资源数</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">如果有的资源是稀缺资源(如数据库连接、线程),而且可能有多个系统都会去使用它,那么需要限制应用;可以使用池化技术来限制总资源数:连接池、线程池。比如分配给每个应用的数据库连接是</span>100<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,那么本应用最多可以使用</span>100<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个资源,超出了可以等待或者抛异常。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限流某个接口的总并发</span>/****<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求数</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务;这个时候就需要限制这个接口的总并发</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求数总请求数了;因为粒度比较细,可以为每个接口都设置相应的阀值。可以使用</span>Java<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中的</span>AtomicLong<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">进行限流:</span>
try {
    if(atomic.incrementAndGet() > 限流数) {
        //拒绝请求
   }
    //处理请求
} finally {
    atomic.decrementAndGet();
}
<span style="line-height: 25.6px; font-family: 微软雅黑, sans-serif;">适合对业务无损的服务或者需要过载保护的服务进行限流,如抢购业务,超出了大小要么让用户排队,要么告诉用户没货了,对用户来说是可以接受的。而一些开放平台也会限制用户调用某个接口的试用请求量,也可以用这种计数器方式实现。这种方式也是简单粗暴的限流,没有平滑处理,需要根据实际情况选择使用;</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限流某个接口的时间窗请求数</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">即一个时间窗口内的请求数,如想限制某个接口</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">/</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">服务每秒</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">/</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">每分钟</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">/</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">每天的请求数</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">/</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">调用量。如一些基础服务会被很多其他系统调用,比如商品详情页服务会调用基础商品服务调用,但是怕因为更新量比较大将基础服务打挂,这时我们要对每秒</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">/</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">每分钟的调用量进行限速;一种实现方式如下所示:</span>
LoadingCache<Long, AtomicLong> counter =
        CacheBuilder._newBuilder_()
                .expireAfterWrite(2, TimeUnit.**_SECONDS_**)
                .build(**new **CacheLoader<Long, AtomicLong>() {
                    @Override
                    **public **AtomicLong load(Long seconds) **throws **Exception {
                        **return new **AtomicLong(0);
                    }
                });
**long **limit = 1000;
**while**(**true**) {
    _//__得到当前秒
    _**long **currentSeconds = System._currentTimeMillis_() / 1000;
    **if**(counter.get(currentSeconds).incrementAndGet() > limit) {
        System.**_out_**.println(**"****限流了:" **+ currentSeconds);
        **continue**;
    }
    _//__业务处理_
}
&nbsp;<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">我们使用</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">Guava</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">的</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">Cache</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">来存储计数器,过期时间设置为</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">2</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">秒(保证</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">1</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">秒内的计数器是有的),然后我们获取当前时间戳然后取秒数来作为</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">KEY</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">进行计数统计和限流,这种方式也是简单粗暴,刚才说的场景够用了。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">平滑限流某个接口的请求数</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">之前的限流方式都不能很好地应对突发请求,即瞬间请求可能都被允许从而导致一些问题;因此在一些场景中需要对突发请求进行整形,整形为平均速率请求处理(比如</span>5r/s<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,则每隔</span>200<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒处理一个请求,平滑了速率)。这个时候有两种算法满足我们的场景:令牌桶和漏桶算法。</span>Guava<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">框架提供了令牌桶算法实现,可直接拿来使用。</span>

Guava RateLimiter<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">提供了令牌桶算法实现:平滑突发限流</span>(SmoothBursty)<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">和平滑预热限流</span>(SmoothWarmingUp)<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">实现。</span>

&nbsp;

**SmoothBursty**
RateLimiter limiter = RateLimiter._create_(5);
System.**_out_**.println(limiter.acquire());
System.**_out_**.println(limiter.acquire());
System.**_out_**.println(limiter.acquire());
System.**_out_**.println(limiter.acquire());
System.**_out_**.println(limiter.acquire());
System.**_out_**.println(limiter.acquire());
&nbsp;<span style="line-height: 25.6px; font-size: 12px;">&nbsp; 将得到类似如下的输出:</span>

<span style="margin: 0px; padding: 0px; font-size: 12px;">&nbsp; 0.0</span>

<span style="margin: 0px; padding: 0px; font-size: 12px;">&nbsp; 0.198239</span>

<span style="margin: 0px; padding: 0px; font-size: 12px;">&nbsp; 0.196083</span>

<span style="margin: 0px; padding: 0px; font-size: 12px;">&nbsp; 0.200609</span>

<span style="margin: 0px; padding: 0px; font-size: 12px;">&nbsp;&nbsp;0.199599</span>

<span style="margin: 0px; padding: 0px; font-size: 12px;">&nbsp;&nbsp;0.19961</span>

<span style="margin: 0px; padding: 0px; line-height: 1.6;">1</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">、</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">RateLimiter.create(5) </span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">表示桶容量为</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">5</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">且每秒新增</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">5</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">个令牌,即每隔</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">200</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">毫秒新增一个令牌;</span>

2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、</span>limiter.acquire()<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">表示消费一个令牌,如果当前桶中有足够令牌则成功(返回值为</span>0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">),如果桶中没有令牌则暂停一段时间,比如发令牌间隔是</span>200<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒,则等待</span>200<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒后再去消费令牌(如上测试用例返回的为</span>0.198239<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,差不多等待了</span>200<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒桶中才有令牌可用),这种实现将突发请求速率平均为了固定请求速率。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;"><span style="margin: 0px; padding: 0px; font-size: 15px;">再看一个突发示例:</span></span>

RateLimiter limiter = RateLimiter.create(5);

System.out.println(limiter.acquire(5));

System.out.println(limiter.acquire(1));

System.out.println(limiter.acquire(1));
<span style="line-height: 25.6px; font-family: 宋体; font-size: 12px;">将得到类似如下的输出:</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.98745</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.183553</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.199909</span>

<span style="line-height: 25.6px;">limiter.acquire(5)</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">表示桶的容量为</span><span style="line-height: 25.6px;">5</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">且每秒新增</span><span style="line-height: 25.6px;">5</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个令牌,令牌桶算法允许一定程度的突发,所以可以一次性消费</span><span style="line-height: 25.6px;">5</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个令牌,但接下来的</span><span style="line-height: 25.6px;">limiter.acquire(1)</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">将等待差不多</span><span style="line-height: 25.6px;">1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒桶中才能有令牌,且接下来的请求也整形为固定速率了。</span>

RateLimiter limiter = RateLimiter.create(5);

System.out.println(limiter.acquire(10));

System.out.println(limiter.acquire(1));

System.out.println(limiter.acquire(1));
<span style="line-height: 25.6px; font-family: 宋体; font-size: 12px;">将得到类似如下的输出:</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">1.997428</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.192273</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.200616</span>

<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">同上边的例子类似,第一秒突发了</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">10</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">个请求,令牌桶算法也允许了这种突发(允许消费未来的令牌),但接下来的</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">limiter.acquire(1)</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">将等待差不多</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">2</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">秒桶中才能有令牌,且接下来的请求也整形为固定速率了。</span>

<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;"><span style="margin: 0px; padding: 0px; font-size: 15px;">接下来再看一个突发的例子:</span></span>

RateLimiter limiter = RateLimiter.create(2);

System.out.println(limiter.acquire());

Thread.sleep(2000L);

System.out.println(limiter.acquire());

System.out.println(limiter.acquire());

System.out.println(limiter.acquire());

System.out.println(limiter.acquire());

System.out.println(limiter.acquire());
<span style="line-height: 25.6px; font-family: 宋体; font-size: 12px;">将得到类似如下的输出:</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.499876</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.495799</span>

<span style="margin: 0px; padding: 0px; line-height: 1.6;">1</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">、创建了一个桶容量为</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">2</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">且每秒新增</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">2</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">个令牌;</span>

2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、首先调用</span>limiter.acquire()<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">消费一个令牌,此时令牌桶可以满足(返回值为</span>0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">);</span>

3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、然后线程暂停</span>2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒,接下来的两个</span>limiter.acquire()<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">都能消费到令牌,第三个</span>limiter.acquire()<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">也同样消费到了令牌,到第四个时就需要等待</span>500<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒了。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">此处可以看到我们设置的桶容量为</span>2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">(即允许的突发量),这是因为</span>SmoothBursty<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中有一个参数:最大突发秒数(</span>maxBurstSeconds<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)默认值是</span>1s<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,突发量</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">桶容量</span>=<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">速率</span>*maxBurstSeconds<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,所以本示例桶容量</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">突发量为</span>2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,例子中前两个是消费了之前积攒的突发量,而第三个开始就是正常计算的了。令牌桶算法允许将一段时间内没有消费的令牌暂存到令牌桶中,留待未来使用,并允许未来请求的这种突发。</span>

&nbsp;

SmoothBursty<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">通过平均速率和最后一次新增令牌的时间计算出下次新增令牌的时间的,另外需要一个桶暂存一段时间内没有使用的令牌(即可以突发的令牌数)。另外</span>RateLimiter<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">还提供了</span>tryAcquire<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">方法来进行无阻塞或可超时的令牌消费。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">因为</span>SmoothBursty<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">允许一定程度的突发,会有人担心如果允许这种突发,假设突然间来了很大的流量,那么系统很可能扛不住这种突发。因此需要一种平滑速率的限流工具,从而系统冷启动后慢慢的趋于平均固定速率(即刚开始速率小一些,然后慢慢趋于我们设置的固定速率)。</span>Guava<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">也提供了</span>SmoothWarmingUp<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">来实现这种需求,其可以认为是漏桶算法,但是在某些特殊场景又不太一样。</span>

&nbsp;

SmoothWarmingUp<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">创建方式:</span>RateLimiter.create(doublepermitsPerSecond, long warmupPeriod, TimeUnit unit)

permitsPerSecond<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">表示每秒新增的令牌数,</span>warmupPeriod<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">表示在从冷启动速率过渡到平均速率的时间间隔。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;"><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">示例如下:</span></span>

RateLimiter limiter = RateLimiter.create(5, 1000, TimeUnit.MILLISECONDS);

for(int i = 1; i < 5;i++) {

    System.out.println(limiter.acquire());

}

Thread.sleep(1000L);

for(int i = 1; i < 5;i++) {

    System.out.println(limiter.acquire());

}
<span style="line-height: 25.6px; font-family: 宋体; font-size: 12px;">将得到类似如下的输出:</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.51767</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.357814</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.219992</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.199984</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.0</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.360826</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.220166</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.199723</span>

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">0.199555</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">速率是梯形上升速率的,也就是说冷启动时会以一个比较大的速率慢慢到平均速率;然后趋于平均速率(梯形下降到平均速率)。可以通过调节</span>warmupPeriod<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">参数实现一开始就是平滑固定速率。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">到此应用级限流的一些方法就介绍完了。假设将应用部署到多台机器,应用级限流方式只是单应用内的请求限流,不能进行全局限流。因此我们需要分布式限流和接入层限流来解决这个问题。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

##
分布式限流

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使使用</span>redis+lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">或者</span>nginx+lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">技术进行实现,通过这两种技术可以实现的高并发和高性能。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">首先我们来使用</span>redis+lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">实现时间窗内某个接口的请求数限流,实现了该功能后可以改造为限流总并发</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求数和限制总资源数。</span>Lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">本身就是一种编程语言,也可以使用它实现复杂的令牌桶或漏桶算法。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">redis+lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">实现中的</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">脚本:</span>**

local key = KEYS[1] –限流KEY(一秒一个)

local limit = tonumber(ARGV[1]) –限流大小

local current = tonumber(redis.call("INCRBY", key, "1")) –请求数+1

if current > limit then –如果超出限流大小

    return 0

elseif current == 1 then –只有第一次访问需要设置2秒的过期时间

    redis.call("expire", key,"2")

end

return 1
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">如上操作因是在一个</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">脚本中,又因</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">Redis</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">是单线程模型,因此是线程安全的。如上方式有一个缺点就是当达到限流大小后还是会递增的,可以改造成如下方式实现:</span>

local key = KEYS[1] –限流KEY(一秒一个)

local limit = tonumber(ARGV[1]) –限流大小

local current = tonumber(redis.call('get', key) or "0")

if current + 1 > limit then –如果超出限流大小

    return 0

else –请求数+1,并设置2秒过期

    redis.call("INCRBY", key,"1")

    redis.call("expire", key,"2")

    return 1

end
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">如下是</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">Java</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">中判断是否需要限流的代码:</span>

public static boolean acquire() throws Exception {

String luaScript = Files.toString(new File("limit.lua"), Charset.defaultCharset());

Jedis jedis = new Jedis("192.168.147.52", 6379);

String key = "ip:" + System.currentTimeMillis()/ 1000; //此处将当前时间戳取秒数

Stringlimit = "3"; //限流大小

return (Long)jedis.eval(luaScript,Lists.newArrayList(key), Lists.newArrayList(limit)) == 1;

}
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">因为</span><span style="line-height: 25.6px;">Redis</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的限制(</span><span style="line-height: 25.6px;">Lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中有写操作不能使用带随机性质的读操作,如</span><span style="line-height: 25.6px;">TIME</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)不能在</span><span style="line-height: 25.6px;">Redis Lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中使用</span><span style="line-height: 25.6px;">TIME</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">获取时间戳,因此只好从应用获取然后传入,在某些极端情况下(机器时钟不准的情况下),限流会存在一些小问题。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 宋体; font-size: 12px;">**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">使用</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">Nginx+Lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">实现的</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">Lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">脚本:</span>**</span>

local locks = require "resty.lock"

local function acquire()

    local lock =locks:new("locks")

    local elapsed, err =lock:lock("limit_key") –互斥锁

    local limit_counter =ngx.shared.limit_counter –计数器

    local key = "ip:" ..os.time()

    local limit = 5 –限流大小

    local current =limit_counter:get(key)

    if current ~= nil and current + 1> limit then –如果超出限流大小

        lock:unlock()

        return 0

    end

    if current == nil then

        limit_counter:set(key, 1, 1) –第一次需要设置过期时间,设置key的值为1,过期时间为1秒

    else

        limit_counter:incr(key, 1) –第二次开始加1即可

    end

    lock:unlock()

    return 1

end

ngx.print(acquire())
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">实现中我们需要使用</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">lua-resty-lock</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">互斥锁模块来解决原子性问题</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">(</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">在实际工程中使用时请考虑获取锁的超时问题</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">)</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">,并使用</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">ngx.shared.DICT</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">共享字典来实现计数器。如果需要限流则返回</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">0</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">,否则返回</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">。使用时需要先定义两个共享字典(分别用来存放锁和计数器数据):</span>

&nbsp;



Java代码 收藏代码 “收藏这段代码”)

  1. http {  
  2.     ……  
  3.     lua_shared_dict locks 10m;  
  4.     lua_shared_dict limit_counter 10m;  
  5. }  

有人会纠结如果应用并发量非常大那么redis或者nginx是不是能抗得住;不过这个问题要从多方面考虑:你的流量是不是真的有这么大,是不是可以通过一致性哈希将分布式限流进行分片,是不是可以当并发量太大降级为应用级限流;对策非常多,可以根据实际情况调节;像在京东使用Redis+Lua来限流抢购流量,一般流量是没有问题的。

 

对于分布式限流目前遇到的场景是业务上的限流,而不是流量入口的限流;流量入口限流应该在接入层完成,而接入层笔者一般使用Nginx

 

##
接入层限流

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">接入层通常指请求流量的入口,该层的主要目的有:负载均衡、非法请求过滤、请求聚合、缓存、降级、限流、</span>A/B<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">测试、服务质量监控等等,可以参考笔者写的《</span>[使用Nginx+Lua(OpenResty)开发高性能Web应用](http://jinnianshilongnian.iteye.com/blog/2280928)<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">》。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对于</span>Nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">接入层限流可以使用</span>Nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">自带了两个模块:连接数限流模块</span>ngx_http_limit_conn_module<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">和漏桶算法实现的请求限流模块</span>ngx_http_limit_req_module<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">。还可以使用</span>OpenResty<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">提供的</span>Lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限流模块</span>lua-resty-limit-traffic<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">进行更复杂的限流场景。</span>

&nbsp;

limit_conn<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">用来对某个</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对应的总的网络连接数进行限流,可以按照如</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、域名维度进行限流。</span>limit_req<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">用来对某个</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对应的请求的平均速率进行限流,并有两种用法:平滑模式(</span>delay<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)和允许突发模式</span>(nodelay)<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">。</span>

&nbsp;

###
ngx_http_limit_conn_module

limit_conn<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">是对某个</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对应的总的网络连接数进行限流。可以按照</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">来限制</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">维度的总连接数,或者按照服务域名来限制某个域名的总连接数。但是记住不是每一个请求连接都会被计数器统计,只有那些被</span>Nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">处理的且已经读取了整个请求头的请求连接才会被计数器统计。</span>

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">&nbsp;</span>**

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">配置示例:</span>**

http {

    limit_conn_zone$binary_remote_addr zone=addr:10m; 

    limit_conn_log_level error; 

    limit_conn_status 503;

    …

    server {

    …

    location /limit {

        limit_conn addr 1;

    }
<span style="margin: 0px; padding: 0px; line-height: 1.6;">limit_conn</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">:要配置存放</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">KEY</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">和计数器的共享内存区域和指定</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">KEY</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">的最大连接数;此处指定的最大连接数是</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">1</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">,表示</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">Nginx</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">最多同时并发处理</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">1</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">个连接;</span>

limit_conn_zone<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:用来配置限流</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、及存放</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对应信息的共享内存区域大小;此处的</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">是&ldquo;</span>$binary_remote_addr<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&rdquo;其表示</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">地址,也可以使用如</span><span style="margin: 0px; padding: 0px; color: windowtext;">$server_name</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">作为</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">来限制域名级别的最大连接数;</span>

limit_conn_status<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:配置被限流后返回的状态码,默认返回</span>503<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;</span>

limit_conn_log_level<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:配置记录被限流后的日志级别,默认</span>error<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">级别。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

**limit_conn<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的主要执行过程如下所示:</span>**

1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、请求进入后首先判断当前</span>limit_conn_zone<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中相应</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的连接数是否超出了配置的最大连接数;</span>

2.1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、如果超过了配置的最大大小,则被限流,返回</span>limit_conn_status<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">定义的错误状态码;</span>

2.2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、否则相应</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的连接数加</span>1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,并注册请求处理完成的回调函数;</span>

3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、进行请求处理;</span>

4<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、在结束请求阶段会调用注册的回调函数对相应</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的连接数减</span>1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">。</span>

&nbsp;

limt_conn<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">可以限流某个</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的总并发</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">请求数,</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">可以根据需要变化。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">按照</span>IP****<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限制并发连接数配置示例:</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">首先定义</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">维度的限流区域:</span>

limit_conn_zone $binary_remote_addrzone=perip:10m;
&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">接着在要限流的</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">location</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">中添加限流逻辑:</span>

location /limit {

    limit_conn perip 2;

    echo "123";

}
<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">即允许每个</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">IP</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">最大并发连接数为</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">2</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">。</span>

<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">使用</span>AB<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">测试工具进行测试,并发数为</span>5<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个,总的请求数为</span>5<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个:</span>
&nbsp;&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">将得到如下<span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif;">access.log</span>输出:</span>

<span style="color: rgb(255, 41, 65); line-height: 25.6px; font-size: 12px;">[08/Jun/2016:20:10:51+0800] [1465373451.802] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[08/Jun/2016:20:10:51+0800] [1465373451.803] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[08/Jun/2016:20:10:51 +0800][1465373451.803] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[08/Jun/2016:20:10:51 +0800][1465373451.803] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[08/Jun/2016:20:10:51 +0800][1465373451.803] 503</span>

<span style="line-height: 25.6px; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">此处我们把</span>access log<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">格式设置为</span>log_format main&nbsp; &#39;[$time_local] [$msec] $status&#39;<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;分别是&ldquo;日期</span> <span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">日期秒</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒值</span> <span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">响应状态码&rdquo;。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">如果被限流了,则在</span>error.log<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中会看到类似如下的内容:</span>

2016/06/08 20:10:51 [error] 5662#0: *5limiting connections by zone "perip", client: 127.0.0.1, server: _,request: "GET /limit HTTP/1.0", host: "localhost"
<span style="margin: 0px; padding: 0px; line-height: 25.6px; font-family: 微软雅黑, sans-serif; font-size: 15px;">&nbsp;</span>

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">按照域名限制并发连接数配置示例:</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">首先定义域名维度的限流区域</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif; font-size: 15px;">:</span>

limit_conn_zone $ server_name zone=perserver:10m;
&nbsp;

<span style="font-family: 微软雅黑, sans-serif;"><span style="line-height: 25.6px; font-size: 15px; white-space: pre-wrap;">接着在要限流的location中添加限流逻辑:</span></span>

location /limit {

    limit_conn perserver 2;

    echo "123";

}
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">即允许每个域名最大并发请求连接数为</span><span style="line-height: 25.6px; font-family: &quot;Helvetica Neue&quot;, Helvetica, &quot;Hiragino Sans GB&quot;, &quot;Microsoft YaHei&quot;, Arial, sans-serif; font-size: 16px; white-space: pre-wrap;">2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;这样配置可以实现服务器最大连接数限制。</span>

&nbsp;

###
ngx_http_limit_req_module

limit_req<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">是漏桶算法实现,用于对指定</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对应的请求进行限流,比如按照</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">维度限制请求速率。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">配置示例:</span>**

http {

    limit_req_zone $binary_remote_addr zone=one:10m rate=1r/s;

    limit_conn_log_level error;

    limit_conn_status 503;

    …

    server {

    …

    location /limit {

        limit_req zone=one burst=5 nodelay;

    }
<span style="line-height: 25.6px;">limit_req</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:配置限流区域、桶容量(突发容量,默认</span><span style="line-height: 25.6px;">0</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)、是否延迟模式(默认延迟);</span>

limit_req_zone<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:配置限流</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、及存放</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">对应信息的共享内存区域大小、固定请求速率;此处指定的</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">是&ldquo;</span>$binary_remote_addr<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&rdquo;表示</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">地址;固定请求速率使用</span>rate<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">参数配置,支持</span>10r/s<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">和</span>60r/m<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,即每秒</span>10<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求和每分钟</span>60<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,不过最终都会转换为每秒的固定请求速率(</span>10r/s<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">为每</span>100<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒处理一个请求;</span>60r/m<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,即每</span>1000<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒处理一个请求)。</span>

limit_conn_status<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:配置被限流后返回的状态码,默认返回</span>503<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;</span>

limit_conn_log_level<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">:配置记录被限流后的日志级别,默认</span>error<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">级别。</span>

&nbsp;

limit_req<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的主要执行过程如下所示:</span>

1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、请求进入后首先判断最后一次请求时间相对于当前时间(第一次是</span>0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)是否需要限流,如果需要限流则执行步骤</span>2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,否则执行步骤</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;</span>

2.1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、如果没有配置桶容量(</span>burst<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">),则桶容量为</span>0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;按照固定速率处理请求;如果请求被限流,则直接返回相应的错误码(默认</span>503<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">);</span>

2.2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、如果配置了桶容量(</span>burst&gt;0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)且延迟模式</span>(<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">没有配置</span>nodelay)<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;如果桶满了,则新进入的请求被限流;如果没有满则请求会以固定平均速率被处理(按照固定速率并根据需要延迟处理请求,延迟使用休眠实现);</span>

2.3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、如果配置了桶容量(</span>burst&gt;0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)且非延迟模式(配置了</span>nodelay<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">);不会按照固定速率处理请求,而是允许突发处理请求;如果桶满了,则请求被限流,直接返回相应的错误码;</span>

3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、如果没有被限流,则正常处理请求;</span>

4<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、</span>Nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">会在相应时机进行选择一些(</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个节点)限流</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">进行过期处理,进行内存回收。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">场景</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">2.1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">测试</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">首先定义</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">IP</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">维度的限流区域:</span>

limit_req_zone $binary_remote_addrzone=test:10m rate=500r/s;
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限制为每秒</span><span style="line-height: 25.6px;">500</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,固定平均速率为</span><span style="line-height: 25.6px;">2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒一个请求。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">接着在要限流的</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">location</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">中添加限流逻辑:</span>

location /limit {

    limit_req zone=test;

    echo "123";

}
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">即桶容量为</span><span style="line-height: 25.6px;">0</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">(</span><span style="line-height: 25.6px;">burst</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">默认为</span><span style="line-height: 25.6px;">0</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">),且延迟模式。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">使用</span>AB<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">测试工具进行测试,并发数为</span>2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个,总的请求数为</span>10<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个:</span>

ab -n 10 -c 2 http://localhost/limit
<span style="line-height: 25.6px;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">将得到如下</span>access.log<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">输出:</span>

<span style="color: rgb(255, 41, 65); line-height: 25.6px; font-size: 12px;">[08/Jun/2016:20:25:56+0800] [1465381556.410] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[08/Jun/2016:20:25:56 +0800][1465381556.410] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[08/Jun/2016:20:25:56 +0800][1465381556.411] 503</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[08/Jun/2016:20:25:56+0800] [1465381556.411] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[08/Jun/2016:20:25:56 +0800][1465381556.412] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[08/Jun/2016:20:25:56 +0800][1465381556.412] 503</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">虽然每秒允许</span><span style="line-height: 25.6px;">500</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,但是因为桶容量为</span><span style="line-height: 25.6px;">0</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,所以流入的请求要么被处理要么被限流,无法延迟处理;另外平均速率在</span><span style="line-height: 25.6px;">2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒左右,比如</span><span style="line-height: 25.6px;">1465381556.410</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">和</span><span style="line-height: 25.6px;">1465381556.411</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">被处理了;有朋友会说这固定平均速率不是</span><span style="line-height: 25.6px;">1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒嘛,其实这是因为实现算法没那么精准造成的。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">如果被限流在</span>error.log<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中会看到如下内容:</span>

<span style="line-height: 25.6px; font-size: 12px;">2016/06/08 20:25:56 [error] 6130#0: *1962limiting requests, excess: 1.000 by zone &quot;test&quot;, client: 127.0.0.1,server: _, request: &quot;GET /limit HTTP/1.0&quot;, host:&quot;localhost&quot;</span>

<span style="line-height: 25.6px;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">如果被延迟了在</span>error.log<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">(日志级别要</span>INFO<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">级别)中会看到如下内容:</span>

<span style="line-height: 25.6px; font-size: 12px;">2016/06/10 09:05:23 [warn] 9766#0: *97021delaying request, excess: 0.368, by zone &quot;test&quot;, client: 127.0.0.1,server: _, request: &quot;GET /limit HTTP/1.0&quot;, host:&quot;localhost&quot;</span>

<span style="line-height: 25.6px;">&nbsp;</span>

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">场景</span>2.2****<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">测试</span>**

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">首先定义</span>IP<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">维度的限流区域:</span>

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">为了方便测试设置速率为每秒</span><span style="line-height: 25.6px;">2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,即固定平均速率是</span><span style="line-height: 25.6px;">500</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒一个请求。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">接着在要限流的</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">location</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">中添加限流逻辑:</span>

location /limit {

    limit_req zone=test burst=3;

    echo "123";

}
<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">固定平均速率为</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">500</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">毫秒一个请求,通容量为</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">3</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">,如果桶满了新的请求被限流,否则可以进入桶中排队并等待(实现延迟模式)。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">为了看出限流效果我们写了一个</span>req.sh<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">脚本:</span>

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">首先进行</span><span style="line-height: 25.6px;">6</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个并发请求</span><span style="line-height: 25.6px;">6</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">次</span><span style="line-height: 25.6px;">URL</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,然后休眠</span><span style="line-height: 25.6px;">300</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒,然后再进行</span><span style="line-height: 25.6px;">6</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个并发请求</span><span style="line-height: 25.6px;">6</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">次</span><span style="line-height: 25.6px;">URL</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;中间休眠目的是为了能跨越</span><span style="line-height: 25.6px;">2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒看到效果,如果看不到如下的效果可以调节休眠时间。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">将得到如下</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">access.log</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">输出:</span>

<span style="color: rgb(255, 41, 65); line-height: 25.6px; font-size: 12px;">[09/Jun/2016:08:46:43+0800] [1465433203.959] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:43 +0800][1465433203.959] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:43 +0800][1465433203.960] 503</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:44+0800] [1465433204.450] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:44+0800] [1465433204.950] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:45 +0800][1465433205.453] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:45 +0800][1465433205.766] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:45 +0800][1465433205.766] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:45 +0800][1465433205.767] 503</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:45+0800] [1465433205.950] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:46+0800] [1465433206.451] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:08:46:46+0800] [1465433206.952] 200</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">![](http://dl2.iteye.com/upload/attachment/0118/1460/dcbeb64a-1662-39d7-bdbf-ea367c4de49b.png)

&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">桶容量为</span><span style="line-height: 25.6px;">3</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,即桶中在时间窗口内最多流入</span><span style="line-height: 25.6px;">3</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,且按照</span><span style="line-height: 25.6px;">2r/s</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的固定速率处理请求(即每隔</span><span style="line-height: 25.6px;">500</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒处理一个请求);桶计算时间窗口(</span><span style="line-height: 25.6px;">1.5</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒)</span><span style="line-height: 25.6px;">=</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">速率(</span><span style="line-height: 25.6px;">2r/s</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)</span><span style="line-height: 25.6px;">/</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">桶容量</span><span style="line-height: 25.6px;">(3)</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,也就是说在这个时间窗口内桶最多暂存</span><span style="line-height: 25.6px;">3</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求。因此我们要以当前时间往前推</span><span style="line-height: 25.6px;">1.5</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒和</span><span style="line-height: 25.6px;">1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒来计算时间窗口内的总请求数;另外因为默认是延迟模式,所以时间窗内的请求要被暂存到桶中,并以固定平均速率处理请求:</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">第一轮:有</span>4<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求处理成功了,按照漏桶桶容量应该最多</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个才对;这是因为计算算法的问题,第一次计算因没有参考值,所以第一次计算后,后续的计算才能有参考值,因此第一次成功可以忽略;这个问题影响很小可以忽略;而且按照固定</span>500<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒的速率处理请求。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">第二轮:因为第一轮请求是突发来的,差不多都在</span>1465433203.959<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">时间点,只是因为漏桶将速率进行了平滑变成了固定平均速率(每</span>500<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒一个请求);而第二轮计算时间应基于</span>1465433203.959<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">;而第二轮突发请求差不多都在</span>1465433205.766<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">时间点,因此计算桶容量的时间窗口应基于</span>1465433203.959<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">和</span>1465433205.766<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">来计算,计算结果为</span>1465433205.766<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">这个时间点漏桶为空了,可以流入桶中</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,其他请求被拒绝;又因为第一轮最后一次处理时间是</span>1465433205.453<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,所以第二轮第一个请求被延迟到了</span>1465433205.950<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">。这里也要注意固定平均速率只是在配置的速率左右,存在计算精度问题,会有一些偏差。</span>

<span style="margin: 0px; padding: 0px; color: red;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">如果桶容量改为</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">(</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">burst=1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">),执行</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">req.sh</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">脚本可以看到如下输出:</span>

<span style="color: rgb(255, 41, 65); line-height: 25.6px; font-size: 12px;">09/Jun/2016:09:04:30+0800] [1465434270.362] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:30 +0800][1465434270.371] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:30 +0800] [1465434270.372]503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:30 +0800][1465434270.372] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:30 +0800][1465434270.372] 503</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:30+0800] [1465434270.864] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:31 +0800][1465434271.178] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:31 +0800][1465434271.178] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:31 +0800][1465434271.178] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:31 +0800][1465434271.178] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:31 +0800][1465434271.179] 503</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:09:04:31+0800] [1465434271.366] 200</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">桶容量为</span><span style="line-height: 25.6px;">1</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,按照每</span><span style="line-height: 25.6px;">1000</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒一个请求的固定平均速率处理请求。</span>

&nbsp;

**<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">场景</span>2.3****<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">测试</span>**

<span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">首先定义</span><span style="margin: 0px; padding: 0px; line-height: 1.6;">IP</span><span style="margin: 0px; padding: 0px; line-height: 1.6; font-family: 微软雅黑, sans-serif;">维度的限流区域:</span>

limit_req_zone $binary_remote_addr zone=test:10m rate=2r/s;
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">为了方便测试配置为每秒</span><span style="line-height: 25.6px;">2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,固定平均速率是</span><span style="line-height: 25.6px;">500</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒一个请求。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">接着在要限流的</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">location</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">中添加限流逻辑:</span>

location /limit {

    limit_req zone=test burst=3 nodelay;

    echo "123";

}
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">桶容量为</span><span style="line-height: 25.6px;">3</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,如果桶满了直接拒绝新请求,且每秒</span><span style="line-height: 25.6px;">2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">最多两个请求,桶按照固定</span><span style="line-height: 25.6px;">500</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒的速率以</span><span style="line-height: 25.6px;">nodelay</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">模式处理请求。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">为了看到限流效果我们写了一个</span>req.sh<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">脚本:</span>

ab -c 6 -n 6 http://localhost/limit

sleep 1

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit

sleep 0.3

ab -c 6 -n 6 http://localhost/limit

sleep 2

ab -c 6 -n 6 http://localhost/limit
&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">将得到类似如下</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">access.log</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">输出:</span>

<span style="color: rgb(255, 41, 65); line-height: 25.6px; font-size: 12px;">[09/Jun/2016:14:30:11+0800] [1465453811.754] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:11+0800] [1465453811.755] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:11+0800] [1465453811.755] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:11+0800] [1465453811.759] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:11 +0800][1465453811.759] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:11 +0800][1465453811.759] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:12+0800] [1465453812.776] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:12+0800] [1465453812.776] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:12 +0800][1465453812.776] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:12 +0800][1465453812.777] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:12 +0800][1465453812.777] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:12 +0800][1465453812.777] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800] [1465453813.095]503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.097] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.097] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.097] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.097] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.098] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13+0800] [1465453813.425] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.425] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.425] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.426] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.426] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.426] 503</span>

&nbsp;

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13+0800] [1465453813.754] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.755] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.755] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.756] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.756] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:13 +0800][1465453813.756] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:15+0800] [1465453815.278] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:15+0800] [1465453815.278] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:15+0800] [1465453815.278] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:15 +0800][1465453815.278] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:15 +0800][1465453815.279] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:15 +0800][1465453815.279] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:17+0800] [1465453817.300] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:17+0800] [1465453817.300] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:17+0800] [1465453817.300] 200</span>

<span style="margin: 0px; padding: 0px; color: rgb(255, 41, 65); font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:17+0800] [1465453817.301] 200</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:17 +0800][1465453817.301] 503</span>

<span style="margin: 0px; padding: 0px; font-size: 12px; max-width: 100%;">[09/Jun/2016:14:30:17 +0800][1465453817.301] 503</span>

![](http://dl2.iteye.com/upload/attachment/0118/1462/606ccb82-fdd1-3375-90f3-823831595156.png)

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">桶容量为</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">(,即桶中在时间窗口内最多流入</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求,且按照</span>2r/s<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的固定速率处理请求(即每隔</span>500<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">毫秒处理一个请求);桶计算时间窗口(</span>1.5<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒)</span>=<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">速率(</span>2r/s<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)</span>/<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">桶容量</span>(3)<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,也就是说在这个时间窗口内桶最多暂存</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求。因此我们要以当前时间往前推</span>1.5<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒和</span>1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒来计算时间窗口内的总请求数;另外因为配置了</span>nodelay<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,是非延迟模式,所以允许时间窗内突发请求的;另外从本示例会看出两个问题:</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">第一轮和第七轮:有</span>4<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求处理成功了;这是因为计算算法的问题,本示例是如果</span>2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒内没有请求,然后接着突然来了很多请求,第一次计算的结果将是不正确的;这个问题影响很小可以忽略;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">第五轮:</span>1.0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒计算出来是</span>3<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">个请求;此处也是因计算精度的问题,也就是说</span>limit_req<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">实现的算法不是非常精准的,假设此处看成相对于</span>2.75<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的话,</span>1.0<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">秒内只有</span>1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">次请求,所以还是允许</span>1<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">次请求的。</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">如果限流出错了,可以配置错误页面:</span>

proxy_intercept_errors on;

recursive_error_pages on;

error_page 503 //www.jd.com/error.aspx;
<span style="line-height: 25.6px;"><span style="font-family: 微软雅黑, sans-serif;"><span style="font-size: 15px;">l</span></span><span><span>imit_conn_zone/limit_req_zone</span></span></span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">定义的内存不足,则后续的请求将一直被限流,所以需要根据需求设置好相应的内存大小。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">此处的限流都是单</span>Nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的,假设我们接入层有多个</span>nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,此处就存在和应用级限流相同的问题;那如何处理呢?一种解决办法:建立一个负载均衡层将按照限流</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">进行一致性哈希算法将请求哈希到接入层</span>Nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">上,从而相同</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的将打到同一台接入层</span>Nginx<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">上;另一种解决方案就是使用</span>Nginx+Lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">(</span>OpenResty<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">)调用分布式限流逻辑实现。</span>

&nbsp;

###
lua-resty-limit-traffic

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">之前介绍的两个模块使用上比较简单,指定</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、指定限流速率等就可以了,如果我们想根据实际情况变化</span>KEY<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">、变化速率、变化桶大小等这种动态特性,使用标准模块就很难去实现了,因此我们需要一种可编程来解决我们问题;而</span>OpenResty<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">提供了</span>lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">限流模块</span>lua-resty-limit-traffic<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">,通过它可以按照更复杂的业务逻辑进行动态限流处理了。其提供了</span>limit.conn<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">和</span>limit.req<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">实现,算法与</span>nginx limit_conn<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">和</span>limit_req<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">是一样的。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">此处我们来实现</span>ngx_http_limit_req_module<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中的【场景</span>2.2<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">测试】,不要忘记下载</span>lua-resty-limit-traffic<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">模块并添加到</span>OpenResty<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">的</span>lualib<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">中。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">配置用来存放限流用的共享字典:</span>

lua_shared_dict limit_req_store 100m;
<span style="line-height: 25.6px; font-family: 微软雅黑, sans-serif; font-size: 15px;">&nbsp;</span>

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">以下是实现【场景</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">2.2</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">测试】的限流代码</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">limit_req.lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">:</span>

local limit_req = require "resty.limit.req"

local rate = 2 –固定平均速率 2r/s

local burst = 3 –桶容量

local error_status = 503

local nodelay = false –是否需要不延迟处理

local lim, err = limit_req.new("limit_req_store", rate, burst)

if not lim then –没定义共享字典

    ngx.exit(error_status)

end

local key = ngx.var.binary_remote_addr –IP维度的限流

–流入请求,如果请求需要被延迟则delay > 0

local delay, err = lim:incoming(key, true)

if not delay and err == "rejected" then –超出桶大小了

    ngx.exit(error_status)

end

if delay > 0 then –根据需要决定是延迟或者不延迟处理

    if nodelay then

        –直接突发处理了

    else

        ngx.sleep(delay) –延迟处理

    end

end
<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">即限流逻辑再</span><span style="line-height: 25.6px;">nginx access</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">阶段被访问,如果不被限流继续后续流程;如果需要被限流要么</span><span style="line-height: 25.6px;">sleep</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">一段时间继续后续流程,要么返回相应的状态码拒绝请求。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">在分布式限流中我们使用了简单的</span>Nginx+Lua<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">进行分布式限流,有了这个模块也可以使用这个模块来实现分布式限流。</span>

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">另外在使用</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">Nginx+Lua</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">时也可以获取</span><span style="margin: 0px; padding: 0px; font-family: Tahoma, sans-serif; font-size: 15px;">ngx.var.connections_active</span><span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif; font-size: 15px;">进行过载保护,即如果当前活跃连接数超过阈值进行限流保护。</span>

if tonumber(ngx.var.connections_active) >= tonumber(limit) then

    //限流

end
<span style="line-height: 25.6px;">&nbsp;</span>

nginx也提供了limit_rate用来对流量限速,如limit_rate 50k,表示限制下载速度为50k。

&nbsp;

<span style="margin: 0px; padding: 0px; font-family: 微软雅黑, sans-serif;">到此笔者在工作中涉及的限流用法就介绍完,这些算法中有些允许突发,有些会整形为平滑,有些计算算法简单粗暴;其中令牌桶算法和漏桶算法实现上是类似的,只是表述的方向不太一样,对于业务来说不必刻意去区分它们;因此需要根据实际场景来决定如何限流,最好的算法不一定是最适用的。</span>

&nbsp;