• nginx 、springMvc(非分布式)相应的限流、消峰


    互联网服务赖以生存的根本是流量, 产品和运营会经常通过各种方式来为应用倒流,比如淘宝的双十一等,如何让系统在处理高并发的同时还是保证自身系统的稳定,

    通常在最短时间内提高并发的做法就是加机器, 但是如果机器不够怎么办? 那就需要做业务降级或系统限流。

    流量控制中用的比较多的三个算法就是令牌桶、漏桶、计数器。

    一、令牌桶限流(TokenBucket)
    令牌桶算法的基本过程如下:

    1. 每秒会有 r 个令牌放入桶中,或者说,每过 1/r 秒桶中增加一个令牌。
    2. 桶中最多存放 b 个令牌,如果桶满了,新放入的令牌会被丢弃。
    3. 当一个 n 字节的数据包到达时,消耗 n 个令牌,然后发送该数据包。
    4. 如果桶中可用令牌小于 n,则该数据包将被缓存或丢弃。

       

    二、漏桶限流(LeakBucket)

    漏桶算法强制一个常量的输出速率而不管输入数据流的突发性

    (1)当输入空闲时,该算法不执行任何动作.就像用一个底部开了个洞的漏桶接水一样, 水进入到漏桶里, 桶里的水通过下面的孔以固定的速率流出

    (2)当水流入速度过大会直接溢出,可以看出漏桶算法能强行限制数据的传输速率.如下图所示:


     

    漏桶和令牌桶比较

    “漏桶算法”能够强行限制数据的传输速率,而“令牌桶算法”在能够限制数据的平均传输数据外,还允许某种程度的突发传输。

    在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的上限,因此它适合于具有突发特性的流量。

    三、计数器限流

    有时我们还会使用计数器来进行限流,主要用来限制一定时间内的总并发数。计数器限流方法可以通过缓存实现计数器,假如以秒为单位进行限流,

    过期时间为1秒,每次请求计数加1,超过每秒允许的最大请求数请求数阀值将被丢弃。

    四、nginx 实现漏桶

        #以用户二进制IP地址,定义三个漏桶,滴落速率1-3req/sec,桶空间1m,1M能保持大约16000个(IP)状态
        limit_req_zone  $binary_remote_addr  zone=qps1:1m   rate=1r/s;
        limit_req_zone  $binary_remote_addr  zone=qps2:1m   rate=2r/s;
        limit_req_zone  $binary_remote_addr  zone=qps3:1m   rate=3r/s;
         
        server {
         
            #速率qps=1,峰值burst=5,延迟请求
            #严格按照漏桶速率qps=1处理每秒请求
            #在峰值burst=5以内的并发请求,会被挂起,延迟处理
            #超出请求数限制则直接返回503
            #客户端只要控制并发在峰值[burst]内,就不会触发limit_req_error_log
            # 例1:发起一个并发请求=6,拒绝1个,处理1个,进入延迟队列4个:
            #time    request    refuse    sucess    delay
            #00:01        6        1        1            4
            #00:02        0        0        1            3
            #00:03        0        0        1            2
            #00:04        0        0        1            1
            #00:05        0        0        1            0
            location /delay {
                limit_req   zone=qps1  burst=5;
            }
             
            #速率qps=1,峰值burst=5,不延迟请求
            #加了nodelay之后,漏桶控制一段时长内的平均qps = 漏桶速率,允许瞬时的峰值qps > 漏桶qps
            #所以峰值时的最高qps=(brust+qps-1)=5
            #请求不会被delay,要么处理,要么直接返回503
            #客户端需要控制qps每秒请求数,才不会触发limit_req_error_log
            # 例2:每隔5秒发起一次达到峰值的并发请求,由于时间段内平均qps=1 所以仍然符合漏桶速率:
            #time    request     refuse    sucess
            #00:01         5         0          5
            #00:05         5         0          5
            #00:10         5         0          5
            # 例3:连续每秒发起并发请求=5,由于时间段内平均qps>>1,超出的请求被拒绝:
            #time    request     refuse     sucess
            #00:01         5         0           5
            #00:02         5         4           1
            #00:03         5         4           1
             
            location /nodelay {
                limit_req   zone=qps1  burst=5 nodelay;
            }
         
        }

     limit_req_module

    五、redis

    local key = KEYS[1] --限流KEY(一秒一个)
    local limit = tonumber(ARGV[1])        --限流大小
    local current = tonumber(redis.call('get', key) or "0")
    if current + 1 > limit then --如果超出限流大小
        redis.call("INCRBY", key,"1") -- 如果不需要统计真是访问量可以不加这行
        return 0
    else  --请求数+1,并设置2秒过期
        redis.call("INCRBY", key,"1")
        if tonumber(ARGV[2]) > -1 then
            redis.call("expire", key,tonumber(ARGV[2])) --时间窗口最大时间后销毁键
        end
        return 1
    end

    lua脚本返回值比较奇怪,用java客户端接受返回值,只能使用Long,没有去深究。这个脚本只需要传入key(url+时间戳/预设时间窗口大小),便可以实现限流。 

    java调用lua脚本

    /**
     * Created by xujingfeng on 2017/3/13.
     * <p>
     * 基于redis lua脚本的线程安全的计数器限流方案
     * </p>
     */
    public class RedisRateLimiter {
    
        /**
         * 限流访问的url
         */
        private String url;
    
        /**
         * 单位时间的大小,最大值为 Long.MAX_VALUE - 1,以秒为单位
         */
        final Long timeUnit;
    
        /**
         * 单位时间窗口内允许的访问次数
         */
        final Integer limit;
    
        /**
         * 需要传入一个lua script,莫名其妙redisTemplate返回值永远是个Long
         */
        private RedisScript<Long> redisScript;
    
        private RedisTemplate redisTemplate;
    
        /**
         * 配置键是否会过期,
         * true:可以用来做接口流量统计,用定时器去删除
         * false:过期自动删除,时间窗口过小的话会导致键过多
         */
        private boolean isDurable = false;
    
        public void setRedisScript(RedisScript<Long> redisScript) {
            this.redisScript = redisScript;
        }
    
        public void setRedisTemplate(RedisTemplate redisTemplate) {
            this.redisTemplate = redisTemplate;
        }
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public boolean isDurable() {
            return isDurable;
        }
    
        public void setDurable(boolean durable) {
            isDurable = durable;
        }
    
        public RedisRateLimiter(Integer limit, Long timeUnit) {
            this.timeUnit = timeUnit;
            Assert.isTrue(timeUnit < Long.MAX_VALUE - 1);
            this.limit = limit;
        }
    
        public RedisRateLimiter(Integer limit, Long timeUnit, boolean isDurable) {
            this(limit, timeUnit);
            this.isDurable = isDurable;
        }
    
        public boolean acquire() {
            return this.acquire(this.url);
        }
    
        public boolean acquire(String url) {
            StringBuffer key = new StringBuffer();
            key.append("rateLimiter").append(":")
                    .append(url).append(":")
                    .append(System.currentTimeMillis() / 1000 / timeUnit);
            Integer expire = limit + 1;
            String convertExpire = isDurable ? "-1" : expire.toString();
            return redisTemplate.execute(redisScript, Arrays.asList(key.toString()), limit.toString(), convertExpire).equals(1l);
        }
    
    }

    5.spring mvc

    RateLimiter

    我们可以使用 Guava 的 RateLimiter 来实现基于令牌桶的流量控制。RateLimiter 令牌桶算法的单桶实现,RateLimiter 对简单的令牌桶算法做了一

    些工程上的优化,具体的实现是 SmoothBursty。需要注意的是,RateLimiter 的另一个实现 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。

    SmoothBursty 有一个可以放 N 个时间窗口产生的令牌的桶,系统空闲的时候令牌就一直攒着,最好情况下可以扛 N 倍于限流值的高峰而不影响

    后续请求,就像三峡大坝一样能扛千年一遇的洪水.



    作者:Lewe
    链接:http://www.jianshu.com/p/7170edcd9239
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 相关阅读:
    博客作业04--树
    博客作业03--栈和队列
    博客作业2---线性表
    博客作业01-抽象数据类型
    C语言最后一次作业--总结报告
    CSAPP(8):系统级IO
    CSAPP(7):虚拟存储器
    CSAPP(6):异常控制流
    CASPP(5):链接
    CSAPP(4):存储器层次结构
  • 原文地址:https://www.cnblogs.com/yuyutianxia/p/7478405.html
Copyright © 2020-2023  润新知