Redis过期时间及其应用(频控/缓存)

Tags
缓存设计
流量控制
Redis
数组
CreatedTime
Aug 24, 2022 03:01 PM
Slug
UpdatedTime
Last updated August 24, 2022

命令

获取过期时间:TTL keyName。key 不存在时,返回-2;key 永久有效,返回-1。
设置过期时间(单位为秒):EXPIRE keyName seconds
更精确地设置过期时间(单位为毫秒):PEXPIRE keyName millSeconds
取消过期时间,设置为永久:PERSIST keyName

应用:频率控制

场景:为了减轻服务器的压力,需要限制每个用户(以 IP 计)一段时间的最大访问量。与时间有关的操作很容易想到 EXPIRE 命令

简单做法:借助事务+过期时间

每次访问的时候,纪录值+1.
$isKeyExists = EXISTS rate.limiting:$IP if $isKeyExists is 1 $times = INCR rate.limiting:$IP if $times > 100 print 访问频率超过了限制,请稍后再试。 exit else # 必须使用事务。否则如果程序出错,或者物理意外,不执行expire命令,那么纪录值永久存在 # 此时只能访问100次 MULTI INCR rate.limiting:$IP EXPIRE $keyName, 60 EXEC

推荐做法:借助列表

上面代码仍然有个问题:如果一个用户在一分钟的第一秒访问了一次博客,在同一分钟的最后一秒访问了 9 次,又在下一分钟的第一秒访问了 10 次,这样的访问是可以通过现在的访问频率限制的,但实际上该用户在 2 秒内访问了 19 次博客,这与每个用户每分钟只能访问 10 次的限制差距较大
借助列表,可以使控制变得平滑:
# 使用列表 rate.limiting:$IP 纪录最近10次访问时间 $listLength = LLEN rate.limiting:$IP # 当有新的访问进来,如果10次访问没有打满 if $listLength < 10 LPUSH rate.limiting:$IP, now() else $time = LINDEX rate.limiting:$IP, -1 # 最早的时间在60s之前,那么就拦截 if now() - $time < 60 print 访问频率超过了限制,请稍后再试。 # 否则,移除最早的时间,并且将现在访问时间加入list最右侧 else LPUSH rate.limiting:$IP, now() LTRIM rate.limiting:$IP, 0, 9
如果次数到达 10000 次呢? 随着次数增加,list 开销变大,内存开销变大。此时有 2 种解决方法: 1、使用漏桶算法 2、将时间缩小。例如现在是控制 60 秒内 100 次访问,上面就不是比较 now()和 $time 差值小于 60,而是小于 6。依次类推。

应用:缓存

可以实现 LRU。LRU 需要缓存过期时间,以及控制台最大存储上限:
修改配置文件的 maxmemory 参数,限制 Redis 最大可用内存大小(单位是字节),当超出了这个限制时 Redis 会依据 maxmemory-policy 参数指定的策略来删除不需要的键直到 Redis 占用的内存小于指定内存