普通限流zset

通过zset维护一个顺从地为时间戳的滑动窗口,value保证唯一性即可。这种限流比较浪费内存空间。

# coding: utf8
import time
import redis

client = redis.StrictRedis()

def is_action_allowed(user_id, action_key, period, max_count):
    key = 'hist:%s:%s' % (user_id, action_key)
    now_ts = int(time.time() * 1000)  # 毫秒时间戳
    with client.pipeline() as pipe:  # client 是 StrictRedis 实例
        # 记录行为
        pipe.zadd(key, now_ts, now_ts)  # value 和 score 都使用毫秒时间戳
        # 移除时间窗口之前的行为记录,剩下的都是时间窗口内的
        pipe.zremrangebyscore(key, 0, now_ts - period * 1000)
        # 获取窗口内的行为数量
        pipe.zcard(key)
        # 设置 zset 过期时间,避免冷用户持续占用内存
        # 过期时间应该等于时间窗口的长度,再多宽限 1s
        pipe.expire(key, period + 1)
        # 批量执行
        _, _, current_count, _ = pipe.execute()
    # 比较数量是否超标
    return current_count <= max_count

for i in range(20):
    print is_action_allowed("laoqian", "reply", 60, 5)

流程就是每次用户请求过来,维护一次时间窗口,将窗口外的记录全部清理掉,只保留窗口内的记录,zset中score保存时间戳,value唯一就行,当zset超过限制就会返回false

分布式限流redis-cell

redis 4.0后提供了一个限流模块redis-cell,该模块采用rust编写的基于令牌桶算法的限流模块,并提供了原子操作的限流功能,并允许突发流量,可以很方便的应用于分布式环境中。

令牌桶限流算法

令牌桶算法的原理是定义一个按一定速率产生token的桶,每次去桶中申请token,若桶中没有足够的token则申请失败,否则成功。在请求不多的情况下,桶中的token基本会饱和,此时若流量激增,并不会马上拒绝请求,所以这种算法允许一定的流量激增。

定义一个令牌桶,其拥有几个关键属性

  • 桶容量
  • 令牌产生速率
  • 当前桶中令牌数
  • 最近一次取(生成)令牌时间

从桶中申请令牌,有两个关键动作

  • 根据上一次生成令牌到现在的时间,及生成速率计算出当前令牌桶中的令牌数
  • 判断令牌桶中是否有足够的令牌,并返回结果。

安装方法

官方提供了安装包和源码编译两种方式,源码需要安装rust环境

  • 根据操作系统下载安装包,注意是运行redis-server的操作系统环境
  • 将下载的文件解压到可以访问到的路径
  • 进入redis-cli,执行命令module load /path/libredis_cell.so,安装报错的话看redis-server log

命令

# lzw:reply key名称,15 capacity 漏斗容量,30 operations / 60 seconds 漏水速率,1 每次取出的个数
cl.throttle lzw:reply 15 30 60 1
1) (integer) 0 # 0表示允许,1表示拒绝
2) (integer) 16 # 漏斗容量capacity
3) (integer) 2 # 漏斗剩余空间 left_quota
4) (integer) 15 # 如果拒绝了,需要多长时间再试(单位秒)
5) (integer) 27 # 多长时间后,漏斗完全空出来(left_quota==capacity,单位秒)

上面指令的意思是允许 用户lzw回复消息行为的频率为每60s最多30次(漏斗速率),漏斗初始容量为15,也就是一开始可以回复15次,然后才开始受漏水速率影响。

应用场景

需要限流的场景,例如控制用户行为,避免垃圾请求,比如UGC社区用户发帖、回复、点赞等行为受控。

GeoHash算法

应用场景

用来实现摩拜单车【附近的Mobike】、美团和饿了么【附近的餐馆】等功能

参考

分布式限流 redis-cell