面试题答案
一键面试数据结构设计
- 使用有序集合(Sorted Set):
- 有序集合可以按照时间戳来排序。每个滑动窗口的请求记录可以以时间戳为分数,唯一标识(如请求ID)为成员。这样可以方便地根据时间范围删除过期数据。例如,假设有一个限流窗口为60秒,每秒记录一次请求,使用
ZADD
命令添加请求记录,ZREM
命令删除过期数据。比如:
ZADD rate_limit:window 1677655200 request_id_1 ZADD rate_limit:window 1677655201 request_id_2
- 要删除60秒前的过期数据,可以使用
ZRANGEBYSCORE
命令获取60秒前的所有记录,然后使用ZREM
批量删除。例如:
local current_time = redis.call('TIME')[1] local old_time = current_time - 60 local keys = redis.call('ZRANGEBYSCORE', 'rate_limit:window', 0, old_time) if #keys > 0 then redis.call('ZREM', 'rate_limit:window', unpack(keys)) end
- 有序集合可以按照时间戳来排序。每个滑动窗口的请求记录可以以时间戳为分数,唯一标识(如请求ID)为成员。这样可以方便地根据时间范围删除过期数据。例如,假设有一个限流窗口为60秒,每秒记录一次请求,使用
- 结合哈希表(Hash):
- 可以使用哈希表存储每个滑动窗口内的请求计数。哈希表的键可以是窗口的标识(如以秒为单位的时间戳),值为请求计数。例如,使用
HSET
命令设置计数:
HSET rate_limit:count 1677655200 10
- 这样在清理过期数据时,除了删除有序集合中的记录,也可以同时删除哈希表中对应的过期窗口计数,减少内存占用。
- 可以使用哈希表存储每个滑动窗口内的请求计数。哈希表的键可以是窗口的标识(如以秒为单位的时间戳),值为请求计数。例如,使用
命令使用频率
- 批量操作:
- 尽量避免频繁的单个删除操作。如上述有序集合中,使用
ZRANGEBYSCORE
获取过期记录后,使用ZREM
命令时,通过unpack
函数将获取的记录列表作为参数批量删除,而不是逐个删除,减少Redis的命令处理负担。
- 尽量避免频繁的单个删除操作。如上述有序集合中,使用
- Lua脚本:
- 将数据清理相关的多个命令封装在Lua脚本中。因为Redis执行Lua脚本是原子性的,并且减少了客户端与服务器之间的通信次数。例如,上述清理有序集合和哈希表过期数据的操作可以封装在一个Lua脚本中:
local current_time = redis.call('TIME')[1] local old_time = current_time - 60 local keys = redis.call('ZRANGEBYSCORE', 'rate_limit:window', 0, old_time) if #keys > 0 then redis.call('ZREM', 'rate_limit:window', unpack(keys)) end local hash_keys = redis.call('HKEYS', 'rate_limit:count') for _, key in ipairs(hash_keys) do if tonumber(key) < old_time then redis.call('HDEL', 'rate_limit:count', key) end end
- 然后在客户端使用
EVAL
命令执行该Lua脚本。
缓存策略
- 分级缓存:
- 可以采用多级缓存策略。例如,在应用层设置一个本地缓存(如Guava Cache)作为一级缓存,先在本地缓存中进行限流判断。如果本地缓存未命中,则再去查询Redis中的滑动窗口数据。这样可以减少对Redis的直接请求,降低Redis的负载。
- 惰性清理:
- 除了定时清理过期数据,可以采用惰性清理策略。当有新的请求进入时,检查滑动窗口内是否有过期数据,如果有则进行清理。这样在高并发情况下,避免了定时清理任务对系统资源的额外占用。不过,惰性清理可能会导致短时间内过期数据占用一定内存,需要根据实际情况权衡。
- 缓存过期策略优化:
- 对于Redis的过期策略,可以采用
volatile - lru
(从已设置过期时间的数据集中挑选最近最少使用的数据淘汰)。这样可以优先淘汰长时间未使用且已设置过期时间的滑动窗口数据,保证Redis内存的合理利用,同时不会影响正常的限流功能。
- 对于Redis的过期策略,可以采用