此文档源自钱文品老师所著《Redis 深度历险:核心原理和应用实践》

漏斗限流是最常用的限流方法之一,顾名思义,这个算法的灵感源于漏斗(funnel)的结构。

漏洞的容量是有限的,如果将漏嘴堵住,然后一直往里面灌水,它就会变满,直至再也装不进去。如果将漏嘴放开,水就会往下流,流走一部分之后,就又可以继续往里面灌水。如果漏嘴流水的速率大于灌水的速率,那么漏斗永远都装不满。如果漏嘴流水速率小于灌水的速率,那么一旦漏斗满了,灌水就需要暂停并等待漏斗腾空。

所以,漏斗的剩余空间就代表着当前行为可以持续进行的数量,漏嘴的流水速率代表着系统允许该行为的最大频率。下面我们使用代码来描述单机漏斗算法。

public class FunnelRateLimiter {static class Funnel {int capacity;float leakingRate;int leftQuota;long leakingTs;public Funnel(int capacity, float leakingRate) {this.capacity = capacity;this.leakingRate = leakingRate;this.leftQuota = capacity;this.leakingTs = System.currentTimeMillis();}void makeSpace() {long nowTs = System.currentTimeMillis();long deltaTs = nowTs - leakingTs;int deltaQuota = (int) (deltaTs * leakingRate);if (deltaQuota < 0) { // 间隔时间太长,整数数字过大溢出this.leftQuota = capacity;this.leakingTs = nowTs;return;}if (deltaQuota < 1) { // 腾出空间太小,最小单位是1return;}this.leftQuota += deltaQuota;this.leakingTs = nowTs;if (this.leftQuota > this.capacity) {this.leftQuota = this.capacity;}}boolean watering(int quota) {makeSpace();if (this.leftQuota >= quota) {this.leftQuota -= quota;return true;}return false;}}private Map<String, Funnel> funnels = new HashMap<>();public boolean isActionAllowed(String userId, String actionKey, int capacity, float leakingRate) {String key = String.format("%s:%s", userId, actionKey);Funnel funnel = funnels.get(key);if (funnel == null) {funnel = new Funnel(capacity, leakingRate);funnels.put(key, funnel);}return funnel.watering(1); // 需要1个quota}
}

Funnel 对象的 make_space 方法是漏斗算法的核心,其在每次灌水前都会被调用以触发漏水,给漏斗腾出空间来。能腾出多少空间取决于过去了多久以及流水的速率。Funnel 对象占据的空间大小不再和行为的频率成正比,它的空间占用是一个常量。

问题来了,分布式的漏斗算法该如何实现?能不能使用 Redis 的基础数据结构来搞定?

我们观察 Funnel 对象的几个字段,我们发现可以将 Funnel 对象的内容按字段存储到一个 hash 结构中,灌水的时候将 hash 结构的字段取出来进行逻辑运算后,再将新值回填到 hash 结构中就完成了一次行为频度的检测。

但是有个问题,我们无法保证整个过程的原子性。从 hash 结构中取值,然后在内存里运算,再回填到 hash 结构,这三个过程无法原子化,意味着需要进行适当的加锁控制。而一旦加锁,就意味着会有加锁失败,加锁失败就需要选择重试或者放弃。

如果重试的话,就会导致性能下降。如果放弃的话,就会影响用户体验。同时,代码的复杂度也跟着升高很多。这真是个艰难的选择,我们该如何解决这个问题呢?Redis-Cell 救星来了!

Redis-Cell

Redis 4.0 提供了一个限流 Redis 模块,它叫 redis-cell。该模块也使用了漏斗算法,并提供了原子的限流指令。有了这个模块,限流问题就非常简单了。

该模块只有1条指令cl.throttle,它的参数和返回值都略显复杂,接下来让我们来看看这个指令具体该如何使用。

> cl.throttle laoqian:reply 15 30 60 1▲     ▲  ▲  ▲  ▲|     |  |  |  └───── need 1 quota (可选参数,默认值也是1)|     |  └──┴─────── 30 operations / 60 seconds 这是漏水速率|     └───────────── 15 capacity 这是漏斗容量└─────────────────── key laoqian

上面这个指令的意思是允许的频率为每 60s 最多 30 次(漏水速率),漏斗的初始容量为 15,也就是说一开始可以连续回复 15 个帖子,然后才开始受漏水速率的影响。我们看到这个指令中漏水速率变成了 2 个参数,替代了之前的单个浮点数。用两个参数相除的结果来表达漏水速率相对单个浮点数要更加直观一些。

> cl.throttle laoqian:reply 15 30 60
1) (integer) 0   # 0 表示允许,1表示拒绝
2) (integer) 15  # 漏斗容量capacity
3) (integer) 14  # 漏斗剩余空间left_quota
4) (integer) -1  # 如果拒绝了,需要多长时间后再试(漏斗有空间了,单位秒)
5) (integer) 2   # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

在执行限流指令时,如果被拒绝了,就需要丢弃或重试。cl.throttle 指令考虑的非常周到,连重试时间都帮你算好了,直接取返回结果数组的第四个值进行 sleep 即可,如果不想阻塞线程,也可以异步定时任务来重试。

Redis 的 漏斗限流相关推荐

  1. redis笔记2 限流、GeoHash和Scan

    限流 简单限流 简单限流的思路是,在规定的时间窗口内,给出规定的最大操作数量限制.使用zset结构作为一个用户行为的记录.zset的value和score都用来表示操作的时间戳.每次操作前,先把操作时 ...

  2. 【Redis核心原理和应用实践】应用 7:一毛不拔 —— 漏斗限流

    漏斗限流是最常用的限流方法之一,顾名思义,这个算法的灵感源于漏斗(funnel)的结构. 漏斗的容量是有限的,如果将漏嘴堵住,然后一直往里面灌水,它就会变满,直至再也装不进去.如果将漏嘴放开,水就会往 ...

  3. java redis 限流_Redis——限流算法之滑动窗口、漏斗限流的原理及java实现

    限流的意义 限流一般是指在一个时间窗口内对某些操作请求的数量进行限制,比如一个论坛限制用户每秒钟只能发一个帖子,每秒钟只能回复5个帖子.限流可以保证系统的稳定,限制恶意请求,防止因为流量暴增导致系统瘫 ...

  4. Redis(六)——限流算法:滑动时间窗口限流和漏斗限流

    本文主要总结自<redis深度历险> 限流的意义 限流一般是指在一个时间窗口内对某些操作请求的数量进行限制,比如一个论坛限制用户每秒钟只能发一个帖子,每秒钟只能回复5个帖子.限流可以保证系 ...

  5. redis实现简单限流

    首先我们给出限流的定义: 1. 限定某个行为在指定时间内被允许的最大次数 2. 限定某个用户的某个行为在指定时间内被允许的最大次数 其实二者差不多,前面一句是限定所有人,后面的是限定了每个用户 注意 ...

  6. Redis实现分布式限流(学习笔记

    Redis实现分布式限流(学习笔记2022.07.09) 前言: 以下实现都是基于: spring-boot-starter-web + spring-boot-starter-data-redis ...

  7. Java实现漏斗限流算法

    前言 最近在学习老钱的<Redis深度历险:核心原理与应用实践>,其深入浅出的讲解,让我爱不释手.其中讲到了漏斗限流,在Redis中可以使用 Redis-Cell 模块来做基于Redis的 ...

  8. 基于Redis的分布式限流详解

    前言 Redis除了能用作缓存外,还有很多其他用途,比如分布式锁,分布式限流,分布式唯一主键等,本文将和大家分享下基于Redis分布式限流的各种实现方案. 一.为什么需要限流 用最简单的话来说:外部请 ...

  9. Redis 如何实现限流功能?

    "限流"这种事在生活中很常见,比如逢年过节时景点的限流,还有工作日的车辆单双号限流等,有人可能会问为什么要限流?我既然买了车子你还不让我上路开?还有我倒景点买了门票,景点不是能赚更 ...

最新文章

  1. 上汽接入Momenta飞轮,成为中国第一个落地RoboTaxi的车企
  2. C++中的值初始化和默认初始化
  3. monk js_使用Monk AI进行手语分类
  4. Java内存泄露8种情况的总结
  5. Python机器学习:多项式回归与模型泛化004为什么需要训练数据集和测试数据集
  6. logrotatesyslog
  7. easyui datagrid添加合计行
  8. x射线计算机断层成像_医疗保健中的深度学习-X射线成像(第4部分-类不平衡问题)...
  9. C语言程序设计-基础
  10. Instagram帖子类型及标题撰写技巧
  11. 金边富贵竹的养护方法
  12. 人机智能的逻辑哲学论
  13. logstash的dissect匹配字符串内置双引号时需要注意的问题
  14. 谷俊丽:从特斯拉到小鹏汽车,同样是智能车,不同基因的自动驾驶
  15. Collectors简单使用
  16. python中的整型是什么意思_Python中整型的基本介绍(代码示例)
  17. 最简单的makefile编写练习【main.c addc.c addc.h makefile】
  18. micropython刷固件
  19. 知到网课你不知道的毒品真相期末考试单元答案
  20. 网络基本功:三次握手及四次挥手

热门文章

  1. 空间存储公链(SSCC):共建数字化特性的共识价值和存储网络
  2. html如实现留言板功能,JS实现留言板功能
  3. 【技术分享】华为叶涛:数据库事务处理的原理与实例剖析
  4. kivy打包问题汇总
  5. 基于SSM的图书馆座位预约管理系统占座系统-java图书馆座位预约管理系统占座系统...
  6. 稳控科技水库水坝监测系统解决方案
  7. 期货开户中常见问题汇总
  8. 看完了 世界是平的,很好的书
  9. 获取小数点前面的数字
  10. Label Assignment