基于redis的简单限流的实现
2020-02-15 本文已影响0人
代码的搬运工
首先我们来看一个常见的、简单的限流策略。系统要限定用户的某个行为在指定的时间里只能允许发生N次,如何使用redis的数据结构来实现这个限流的功能?我们先定义这个接口,理解了这个接口的定义,读者就应该能明白我们期望达到的功能。

这个限流需求中存在一个滑动时间窗口(定宽),想想zset数据结构的score值,是不是可以通过score来圈出这个时间窗口来。我们只需要保留这个时间窗口,窗口之外的数据都可以砍掉。那这个zset的value填什么比较合适呢?它只需要保证唯一性即可,用uuid会比较浪费空间,那就改用毫秒时间戳吧。
如下图所示,用一个zset结构记录用户的行为历史,每一个行为都会作为zset中的一个key保存下来。同一个用户的同一种行为用一个zset记录。

为节省内存,我们只需要保留时间窗口内的行为记录,同时如果用户是冷用户,滑动时间窗口内的行为是空记录,那么这个zset就可以从内存中移除,不用占用空间。
通过统计滑动窗口内的行为数量与阀值max_count进行比较就可以得出当前的行为是否被允许。用代码表示如下:

这段代码还是略显复杂,需要读者花一定的时间好好啃。它的整体思路就是:每一个行为到来时,都维护一次时间窗口。将时间窗口外的记录全部清理掉,只保留窗口内的记录。zset集合中只有score值非常重要,value值没有特别的意义,只需要保证它是唯一的就可以了。
因为这几个连续的redis操作都是针对同一个key的,使用pipeline可以显著提升redis存取效率。但这种方案也有缺点,因为它要记录时间窗口内所有的行为记录,如果这个量很大,比如“限定60s内操作不得超过100万次”之类,它是不适合做这样的限流的,因为会消耗大量的内存空间。