如何用 Redis 实现限流(Rate Limiting)?(Logstash 插件实战)

Redis 实现限流及 Logstash 插件实战

如何用 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
用户IDrate_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 模型动态调整限流阈值

八、最佳实践

  1. 出错时默认放行:避免 Redis 故障导致日志中断
  2. 使用连接池:提升性能
  3. 合理设置 limit:根据业务需求调整
  4. 添加监控:观察限流触发频率
  5. 日志记录:记录超限事件用于审计

九、参考资源


十、结语

通过 Redis 实现限流,你可以:

  • ✅ 保护后端系统不被压垮
  • ✅ 检测异常行为(如暴力破解)
  • ✅ 控制成本(如 API 调用配额)

结合 Logstash 插件机制,能将限流能力无缝集成到日志处理管道中。

🔑 核心:用 Redis 的原子操作 + TTL 实现高效计数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值