zoukankan      html  css  js  c++  java
  • 4

     
    单机限流策略(应用级限流)

     
       在学习分布式限流之前,需要先了解一下单机基本的限流策略。
     
       限流即流量限制,或者高大上一点,叫做流量整形,限流的目的是在遇到流量高峰期或者流量突增(流量尖刺)时,把流量速率限制在系统所能接受的合理范围之内,不至于让系统被高流量击垮。限流的结果对于调用方来说,可能是延迟获得返回,或者直接调用失败。
     
       其实,服务降级系统中的限流并没有我们想象的那么简单,第一,限流方案必须是可选择的,没有任何方案可以适用所有场景,每种限流方案都有自己适合的场景,我们得根据业务和资源的特性和要求来选择限流方案;第二,限流策略必须是可配的,对策略调优一定是个长期的过程,这里说的策略,可以理解成建立在某个限流方案上的一套相关参数。
     
        单机限流主要有几种思路:
    • 限制单位时间内的访问次数,比如一分钟内最多访问多少次
    • 限制并发量,同一时刻访问量不超过一个阈值
     
        单机限流主要有3种实现方式:
    • 计数器
      • 周期清零(对应第一种思路的具体实现)
      • 并发计数(对应第二种思路的具体实现)
    • 漏桶 Leaky Bucket(对应第二种思路的具体实现)
    • 令牌桶 Token Bucket(对应第一种(每个周期添加固定的令牌)+第二种(修改每次获取的令牌数)思路的具体实现) Guava rateLimiter
     
        接下来分别讲讲这几种思路以及对应实现方式的优缺点。
     
        (1)限制单位时间内访问次数,通常会用计数器来实现。考虑到线程安全问题,选择使用 AtomicLong作为计数器。在实际应用中,通常会采用环形结构的计数器,这是一个不错的计数方式。这里用 AtomicLong[3] 数组来计数,AtomicLong[0] 表示前一个周期的计数器,AtomicLong[1] 表示当前周期的计数器,AtomicLong[2] 表示下一个周期的计数器。不停的循环,来保证计数以及统计的需求。上一周期的数据可以保存用于发送。下一个周期的数据清0,以便在新周期该位可以用于计数。清0的方式也有多种,例如交给某一个线程来定时清0,或者,在计数时做判断,这时需要额外的变量存储计数更新时间。
     
           这种方式实现起来简单,阈值如果可以动态配置,甚至可以作为业务开关来使用。
           但是这种方式也有一定局限性,突刺消耗,指一个周期内前段时间的突增导致资源被大量消耗,致使剩余时间内该服务拒绝服务或延迟提供服务到下一个周期。此外,这种粗略的限制方式对于大量调用在极短时间内的冲击来说,并不能很好应对,可能会直接压跨系统甚至造成雪崩效应。
     
        (2)限制并发量,这种方式更加成熟,限制并发量的同时,也限制了单位时间内的访问次数。这种思路的实现可以用信号量来做,但是这种方式,只控制了边界条件。
     
            漏桶算法,可以说是一种流量整型算法,它严格控制了输出的速率,而对于输入的任务,是有一定容量的,当超过这个容量,只能丢弃。首先想到的就是这对于业务来说不怎么友好,当业务流量激增,就不得不重新调参。而且对于大型服务来说,漏桶处可能会成为瓶颈。线程池 + 有限容量的队列,就是一个漏桶。漏桶基本可以看成一个缓冲队列的设计。
     
        (3)既能限制单位时间内的访问次数,又能限制并发量,令牌桶的方式更加灵活,对于放置令牌和回收令牌,同样可以用信号量来实现,但是它不再限制获取信号量后必须归还,它用另一个线程定时往桶中放入令牌达到限流的目的。同时,令牌的消耗也可以随着令牌的使用率(使用中的令牌数/令牌总数)增大而增大,使得后面增速变慢。
        Guava 的RateLimiter 提供了令牌桶的实现,包括平滑突发限流和平滑预热限流。
     
     
    RFC 的 流量限制

        这部分先不做学习了解,这里令牌桶可以分为两种,单色双色
     
    分布式限流

     
        如果从服务的性能角度去考虑,应用级限流是最好的方式,而分布式限流的场景其实更多的是在于业务上限流,比如双十一,京东618,或者是小米抢购,这种在最上层入口处做限流。分布式限流需要借助第三方来实现,考虑原子性和速率的问题,Redis 是一个不错的实现工具。Redis + Lua 是一种不错的方式,因为本身Lua脚本就可以对这几种限流方案做很好的实现。此外,也可以用Nginx + Lua。
     
    计数器:
        基于 Redis 去做,用 Redis 作为计数器,某个进程对Redis做清除。
     
    漏桶:
        这种方式也不难,一种较好的方式是,桶是放在本地的,这样减少了请求时把请求存放到Redis中的网络和存储开销。但是这样可能会存在些问题,理想情况下负载均衡,各个服务器的桶的情况应该相近,但是事实未必如此。限流的池放在Redis的set中,拿到set中的值后,就可以从自己的桶里取出来一个数,这种方式来做整体的限流。可以先从桶拿到请求,然后再从Redis拿到许可,这样避免了许可拿到后发现没有要处理的数据,再归还许可。
     
    令牌桶:
        实现仍然类似
     
     
     
     
     
  • 相关阅读:
    BNU 51002 BQG's Complexity Analysis
    BNU OJ 51003 BQG's Confusing Sequence
    BNU OJ 51000 BQG's Random String
    BNU OJ 50999 BQG's Approaching Deadline
    BNU OJ 50998 BQG's Messy Code
    BNU OJ 50997 BQG's Programming Contest
    CodeForces 609D Gadgets for dollars and pounds
    CodeForces 609C Load Balancing
    CodeForces 609B The Best Gift
    CodeForces 609A USB Flash Drives
  • 原文地址:https://www.cnblogs.com/43726581Gavin/p/9043979.html
Copyright © 2011-2022 走看看