面试题答案
一键面试- 使用Redis事务和Lua脚本解决思路:
- Redis事务:通过MULTI、EXEC命令将多个操作包装成一个原子操作,确保在事务执行期间不会被其他客户端打断。但是,Redis事务不保证在事务执行过程中Redis服务器不会崩溃,如果崩溃,事务中的部分命令可能已经执行,部分未执行。
- Lua脚本:Lua脚本在Redis中是原子执行的,它可以将复杂的逻辑封装在一个脚本中,避免多次往返客户端和服务器之间,同时保证操作的原子性。在处理固定窗口限流机制的窗口切换问题时,Lua脚本可以获取旧窗口的数据,清理旧窗口数据,初始化新窗口计数等一系列操作在一个原子操作中完成。
- 示例代码(Lua脚本示例):
-- KEYS[1] 为限流窗口的键
-- ARGV[1] 为窗口大小(秒)
-- ARGV[2] 为限流阈值
-- 获取当前时间戳(秒)
local current_time = tonumber(redis.call('TIME')[1])
-- 计算当前窗口的开始时间
local window_start = math.floor(current_time / ARGV[1]) * ARGV[1]
-- 获取旧窗口的计数
local old_count = tonumber(redis.call('GET', KEYS[1]))
if old_count == nil then
old_count = 0
end
-- 如果是新窗口,重置计数
if window_start ~= tonumber(redis.call('GET', KEYS[1].. '_window_start')) then
old_count = 0
redis.call('SET', KEYS[1].. '_window_start', window_start)
end
-- 检查是否超过限流阈值
if old_count + 1 > ARGV[2] then
return 0
else
-- 增加计数
redis.call('SET', KEYS[1], old_count + 1)
return 1
end
在使用时,通过Redis客户端执行这个Lua脚本,例如在Python中使用redis - py
库:
import redis
r = redis.Redis(host='localhost', port=6379, db = 0)
script = """
local current_time = tonumber(redis.call('TIME')[1])
local window_start = math.floor(current_time / ARGV[1]) * ARGV[1]
local old_count = tonumber(redis.call('GET', KEYS[1]))
if old_count == nil then
old_count = 0
end
if window_start ~= tonumber(redis.call('GET', KEYS[1].. '_window_start')) then
old_count = 0
redis.call('SET', KEYS[1].. '_window_start', window_start)
end
if old_count + 1 > ARGV[2] then
return 0
else
redis.call('SET', KEYS[1], old_count + 1)
return 1
end
"""
# 调用Lua脚本,keys为键列表,args为参数列表
result = r.eval(script, 1, 'rate_limit_key', 60, 100)
if result == 1:
print('请求通过')
else:
print('请求被限流')
这个示例代码实现了一个简单的固定窗口限流机制,并且在窗口切换瞬间保证了限流数据的一致性和准确性。通过Lua脚本原子地处理窗口切换相关的操作,避免了数据不一致问题。