美文网首页
集群限流削峰方案的设计

集群限流削峰方案的设计

作者: garychenqin | 来源:发表于2019-01-01 15:52 被阅读0次

        为了保证系统高峰期能够稳定运行,尤其对于基础平台,接入的系统众多、整体的高可用显得尤为重要、但对于个别应用的突发瞬时流量、如果没有很好的限流策略,会影响整体系统的性能,甚至将整个系统击垮,因此如何实现流量控制至关重要。

        一、常用的限流方式:

        (1)漏桶算法。    

        漏桶算法就像我们平常使用的漏斗、不管我们倒入液体的速度有多快,漏斗都是从下面的小口匀速流出。漏桶算法跟漏斗类似,调用方作为“水滴”的消费者、我们限流方作为“水滴”的提供者、无论调用方调用频率多么不稳定、我们服务提供者只允许它按照固定的速率获取数据。

    漏桶算法示意图

        (2)令牌桶算法

        漏桶只能规定调用方按照固定的速率调用,令牌桶作为漏桶的一种改进、规定了调用方在一段时间内总的调用频率,允许调用方在一段时间内能够出现并发。举个简单的例子:你从运营商每个月定制了1G流量,他们不会管你是一天用完还是三十天用完,如果你提前用完了你就只能等下个月才会再有流量,同理你没用完,下个月也会清空当前月的流量,重新分配给你1G流量。

    令牌桶示意图

        二、基于Redis的分布式令牌桶实现。

        对于单机版的限流,我们可以用Guava的RateLimiter实现、Guava是基于令牌桶实现的限流算法,对于生产环境基本上是集群、当然也是可以取平均值的方式来粗略估算每个节点的限流速率,比如我们总的限流3000/s,集群中包含10台机器,则每台机器的限流速率300/s。这种限流方式对于轮询、随机的负载均衡算法还算较为公平、但是对于ip_hash等方式那就偏差过大,并不是最好的方案。

        需求:系统要实现对每个申请应用的个性化限流、每个接入系统都会分配单独的appKey、每个appKey限流QPS <= 500(QPS:每秒的请求次数)。

        如果采取单机版的限流,接入系统有N个,服务器节点数量M个,每个节点只能粗略计算限制QPS=500/N,每个节点需要初始化N个令牌桶,M个节点就需要初始化N*M个令牌桶。

        就算是我们采取懒加载的机制、每个appKey请求到达的时候才初始化RateLimiter,但是只能粗略计算每个节点的限流QPS,结果并不准确。

        如何才能做到集群的整体限流呢,思来想去,Redis中有incr命令实现原子自增、如果每个调用appKey请求一次,我们对其自增1,如果一秒内超过500,就拒绝当前请求能够实现我们的需求。如何统计1秒内呢,我们可以利用过期机制,设定当前请求计数的key过期时间为1s,但是incr没有提供原子操作的incr过期时间,该如何实现呢?

        int currentQPS = redis.incr(appKey, 1);

        if(currentQPS == 1) redis.expire(appKey, 1s);

        最开始我考虑到可以用上面的方式实现,看起来并没有什么问题,当是第一次请求的时候我们设置过期时间1s,但压测阶段老是会有问题,仔细一想,我们知道redis是单线程模型,所有请求到达会强制变成单线程排队,比如说我压测的一瞬间我QPS达到1w,有1w个incr命令排队等待执行,第一个执行的incr命令结束后设置过期时间排到了1w之后,导致该key不能正确的在1s内失效,下一个计时时间段得不到预期的结果。

        同时由于上述操作分成了两步,并不是原子操作,如果expire命令执行失败,该key永远不会失效、导致后续该调用方所有的请求都会被拒绝。

        后来查询一些资料,发现利用lua脚本能够实现命令的原子操作:

    local key =KEYS[1]

    local expire_time =ARGV[1]

    local count =redis.call("INCR", key, 1)

    if count == 1 then

        redis.call("EXPIRE", key, expire_time)

    end

    return count

        其中KEYS[1]为计数的appKey、ARGV[1]为一个计时时间间隔,单位为s。

        最后利用redis的eval命令执行lua脚本,实现incr与expire的原子操作:

    Long current = (Long) jedis.eval(INCR_LUA_SCRIPT, Arrays.asList(key)), Arrays.asList( “1”));

    if (current > 500) return false;

    return true;

    相关文章

      网友评论

          本文标题:集群限流削峰方案的设计

          本文链接:https://www.haomeiwen.com/subject/mtxekqtx.html