Redis限流正确姿势指导指南,结尾附带Bug小惊喜,不要错过哦,2021大厂Java面试真题集锦

| — | — | — | — | — | — | — |

| 固定窗口 | 计数周期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就可以了,很简单,但是在分布式环境下,就要求我

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

们自己来实现了。

图片

一般分布式限流刚开始都会使用固定窗口算法进行限流,这主要原因是借助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之前,做一层判断即可,这样一个分布式限流就完成了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值