如何在 Logstash 插件中与 Redis Sentinel / Cluster 模式集成?

Logstash 插件集成 Redis 哨兵/集群模式

如何在 Logstash 插件中与 Redis Sentinel / Cluster 模式集成?

在生产环境中,Redis 单点存在高可用风险。为了保障日志处理系统的稳定性,Logstash 自定义插件必须支持:

  • Redis Sentinel(哨兵模式):主从高可用 + 自动故障转移
  • Redis Cluster(集群模式):数据分片 + 水平扩展

本文将详细介绍 如何在 Logstash 插件中集成 Redis Sentinel 和 Cluster 模式,并提供完整代码示例、连接池配置、故障恢复机制等企业级实践。


一、Redis 高可用架构对比

特性Sentinel(哨兵)Cluster(集群)
架构主从 + 哨兵监控分片集群(16384 slots)
数据分布全量复制分片存储
高可用自动主从切换节点故障自动迁移
扩展性垂直扩展水平扩展
适用场景中小规模,高可用优先大规模,高吞吐、大数据量
客户端要求支持 Sentinel 发现支持 Cluster 路由

✅ 生产推荐:

  • 中小系统:Sentinel
  • 大数据量/高并发:Cluster

二、前提:使用支持 Sentinel/Cluster 的 Redis Gem

Logstash 默认使用的 redis gem(https://github.com/redis/redis-rb完全支持 Sentinel 和 Cluster

确保 .gemspec 中依赖版本 ≥ 4.0:

# logstash-filter-redis_ha.gemspec
s.add_runtime_dependency "redis", ">= 4.0", "< 5.0"
s.add_runtime_dependency "connection_pool", "~> 2.3"

安装插件时会自动安装。


三、方案一:集成 Redis Sentinel(哨兵模式)

3.1 Sentinel 架构示意图

[Logstash] 
   ↓ (发现 master)
[Sentinel1] → 自动选举 → [Redis Master]
[Sentinel2]             ↗
[Sentinel3]        [Redis Slave]

客户端通过哨兵获取当前 master 地址。


3.2 插件配置参数

config :sentinels, :validate => :array, :required => false
# 示例: ["host1:26379", "host2:26379"]
config :master_name, :validate => :string, :required => true
config :redis_password, :validate => :password
config :sentinel_password, :validate => :password  # 哨兵可能单独设密

3.3 核心代码实现(带连接池)

# lib/logstash/filters/redis_sentinel.rb
require "logstash/filters/base"
require "logstash/namespace"
require "connection_pool"
require "redis"

class LogStash::Filters::RedisSentinel < LogStash::Filters::Base
  config_name "redis_sentinel"

  # 配置参数
  config :master_name, :validate => :string, :required => true
  config :sentinels, :validate => :array, :required => true
  config :sentinel_password, :validate => :password
  config :redis_password, :validate => :password
  config :db, :validate => :number, :default => 0
  config :pool_size, :validate => :number, :default => 5
  config :pool_timeout, :validate => :number, :default => 5

  def register
    @logger.info("Connecting to Redis Sentinel", 
                 :master => @master_name, 
                 :sentinels => @sentinels)

    # 解析 sentinels 数组为 hash 格式
    sentinel_hosts = @sentinels.map do |addr|
      host, port = addr.split(":")
      { host: host, port: port.to_i }
    end

    # 创建连接池
    @redis_pool = ConnectionPool.new(size: @pool_size, timeout: @pool_timeout) do
      Redis.new(
        url: "redis://:#{@redis_password&.value if @redis_password}@#{@master_name}",
        sentinels: sentinel_hosts,
        role: :master,  # 只连接 master
        password: @sentinel_password&.value,
        db: @db,
        reconnect_attempts: 3,
        connect_timeout: 2,
        read_timeout: 5,
        write_timeout: 5
      )
    end

    # 测试连接
    @redis_pool.with { |redis| redis.ping }
  rescue => e
    @logger.error("Failed to connect to Redis Sentinel", :error => e.message)
    raise e
  end

  def filter(event)
    key = event.get("lookup_key")
    return unless key

    begin
      result = @redis_pool.with do |redis|
        redis.get(key)
      end

      if result
        event.set("lookup_value", result)
        filter_matched(event)
      end
    rescue Redis::CannotConnectError, Redis::ConnectionError => e
      @logger.warn("Redis connection failed, will retry", :error => e.message)
      # 连接池会自动重试
    rescue => e
      @logger.warn("Redis lookup failed", :error => e.message)
    end
  end

  def close
    @redis_pool&.shutdown do |redis|
      redis.quit rescue nil
    end
  end
end

3.4 配置示例(logstash.conf

filter {
  redis_sentinel {
    master_name => "mymaster"
    sentinels => ["192.168.1.10:26379", "192.168.1.11:26379", "192.168.1.12:26379"]
    redis_password => "strongpass123"
    sentinel_password => "sentinel_pass"  # 如果哨兵有密码
    pool_size => 10
  }
}

四、方案二:集成 Redis Cluster(集群模式)

4.1 Cluster 架构特点

  • 数据自动分片到 16384 slots
  • 至少 3 master 节点
  • 客户端需支持 ASK/MOVED 重定向

4.2 插件配置参数

config :cluster_nodes, :validate => :array, :required => true
# 示例: ["node1:6379", "node2:6379", "node3:6379"]
config :cluster_password, :validate => :password
config :cluster_retry_attempts, :validate => :number, :default => 5

4.3 核心代码实现

# lib/logstash/filters/redis_cluster.rb
require "logstash/filters/base"
require "logstash/namespace"
require "connection_pool"
require "redis"

class LogStash::Filters::RedisCluster < LogStash::Filters::Base
  config_name "redis_cluster"

  config :cluster_nodes, :validate => :array, :required => true
  config :cluster_password, :validate => :password
  config :db, :validate => :number, :default => 0
  config :pool_size, :validate => :number, :default => 5
  config :pool_timeout, :validate => :number, :default => 5
  config :connect_timeout, :validate => :number, :default => 2
  config :read_timeout, :validate => :number, :default => 5

  def register
    @logger.info("Connecting to Redis Cluster", 
                 :nodes => @cluster_nodes.size)

    # 解析节点
    nodes = @cluster_nodes.map do |addr|
      host, port = addr.split(":")
      { host: host, port: port.to_i }
    end

    @redis_pool = ConnectionPool.new(size: @pool_size, timeout: @pool_timeout) do
      Redis.new(
        cluster: nodes,
        password: @cluster_password&.value,
        db: @db,
        connect_timeout: @connect_timeout,
        read_timeout: @read_timeout,
        write_timeout: @write_timeout,
        reconnect_attempts: 3,
        timeout: 5
      )
    end

    # 测试连接
    @redis_pool.with { |redis| redis.ping }
  rescue => e
    @logger.error("Failed to connect to Redis Cluster", :error => e.message)
    raise e
  end

  def filter(event)
    key = event.get("user_id")
    return unless key

    full_key = "user:profile:#{key}"

    begin
      result = @redis_pool.with do |redis|
        redis.get(full_key)
      end

      if result
        event.set("user_profile", result)
        filter_matched(event)
      end
    rescue Redis::CommandError => e
      if e.message.include?("MOVED") || e.message.include?("ASK")
        @logger.debug("Cluster redirection, will retry", :error => e.message)
        # redis-rb 会自动处理重试
      else
        @logger.warn("Cluster command failed", :error => e.message)
      end
    rescue => e
      @logger.warn("Redis cluster error", :error => e.message)
    end
  end

  def close
    @redis_pool&.shutdown do |redis|
      redis.quit rescue nil
    end
  end
end

4.4 配置示例

filter {
  redis_cluster {
    cluster_nodes => ["192.168.2.10:6379", "192.168.2.11:6379", "192.168.2.12:6379"]
    cluster_password => "cluster_strong_pass"
    pool_size => 8
  }
}

五、连接池与高可用最佳实践

5.1 连接池配置建议

参数SentinelCluster
pool_size5~1010~20(因多节点)
timeout5s5s
reconnect_attempts33

5.2 故障恢复机制

  • redis-rb 自动处理:
    • Sentinel:自动发现新 master
    • Cluster:自动处理 MOVED/ASK 重定向
  • ✅ 连接池自动重试获取连接
  • ✅ 在 rescue 中记录日志,不阻塞事件流

5.3 监控建议

指标监控方式
连接池等待超时日志中 pool timeout
Redis 响应延迟@redis_pool.with { redis.ping } 计时
Cluster 重定向次数日志中 MOVED/ASK
Sentinel 切换事件Redis 哨兵日志

六、常见问题排查

问题原因解决方案
Cannot obtain master addressSentinel 地址错或密码错检查 sentinelssentinel_password
No resolvable masters所有 Sentinel 不可用检查网络和 Sentinel 状态
MOVED xxx 频繁Cluster 网络不稳定优化网络,增加超时
连接池超时并发高或 Redis 慢增大 pool_size 或优化查询
密码错误password.value 未正确获取使用 @password&.value

七、最佳实践总结

项目推荐做法
Sentinel用于高可用,3 哨兵 + 1主1从起
Cluster用于大数据量,至少 3 master
连接池必用 connection_pool
错误处理rescue 所有 Redis 异常,避免阻塞
日志记录连接状态和关键错误
测试在真实 Sentinel/Cluster 环境测试

八、参考资源


九、结语

通过集成 Redis Sentinel 或 Cluster,你的 Logstash 插件将具备:

  • 高可用性:自动故障转移
  • 可扩展性:支持大数据量
  • 生产就绪:符合企业级要求

是构建 稳定、可靠日志处理系统 的关键一步。


🎁 附:完整插件模板

你可以基于以下仓库创建自己的 HA Redis 插件:

git clone https://github.com/logstash-plugins/logstash-filter-redis.git
# 改造为支持 Sentinel/Cluster
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值