如何用 Redis 实现限流(Rate Limiting)?(Logstash 插件实战)
在日志处理系统中,限流(Rate Limiting) 是一项重要的安全与稳定性机制。它可以防止:
- 恶意用户高频刷接口
- 日志风暴导致系统过载
- 外部 API 被超额调用
Redis 凭借其 高性能、原子操作、过期机制(TTL),是实现限流的最佳选择。
本文将详细介绍 基于 Redis 的多种限流算法,并提供 Logstash 自定义插件实现示例,支持 IP、用户、字段级限流。
一、常见的限流算法
| 算法 | 说明 | 适用场景 |
|---|---|---|
| ✅ 固定窗口(Fixed Window) | 每 N 秒最多 M 次 | 简单计数 |
| ✅ 滑动窗口(Sliding Window) | 更精确控制流量 | 高精度限流 |
| ✅ 令牌桶(Token Bucket) | 平滑限流,支持突发 | API 网关 |
| ✅ 漏桶(Leaky Bucket) | 恒定速率处理 | 流量整形 |
本文以 固定窗口 和 滑动窗口 为主,适合 Logstash 场景。
二、限流策略设计
2.1 限流维度(Key)
| 维度 | Redis Key 示例 |
|---|---|
| IP 地址 | rate_limit:ip:192.168.1.100 |
| 用户ID | rate_limit:user:1001 |
| 接口路径 | rate_limit:path:/api/login |
| 组合维度 | rate_limit:ip_user:192.168.1.100:1001 |
2.2 限流规则配置
# 插件配置
redis_ratelimit {
source => "clientip" # 从哪个字段提取 key
limit => 100 # 每 60 秒最多 100 次
window => 60 # 时间窗口(秒)
redis_host => "127.0.0.1"
redis_port => 6379
block_duration => 300 # 超限后封禁时间(可选)
}
三、实战:编写 Logstash 限流插件
插件功能:
- 检查事件是否超出限流规则
- 超限时自动打标签(如
_ratelimited) - 支持可配置的维度和阈值
3.1 插件结构
logstash-filter-redis_ratelimit/
├── lib/logstash/filters/redis_ratelimit.rb
└── logstash-filter-redis_ratelimit.gemspec
3.2 核心代码:redis_ratelimit.rb
# encoding: utf-8
require "logstash/filters/base"
require "logstash/namespace"
require "connection_pool"
require "redis"
class LogStash::Filters::RedisRatelimit < LogStash::Filters::Base
config_name "redis_ratelimit"
# 插件参数
config :source, :validate => :string, :required => true
config :limit, :validate => :number, :required => true
config :window, :validate => :number, :default => 60
config :block_duration, :validate => :number, :default => 0 # 封禁时间(0=不禁)
config :redis_host, :validate => :string, :default => "127.0.0.1"
config :redis_port, :validate => :number, :default => 6379
config :redis_db, :validate => :number, :default => 0
config :redis_password, :validate => :password
config :pool_size, :validate => :number, :default => 5
config :pool_timeout, :validate => :number, :default => 5
config :tag_on_ratelimit, :validate => :string, :default => "_ratelimited"
def register
@logger.info("Redis Rate Limiter initialized",
:limit => @limit,
:window => @window,
:source => @source)
# 创建 Redis 连接池
@redis_pool = ConnectionPool.new(size: @pool_size, timeout: @pool_timeout) do
Redis.new(
host: @redis_host,
port: @redis_port,
db: @redis_db,
password: @redis_password&.value,
timeout: 5
)
end
end
def filter(event)
key = event.get(@source)
return unless key
# 构建 Redis Key
redis_key = "rate_limit:#{key}"
begin
count = @redis_pool.with do |redis|
# 使用 INCR + EXPIRE 实现固定窗口限流
pipe = redis.pipelined do
redis.incr(redis_key)
redis.ttl(redis_key)
end
current_count = pipe[0]
ttl = pipe[1]
# 第一次计数,设置过期时间
if ttl == -1 || ttl == -2
redis.expire(redis_key, @window)
end
current_count
end
if count > @limit
# 超限处理
event.tag(@tag_on_ratelimit)
if @block_duration > 0
# 可选:封禁一段时间
@redis_pool.with do |redis|
redis.setex("#{redis_key}:blocked", @block_duration, "1")
end
end
@logger.warn("Rate limit exceeded",
:key => key,
:count => count,
:limit => @limit,
:window => @window)
else
filter_matched(event)
end
rescue => e
@logger.warn("Redis rate limit error", :error => e.message)
# 出错时放行,避免阻塞日志流
filter_matched(event)
end
end
def close
if @redis_pool
@redis_pool.shutdown do |redis|
redis.quit rescue nil
end
end
end
end
四、高级限流算法实现
4.1 滑动窗口限流(基于有序集合)
def sliding_window_check(redis, key, limit, window)
now = Time.now.to_i
redis.zremrangebyscore(key, 0, now - window) # 清理旧记录
redis.pipelined do
redis.zcard(key) # 当前请求数
redis.zadd(key, now, "req_#{now}_#{rand(1000)}")
redis.expire(key, window)
end[0]
end
更精确,但消耗更多内存。
4.2 令牌桶算法(Token Bucket)
def token_bucket(redis, key, capacity, rate_per_sec, now)
# 获取上次更新时间和令牌数
data = redis.hmget(key, "last_time", "tokens")
last_time = data[0] ? data[0].to_f : now
tokens = data[1] ? data[1].to_f : capacity
# 补充令牌
tokens = [capacity, tokens + (now - last_time) * rate_per_sec].min
if tokens >= 1
tokens -= 1
redis.pipelined do
redis.hset(key, "tokens", tokens)
redis.hset(key, "last_time", now)
redis.expire(key, 3600) # 1小时过期
end
true # 允许通过
else
false # 拒绝
end
end
适合平滑限流,支持突发流量。
五、配置示例(logstash.conf)
filter {
# 限制每个 IP 每分钟最多 100 条日志
redis_ratelimit {
source => "clientip"
limit => 100
window => 60
redis_host => "redis.internal"
tag_on_ratelimit => "_flood_attack"
}
# 如果被限流,丢弃或告警
if "_flood_attack" in [tags] {
# 可选:丢弃
# drop { }
# 可选:发送告警
http {
url => "https://oapi.dingtalk.com/robot/send?access_token=xxx"
http_method => "post"
format => "json"
content_type => "application/json"
message => '{"msgtype": "text", "text": {"content": "检测到日志洪水攻击: %{clientip}"}}'
}
}
}
六、性能优化建议
| 优化项 | 建议 |
|---|---|
| 连接池 | 使用 connection_pool 复用连接 |
| Pipeline | 使用 pipelined 减少网络往返 |
| Key 设计 | 避免 key 过长或过深 |
| TTL | 合理设置过期时间,避免内存泄漏 |
| 监控 | 监控 rate_limit:* key 数量和增长 |
七、应用场景
| 场景 | 配置方式 |
|---|---|
| 防日志洪水 | source: clientip, limit: 100/min |
| API 调用限流 | source: user_id, limit: 1000/hour |
| 登录失败锁定 | source: username, block_duration: 300 |
| 异常行为检测 | 结合 ML 模型动态调整限流阈值 |
八、最佳实践
- ✅ 出错时默认放行:避免 Redis 故障导致日志中断
- ✅ 使用连接池:提升性能
- ✅ 合理设置
limit:根据业务需求调整 - ✅ 添加监控:观察限流触发频率
- ✅ 日志记录:记录超限事件用于审计
九、参考资源
- Redis 限流指南:https://redis.io/commands/incr/
- 滑动窗口实现:https://redis.io/commands/zadd/
- Token Bucket 算法:https://en.wikipedia.org/wiki/Token_bucket
十、结语
通过 Redis 实现限流,你可以:
- ✅ 保护后端系统不被压垮
- ✅ 检测异常行为(如暴力破解)
- ✅ 控制成本(如 API 调用配额)
结合 Logstash 插件机制,能将限流能力无缝集成到日志处理管道中。
🔑 核心:用 Redis 的原子操作 + TTL 实现高效计数
Redis 实现限流及 Logstash 插件实战

821

被折叠的 条评论
为什么被折叠?



