Redis深度历险-读书笔记(4)
简单限流
限流
- 由于系统的处理能力是有限的,当外部的请求流量不断增长时,为了保证服务的可用性,需要阻止计划外的请求继续对系统施压。
- 除了控制流量,限流还有一个用处:控制用户行为,避免垃圾请求。例如社区中的用户,回帖、回复、点赞等行为都要受到控制,如果在规定时间内的某种操作超过了设定的阈值,那这种操作就是非法行为,需要对其进行相应的处理
如何使用Redis来实现简单限流
- 策略:系统要限定用户的某个行为在指定的时间里只能允许发生 N 次
- 接口定义:
# 指定用户 user_id 的某个行为 action_key 在特定的时间内 period 只允许发生一定的次数 max_count
def is_action_allowed(user_id, action_key, period, max_count):
return True
# 调用这个接口 , 一分钟内只允许最多回复 5 个帖子
can_reply = is_action_allowed("user", "reply", 60, 5)
if can_reply:
do_reply()
else:
raise ActionThresholdOverflow()
方案
限流需求需要得到在60秒内用户的行为次数信息,我们通过构造一个滑动时间窗口,获取这段时间内用户的行为次数来判断是否触发限流,窗口的长度60秒,窗口之外的数据都可以忽略。在Redis中,可以使用zset
的score
值来构造,用一个zset
结构记录用户的行为历史,每一个行为都会作为zset
中的一个key
保存下来。同一个用户同一种行为用一个zset
记录,通过统计滑动窗口内的行为数量与阈值 max_count 进行比较就可以得出当前的行为是否符合限流条件。
为了节省空间,我们只需要保留时间窗口内的行为记录,如果记录为空,那么这个 zset 就可以从内存中移除,不再占用空间。
# 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("user", "reply", 60, 5)
每一个行为到来时,都维护一次时间窗口。将时间窗口外的记录全部清理掉,只保留窗口内的记录。zset 集合中只有 score
值非常重要,value
值没有特别的意义,只需要保证它是唯一的就可以了。
不足
因为简单限流要记录时间窗口内所有的行为记录,如果这个量很大,比如限定 60s 内操作不得超过 100w 次这样的参数,它这样做会消耗大量的存储空间,是不合适的。
Redis中的Info指令
在使用 Redis 时,通过
Info
指令,可以清晰地知道 Redis 内部一系列运行参数。-
Info 指令显示的信息非常繁多,分为 9 大块,每个块都有非常多的参数,这 9 个块分别是:
- Server 服务器运行的环境参数
- Clients 客户端相关信息
- Memory 服务器运行内存统计数据
- Persistence 持久化信息
- Stats 通用统计数据
- Replication 主从复制相关信息
- CPU CPU 使用情况
- Cluster 集群信息
- KeySpace 键值对统计数量信息
Info 可以一次性获取所有的信息,也可以按块取信息。
# 获取所有信息
> info
# 获取内存相关信息
> info memory
# 获取复制相关信息
> info replication
一些常用指令
- 查看每秒操作数
# ops_per_sec: operations per second,也就是每秒操作数
> redis-cli info stats |grep ops
instantaneous_ops_per_sec:789
ops 是 789,也就是所有客户端每秒会发送 789 条指令到服务器执行。极限情况下,Redis 可以每秒执行 10w 次指令,CPU 几乎完全榨干。如果 qps 过高,可以考虑通过 monitor 指令快速观察一下究竟是哪些 key 访问比较频繁,从而在相应的业务上进行优化,以减少 IO 次数。
- 查看连接了多少客户端
> redis-cli info clients
# Clients
connected_clients:124 # 这个就是正在连接的客户端数量
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0
- 查看内存占用
> redis-cli info memory | grep used | grep human
used_memory_human:827.46K # 内存分配器 (jemalloc) 从操作系统分配的内存总量
used_memory_rss_human:3.61M # 操作系统看到的内存占用 ,top 命令看到的内存
used_memory_peak_human:829.41K # Redis 内存消耗的峰值
used_memory_lua_human:37.00K # lua 脚本引擎占用的内存大小