Redis限流正确姿势指导指南,结尾附带Bug小惊喜,不要错过哦~

一、限流算法

限流算法经典的一般有四种:计数器(固定窗口)算法、滑动窗口算法、漏桶算法、令牌桶算法。

redis分布式限流来自林老师带你学编程00:0001:34

关于它们的实现网上有一堆的资料,有兴趣的可以百度、Google一下,下面是四种算法大致总结。

算法确定参数空间复杂度时间复杂度限制突发流量平滑限流分布式环境下实现难度
固定窗口计数周期T、周期内最大访问数N低O(1)(记录周期内访问次数及周期开始时间低O(1)
滑动窗口计数周期T、周期内最大访问数N高O(N)(记录每个小周期中的访问数量)中O(N)相对实现。滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑
漏桶漏桶流出速度r、漏桶容量N低O(1)(记录当前漏桶中容量)高O(N)
令牌桶令牌产生速度r、令牌桶容量N低O(1)(记录当前令牌桶中令牌数)高O(N)

二、分布式限流实现

现在的服务基本都是采用分布式多实例,单例的限流一般都是采用框架实现,例如Guava就一键式集成了令牌桶限流算法,直接调用API就可以了,很简单,但是在分布式环境下,就要求我们自己来实现了。

图片

一般分布式限流刚开始都会使用固定窗口算法进行限流,这主要原因是借助Redis,可以轻轻松松几行代码搞定分布式限流。

if (redisTemplate.hasKey(key)) {
     int currentNum = redisTemplate.opsForValue().get(key);
     if (currentNum < maxMum) {
          // 请求并发量加1
          redisTemplate.opsForValue().increment(key, 1);
      } else {
          return;
      }
} else {
   redisTemplate.opsForValue().set(key, 1, 1, TimeUnit.MINUTES);
}

三、限流成绝流

如上述代码所示,非常简单,设置一个带有过期时间的key,再加一个阀值就可以了,代码看似没有问题,其实隐藏着一个巨大的Bug,也正是因为这个Bug导致限流变成绝流。

3.1 Bug场景

先给大家讲解一下Bug出现的场景,博主将限流代码发布到线上之后,刚开始确实实现了限流,达到阀值之后就不会再增加了,但是过了几分钟之后发现还处于限流状态,此时Redis早就到了过期时间。查看Redis对应的Key发现,Key的过期时间竟然是-1,也就是永不过期。这个过程就非常诡异了,博主将Key删除掉之后,过一段时间又发生同样的问题。

3.2 错误整理

我们从这个错误场景中可以得出,这个Bug并不是必然发生的,而是在某一个时间点就会突然发生,明确这一点之后,基本可以确定应该是Redis操作这个Key发生的问题。

我们再来仔细看一下代码,所有的代码都没有问题,唯一可能出现的只可能是redisTemplate.opsForValue().increment(key, 1)命令了,假如说执行increment的时候,key正好过期了呢,执行increment方法是会报错还是会生成一个新的Key然后加1呢?

通过实验我们可以得出,通过redisTemplate.opsForValue().increment(key, 1)命令也可以创建Key,而且创建的还是不过期的Key,好了罪魁祸首找到了,那我们应该如何修复这个Bug呢,别着急我们接着往下看。

不说版本就瞎逼逼,那是耍流氓。RedisTemplate的版本号为:spring.data.redis-2.1.8.RELEASE

3.3 Bug修复

public void resetExpireKey(String key) {
    Long expireSeconds = redisTemplate.opsForValue().getOperations().getExpire(key);
    // 永不过期或者不存在
    if (expireSeconds == -1
                || expireSeconds == -2) {
        redisTemplate.opsForValue().set(key, 1, 1, TimeUnit.MINUTES);
    }
}

@Override
public boolean isLimiter(String key, int maxMum) {
    try {
        // key存在,说明1分钟之后还未过期
        if (redisTemplate.hasKey(key)) {
            // 重置过期时间
            resetExpireKey(key);
            int currentNum = redisTemplate.opsForValue().get(key);
            if (currentNum < maxMum) {
                // 请求并发量加1
                redisTemplate.opsForValue().increment(key, 1);
            } else {
                return true;
            }
        } else {
                // 已经过期,下一个周期开始(过期时间为1分钟)
            redisTemplate.opsForValue().set(key, 1, 1, TimeUnit.MINUTES);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

解决这个Bug其实很简单,只要在increment之前,做一层判断即可,这样一个分布式限流就完成了。

四、架构升级

4.1 Redis限流缺陷:

  1. 因为结构简单,它的缺陷也是非常明显的,如果我们设置的一分钟内上限是1w,但还是有可能导致高并发而不被限流,比如0.1s用完1w是额度,这个每秒的并发量就非常高了。

  2. 还有另外一个问题就是,如果多个实例,可能会导致某一个实例负载过高,导致限流不均匀。

4.2 架构升级

第一个问题只能换限流的算法模型了,因为固定窗口的算法就是有这个问题,需要修改为滑动窗口、漏桶、令牌桶算法,具体的实现博主会再后面的文章中和大家介绍,敬请期待。

第二个问题可以结合服务内部限流+分布式限流两种分别来实现,我们可以设置一个总的阀值和服务内部的阀值,如果内部达到阀值,总的阀值还没到,就可以推送到其它实例去处理。

图片

五、总结

今天的分享就到这边了,通过这个Bug,童靴们有没有感觉到,一个这么简单的代码逻辑,竟然隐藏着如此凶险的Bug。林老师带你学编程,今天你学到了嘛。

-----------------------

公众号:林老师带你学编程

网站:wolzq.com

代码无非增删改查,关注老师给你Coding

图片

林老师带你学编程https://wolzq.com  

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页