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

在线工具站
  • 推荐一个程序员在线工具站:程序员常用工具http://cxytools.com),有时间戳、JSON格式化、文本对比、HASH生成、UUID生成等常用工具,效率加倍嘎嘎好用。
程序员资料站
小报童专栏精选Top100
  • 推荐一个小报童专栏导航站:小报童精选Top100http://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 脚本实现

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 次请求:

python 复制代码
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 脚本实现

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:

python 复制代码
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 的高性能和丰富功能使其成为实现分布式限流的理想选择。

相关推荐
自不量力的A同学5 分钟前
Redisson 4.2.0 发布,官方推荐的 Redis 客户端
数据库·redis·缓存
fengxin_rou11 分钟前
[Redis从零到精通|第四篇]:缓存穿透、雪崩、击穿
java·redis·缓存·mybatis·idea·多线程
是阿楷啊1 小时前
Java大厂面试场景:音视频场景中的Spring Boot与微服务实战
spring boot·redis·spring cloud·微服务·grafana·prometheus·java面试
笨蛋不要掉眼泪1 小时前
Redis哨兵机制全解析:原理、配置与实战故障转移演示
java·数据库·redis·缓存·bootstrap
ALex_zry13 小时前
Redis Cluster 分布式缓存架构设计与实践
redis·分布式·缓存
乔江seven17 小时前
【Flask 进阶】3 从同步到异步:基于 Redis 任务队列解决 API 高并发与长耗时任务阻塞
redis·python·flask
这周也會开心17 小时前
Redis与MySQL回写中的数据类型存储设计
数据库·redis·mysql
shuair18 小时前
redis缓存预热、缓存击穿、缓存穿透、缓存雪崩
redis·spring·缓存
AI_567819 小时前
Postman接口测试提速技巧:批量请求+智能断言实践
测试工具·lua·postman
shuair19 小时前
guava布隆过滤器及cuckoo过滤器
redis·guava