Nivelle 开拓视野冲破艰险看见世界 身临其境贴近彼此感受生活

限流

2017-10-25
nivelle

限流

背景:高并发系统中,有很多手段来保护系统,如缓存,降级和限流.缓存的目的是提升系统访问速度和增大系统处理能力,可谓抗高并发流量的银弹.而降级是当服务出问题或者影响到核心流程的性能,需要暂时屏蔽掉,待高峰过去或者问题解决后再打开的场景.而有些场景并不能用缓存和降级来解决,比如稀缺资源(秒杀,抢购),写服务(评论,下单),频繁的复杂查询(评论的最后几页)等.需要限流手段来处理.

限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的请求进行限速来保护系统,一旦达到限制则可以拒绝服务(定向到错误页或者告知资源没有了),排队或者等待(比如秒杀,评论,下单),降级(返回兜底数据或者默认数据,如商品详情页或者库存默认有货)

  • 限流种类:

  • 限制总并发数(数据库连接池,线程池)

  • 限制瞬时并发数(如Nginx的limit_conn模块,用来限制瞬时并发连接数)

  • 限制时间窗口内的平均速率(如Guava的RateLimiter,Nginx的limit_req模块用来限制每秒的平均速率),以及限制远程接口调用速率,限制MQ的消费速率等还可以根据网络连接数,网络流量,CPU或内存负载来限流

限流算法

  • 令牌桶算法

令牌桶算法,是一个存放固定容量令牌的桶,按照固定速率往桶里添加令牌.

描述:

1.假设限制2r/s,则按照500毫秒的固定速率往桶中添加令牌

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

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

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

image

  • 漏桶算法

漏桶作为计量工具时,可以用于流量整形和流量控制.

描述:

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

  2. 如果桶是空的,则不需流出水滴

  3. 可以以任意速率流入水滴到漏桶

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

image

比较:

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

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

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

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

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

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

还可以使用计数器来进行限流,主要是用来限制并发数,比如数据库连接池大小,线程池大小,秒杀并发数都是计数器的用法.只要全局总请求数或者一定时间段的总请求数达到设定阈值,则进行限流.简单粗暴.

应用级限流

限流总并发/连接/请求数

系统有极限并发/请求数,总有TPS(是TransactionsPerSecond的缩写,也就是事务数/秒。)和QPS(Queries Per Second意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数,是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。)如果超过了阈值,则系统就会不响应用户请求或者响应很慢,因此需要过载保护,防止击垮系统.

Tomcat中Connector配置如下:

  • acceptCount:如果Tomcat的线程都忙于响应,新来的连接会进入队列排队,如果超出排队大小,则拒绝连接;

  • maxConections:瞬时最大连接数,超出的会排队等待;

  • maxThreads:Tomcat能启动用来处理请求的最大线程数,如果请求处理量一直远远大于最大线程数,则会引起响应变慢甚至会僵死.

限流总资源数

如果有的资源是稀缺资源(如数据库连接;线程),而且可能你有多个系统都回去使用它,那么需要加以限制.可以使用池化技术来限制总资源数,如连接池,线程池.假设分配给每个应用的数据库连接是100,那么本应用最多可以使用100个资源,超出则可以等待或者抛出异常.

限流某个接口的总并发/请求数

如果接口可能会有突发访问情况,但又担心访问量太大造成崩溃,如抢购业务,那么这个时候就需要限制接口的总并发/请求总数请求数了.因为粒度比较细,可以为每个接口都设置相应的阈值.可以使用java中的AtomicLong或者Semaphore进行限流.

try {
    if(atomic.incrementAndGet()>限流数){
        //拒绝请求
    }
    //处理请求
}finally{
    atomic.decrementAndGet();
}


这种方式适合对可降级业务或者需要过载保护的服务进行限流,如抢购业务,超出限额,要么让用户排队,或者告诉没货了,这对用户来说可以接受.而一些开发平台也会限制用户调用某个接口的试用请求量,这时就可以用这种计数器方式实现.

限流某个接口的时间窗请求数

即一个时间窗口内的请求数,如想限制某个接口/服务每秒/每分钟/每天的请求数调用量.如一些基层服务会被很多其他系统调用,比如商品详情页服务会调用基础商品服务,但是更新量比较大有可能将基础服务打挂.这时,我们要对每秒/每分钟的调用量进行限速:


LoadingCache<Long,AtomicLong> counter = CacherBuilder.newBuilder().expireAfterWrite(2,TimeUnit.SECONDS).build(new CacherLooader<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).increntAndGet()>limit){
        System.out.println("限流了:"+currentSeconds);
        countine;
    }
    //业务处理
}

平滑限流某个接口的请求数

之前限流方式都不能应对突发请求,瞬时间请求可能都被允许,从而导致一些问题.因此,在一些场景中需要对突发请求进行整形,整形未平均速率请求处理.这个时候令牌桶和漏桶算法就可以满足我们的需求.

Guava框架提供了令牌桶算法实现,可直接拿来使用:

RateLimiter limiter = RateLimiter.create(5);

limiter.acquire()


  • RateLimiter.create(5)表示桶容量为5且每秒新增5个了令牌,即每隔200毫秒新增一个令牌
  • limit.acquire()表示消费一个令牌.如果当前桶中有足够令牌,则成功(返回值为0),如果桶中没有令牌,则暂停一段时间.比如,发令牌的时间间隔是200毫秒,则等待200毫秒后再去消费,这种实现将突发请求速率平均固定请求速率.

因为SmoothBursty允许一定程度的突发,会有人担心如果允许这种突发,假设突然间来了大流量,系统可能扛不住.因此,需要一种平滑速率的限流工具,从而在系统冷却后慢慢趋于平均速率(刚开始速率小一点,然后慢慢趋于我们设置的固定速率).guava也提供了SmoothWarmingUp来实现这种需求,其可以认为是漏桶算法.

SmoothWarmingUp


RateLimiter.create(doublepermitsPerSecond,long warmupPeriod,TimeUnit unit)

  • permitsPerSecond:每秒新增的令牌数
  • warmupPeriod:从冷却启动速率过渡到平均速率的时间间隔
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());
}


这样是梯形上升速率,也就是说冷启动时会以一个比较大的速率慢慢达到平均速率.然后趋于平均速率(梯形下降到平均速率).

分布式限流

分布式限流关键是将限流服务做成原子化,而解决方案可以使用Redis+lua或者Nginx+lua技术技术实现,通过这两种技术可以实现高并发和高性能.

接入层限流

接入层通常指请求流量的入口,该层的主要目的有:负载均衡,非法请求过滤,请求聚合,缓存,降级,限流,A/B测试,服务监控等.

Nginx接入层限流可以使用Nginx自带的两个模块:连接数限流模块ngx_http_limit_conn_module和漏桶算法实现的请求限流模块ngx_http_limit_req_module.换可以使用OpenResty提供的Lua限流模块lua-resty-limit-traffic应对更复杂的限流场景.

limit_conn 用来对某个key对应的总的网络连接数进行限流,可以按照IP,域名维度进行限流.limit_req用来对某个key对应的请求的平均速率进行限流,有两种方法:平滑模式(delay)和允许突发模式(nodelay).


下一篇 连接池线程池

评论