使用 Redis + Lua 实现分布式限流

在线工具站
  • 推荐一个程序员在线工具站:程序员常用工具(http://cxytools.com),有时间戳、JSON格式化、文本对比、HASH生成、UUID生成等常用工具,效率加倍嘎嘎好用。
程序员资料站
  • 推荐一个程序员编程资料站:程序员的成长之路(http://cxyroad.com),收录了一些列的技术教程、各大面试专题,还有常用开发工具的教程。
小报童专栏精选Top100
  • 推荐一个小报童专栏导航站:小报童精选Top100(http://xbt100.top),收录了生财有术项目精选、AI海外赚钱、纯银的产品分析等专栏,陆续会收录更多的专栏,欢迎体验~

分布式系统中,限流是保证系统稳定性和可用性的重要措施之一。通过限流,可以避免流量突增对系统带来的冲击,保证系统的高可用性。Redis 作为一个高性能的内存数据库,不仅可以用来缓存数据,还可以用来实现分布式限流。而通过结合 Lua 脚本,可以更高效地实现复杂的限流逻辑。本文将介绍如何使用 Redis 和 Lua 脚本实现分布式限流。

为什么选择 Redis + Lua 实现限流

Redis 是一个高性能的键值存储系统,具有以下特点使其成为实现分布式限流的理想选择:

  1. 高性能:Redis 以内存存储为基础,读写速度极快,能够处理高并发请求。
  2. 丰富的数据结构:Redis 提供了多种数据结构,如字符串、列表、集合、哈希等,便于灵活实现各种限流策略。
  3. 持久化支持:Redis 支持数据持久化,可以在重启后恢复数据。
  4. Lua 脚本支持:Redis 内置 Lua 脚本引擎,可以在一次请求中执行一系列操作,保证原子性,避免并发问题。

限流策略介绍

常见的限流策略包括以下几种:

  1. 固定窗口限流:在固定时间窗口内限制请求数量,超过限制则拒绝请求。
  2. 滑动窗口限流:相对于固定窗口,滑动窗口限流更加精细化,可以更平滑地限制请求。
  3. 令牌桶算法:以固定速率生成令牌,请求需要消耗令牌,没有令牌则拒绝请求。
  4. 漏桶算法:以固定速率处理请求,超过速率的请求则排队等待。

本文将以固定窗口限流和令牌桶算法为例,介绍如何使用 Redis + Lua 实现分布式限流。

实现固定窗口限流

固定窗口限流是一种简单的限流策略,在固定时间窗口内限制请求数量。可以通过 Redis 的 INCREXPIRE 命令配合 Lua 脚本实现。

Lua 脚本实现

local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key) or "0")

if current + 1 > limit then
    return 0
else
    redis.call("INCRBY", key, 1)
    redis.call("EXPIRE", key, ARGV[2])
    return 1
end

使用示例

假设限流策略为每分钟最多允许 100 次请求:

import redis

client = redis.StrictRedis(host='localhost', port=6379, db=0)

# Lua script for fixed window rate limiting
lua_script = """
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local current = tonumber(redis.call('GET', key) or "0")

if current + 1 > limit then
    return 0
else
    redis.call("INCRBY", key, 1)
    redis.call("EXPIRE", key, ARGV[2])
    return 1
end
"""

rate_limit_script = client.register_script(lua_script)

key = "rate_limit:fixed_window"
limit = 100
expiry = 60  # time window in seconds

result = rate_limit_script(keys=[key], args=[limit, expiry])
if result == 1:
    print("Request allowed")
else:
    print("Request rate-limited")

实现令牌桶算法限流

令牌桶算法是另一种常见的限流策略,可以较好地平衡突发流量和长期流量。通过定期向桶中添加令牌,请求时消耗令牌,没有令牌则拒绝请求。

Lua 脚本实现

local key = KEYS[1]
local rate = tonumber(ARGV[1])
local burst = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local fill_time = burst / rate
local ttl = math.floor(fill_time * 2)
local last_tokens = tonumber(redis.call("GET", key .. ":tokens"))
if last_tokens == nil then
    last_tokens = burst
end
local last_refreshed = tonumber(redis.call("GET", key .. ":timestamp"))
if last_refreshed == nil then
    last_refreshed = 0
end

local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(burst, last_tokens + (delta * rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
    new_tokens = filled_tokens - requested
end

redis.call("SETEX", key .. ":tokens", ttl, new_tokens)
redis.call("SETEX", key .. ":timestamp", ttl, now)

return allowed and 1 or 0

使用示例

假设限流策略为每秒生成 10 个令牌,桶容量为 20:

import redis
import time

client = redis.StrictRedis(host='localhost', port=6379, db=0)

# Lua script for token bucket rate limiting
lua_script = """
local key = KEYS[1]
local rate = tonumber(ARGV[1])
local burst = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local fill_time = burst / rate
local ttl = math.floor(fill_time * 2)
local last_tokens = tonumber(redis.call("GET", key .. ":tokens"))
if last_tokens == nil then
    last_tokens = burst
end
local last_refreshed = tonumber(redis.call("GET", key .. ":timestamp"))
if last_refreshed == nil then
    last_refreshed = 0
end

local delta = math.max(0, now - last_refreshed)
local filled_tokens = math.min(burst, last_tokens + (delta * rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
if allowed then
    new_tokens = filled_tokens - requested
end

redis.call("SETEX", key .. ":tokens", ttl, new_tokens)
redis.call("SETEX", key .. ":timestamp", ttl, now)

return allowed and 1 or 0
"""

token_bucket_script = client.register_script(lua_script)

key = "rate_limit:token_bucket"
rate = 10  # tokens per second
burst = 20  # bucket capacity
now = int(time.time())
requested = 1

result = token_bucket_script(keys=[key], args=[rate, burst, now, requested])
if result == 1:
    print("Request allowed")
else:
    print("Request rate-limited")

优化和扩展

监控与报警

为了及时发现限流策略的异常情况,可以结合监控工具如 Prometheus 和 Grafana,对 Redis 的限流情况进行监控和报警。通过定期统计限流命中次数和请求数量,可以分析限流效果,并及时调整限流策略。

动态调整限流策略

在实际应用中,可能需要根据流量情况动态调整限流策略。可以结合 Redis 的 Pub/Sub 功能,通过发布和订阅消息来实时调整限流参数。

集群环境中的限流

在 Redis 集群环境中,可以将限流脚本分发到各个节点,确保限流逻辑在各个节点上执行。可以使用 Redis 的主从复制和分片机制,实现全局的分布式限流。

总结

通过结合 Redis 和 Lua 脚本,可以高效实现多种限流策略,如固定窗口限流和令牌桶算法。Redis 的高性能和丰富功能使其成为实现分布式限流的理想选择。

  • 33
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
实现分布式限流可以使用 RedisLua 脚本来完成。以下是可能的实现方案: 1. 使用 Redis 的 SETNX 命令来实现基于令牌桶算法的限流 令牌桶算法是一种常见的限流算法,它可以通过令牌的放置和消耗来控制流量。在 Redis 中,我们可以使用 SETNX 命令来实现令牌桶算法。 具体实现步骤如下: - 在 Redis 中创建一个有序集合,用于存储令牌桶的令牌数量和时间戳。 - 每当一个请求到达时,我们首先获取当前令牌桶中的令牌数量和时间戳。 - 如果当前时间戳与最后一次请求的时间戳之差大于等于令牌桶中每个令牌的发放时间间隔,则将当前时间戳更新为最后一次请求的时间戳,并且将令牌桶中的令牌数量增加相应的数量,同时不超过最大容量。 - 如果当前令牌桶中的令牌数量大于等于请求需要的令牌数量,则返回 true 表示通过限流,将令牌桶中的令牌数量减去请求需要的令牌数量。 - 如果令牌桶中的令牌数量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现令牌桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 令牌桶的容量 local capacity = tonumber(ARGV[1]) -- 令牌的发放速率 local rate = tonumber(ARGV[2]) -- 请求需要的令牌数量 local tokens = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取当前令牌桶中的令牌数量和时间戳 local bucket = redis.call('ZREVRANGEBYSCORE', key, now, 0, 'WITHSCORES', 'LIMIT', 0, 1) -- 如果令牌桶为空,则初始化令牌桶 if not bucket[1] then redis.call('ZADD', key, now, capacity - tokens) return 1 end -- 计算当前令牌桶中的令牌数量和时间戳 local last = tonumber(bucket[2]) local tokensInBucket = tonumber(bucket[1]) -- 计算时间间隔和新的令牌数量 local timePassed = now - last local newTokens = math.floor(timePassed * rate) -- 更新令牌桶 if newTokens > 0 then tokensInBucket = math.min(tokensInBucket + newTokens, capacity) redis.call('ZADD', key, now, tokensInBucket) end -- 检查令牌数量是否足够 if tokensInBucket >= tokens then redis.call('ZREM', key, bucket[1]) return 1 else return 0 end ``` 2. 使用 RedisLua 脚本来实现基于漏桶算法的限流 漏桶算法是另一种常见的限流算法,它可以通过漏桶的容量和漏水速度来控制流量。在 Redis 中,我们可以使用 Lua 脚本来实现漏桶算法。 具体实现步骤如下: - 在 Redis 中创建一个键值对,用于存储漏桶的容量和最后一次请求的时间戳。 - 每当一个请求到达时,我们首先获取当前漏桶的容量和最后一次请求的时间戳。 - 计算漏水速度和漏水的数量,将漏桶中的容量减去漏水的数量。 - 如果漏桶中的容量大于等于请求需要的容量,则返回 true 表示通过限流,将漏桶中的容量减去请求需要的容量。 - 如果漏桶中的容量不足,则返回 false 表示未通过限流。 下面是使用 RedisLua 脚本实现漏桶算法的示例代码: ```lua -- 限流的 key local key = KEYS[1] -- 漏桶的容量 local capacity = tonumber(ARGV[1]) -- 漏水速度 local rate = tonumber(ARGV[2]) -- 请求需要的容量 local size = tonumber(ARGV[3]) -- 当前时间戳 local now = redis.call('TIME')[1] -- 获取漏桶中的容量和最后一次请求的时间戳 local bucket = redis.call('HMGET', key, 'capacity', 'last') -- 如果漏桶为空,则初始化漏桶 if not bucket[1] then redis.call('HMSET', key, 'capacity', capacity, 'last', now) return 1 end -- 计算漏水的数量和漏桶中的容量 local last = tonumber(bucket[2]) local capacityInBucket = tonumber(bucket[1]) local leak = math.floor((now - last) * rate) -- 更新漏桶 capacityInBucket = math.min(capacity, capacityInBucket + leak) redis.call('HSET', key, 'capacity', capacityInBucket) redis.call('HSET', key, 'last', now) -- 检查容量是否足够 if capacityInBucket >= size then return 1 else return 0 end ``` 以上是使用 RedisLua 脚本实现分布式限流的两种方案,可以根据实际需求选择适合的方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

良月柒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值