MST

星途 面试题库

面试题:Redis固定窗口限流窗口切换瞬间的一致性保障

在Redis固定窗口限流机制里,窗口切换瞬间可能出现数据不一致问题,比如新窗口开始时旧窗口的数据还未清理或新窗口计数未正确初始化。请阐述如何通过Redis事务、Lua脚本等技术手段来保证窗口切换瞬间限流数据的一致性和准确性,并给出示例代码。
17.2万 热度难度
数据库Redis

知识考点

AI 面试

面试题答案

一键面试
  1. 使用Redis事务和Lua脚本解决思路
    • Redis事务:通过MULTI、EXEC命令将多个操作包装成一个原子操作,确保在事务执行期间不会被其他客户端打断。但是,Redis事务不保证在事务执行过程中Redis服务器不会崩溃,如果崩溃,事务中的部分命令可能已经执行,部分未执行。
    • Lua脚本:Lua脚本在Redis中是原子执行的,它可以将复杂的逻辑封装在一个脚本中,避免多次往返客户端和服务器之间,同时保证操作的原子性。在处理固定窗口限流机制的窗口切换问题时,Lua脚本可以获取旧窗口的数据,清理旧窗口数据,初始化新窗口计数等一系列操作在一个原子操作中完成。
  2. 示例代码(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脚本原子地处理窗口切换相关的操作,避免了数据不一致问题。