redis-sentinel-pool 0.1.0

An async Redis Sentinel-aware connection pool built on top of redis-rs and bb8, with transparent master failover.
Documentation

redis-sentinel-pool

简体中文 | English

基于 redis 1.2.1sentinel 模块和 bb8 连接池,构建的 Sentinel 感知的异步 Redis 连接池

设计目标:

  • 对业务完全透明的 master failover:哨兵切换主节点后,业务侧 无需 修改任何地址或重启进程,下一次 pool.get() 就会拿到指向新 master 的连接。
  • 秒级感知:可选的后台 watcher 直接订阅 Sentinel 的 +switch-master Pub/Sub 事件,收到通知后 立即 作废全池连接。
  • 零侵入接口:借出的就是 redis::aio::MultiplexedConnection,所有 redis::AsyncCommands / redis::cmd! 都可正常使用。
  • 可选的自动重试SentinelPool::execute(|conn| async { ... }) 在底层错误属于"连接级 / 短暂错误"时自动换连接重试,调用方写起来跟操作单机 Redis 一样。

添加依赖

[dependencies]
redis-sentinel-pool = { path = "../redis-sentinel-rs" }   # 或发布到 crates.io 后的版本号
tokio = { version = "1", features = ["full"] }
redis = { version = "1.2.1", features = ["aio", "tokio-comp"] }

本 crate 自身已经 pub use redis;,所以也可以直接通过 redis_sentinel_pool::redis::AsyncCommands 访问。


最小示例

use redis::AsyncCommands;
use redis_sentinel_pool::{SentinelPool, SentinelPoolConfig};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let cfg = SentinelPoolConfig::new(
        vec![
            "redis://127.0.0.1:26379",
            "redis://127.0.0.1:26380",
            "redis://127.0.0.1:26381",
        ],
        "mymaster",
    )
    .max_size(32)
    .redis_password("your-pass");      // 可选

    let pool = SentinelPool::new(cfg).await?;

    let mut conn = pool.get().await?;
    let _: () = conn.set("hello", "world").await?;
    let val: String = conn.get("hello").await?;
    println!("hello = {val}");
    Ok(())
}

让 failover 完全透明

let result: Option<String> = pool
    .execute(|mut conn| async move { conn.get("some-key").await })
    .await?;

execute 内部会:

  1. pool.get().await 拿一条连接;
  2. 执行闭包;
  3. 若失败且属于 可重试 错误(IO 错误、RetryMethod::Reconnect / WaitAndRetry / RetryImmediately 等),主动调用 force_refresh() 让全池连接作废,然后 sleep(backoff * attempt) 重试;
  4. 重试次数耗尽后返回 [SentinelPoolError::RetryExhausted]。

工作原理

┌─────────────────┐        +switch-master           ┌─────────────────┐
│  业务代码        │ ◀──────────────────────────── │ Sentinel 集群    │
│  pool.get()      │                               │ (26379/26380/...)│
│  pool.execute()  │                               └────────┬────────┘
└────────┬────────┘                                         │
         │ borrow                                            │ subscribe
         ▼                                                   ▼
┌─────────────────┐  bump_epoch on failover  ┌──────────────────────┐
│ bb8::Pool        │ ◀──────────────────────│ Watcher (后台 task)   │
│  + Manager       │                         └──────────────────────┘
└────────┬────────┘
         │ async_get_connection()  ───►  每次都向 sentinel 询问当前 master
         ▼
┌─────────────────┐
│ master 节点      │
└─────────────────┘
  • Manager 实现了 bb8::ManageConnection
    • connect():通过共享的 SentinelClient::async_get_connection() 创建一条到当前 master 的 MultiplexedConnection,并打上当前 epoch
    • is_valid():每次借出做 PING,必要时再发一条 ROLE 校验真的是 master(避免连到刚被 demote 的旧主)。
    • has_broken():连接出生的 epoch 落后于全局 epoch,立刻视为坏连接。
  • Watcher 后台 task:订阅 Sentinel 的 +switch-master 频道,收到与本服务相关的事件就 bump_epoch(),让池里所有旧连接秒级失效;watcher 与池本身是 解耦 的,关闭它(enable_watcher(false))后池仍然能在第一次失败时回收旧连接、自动指向新 master,只是恢复速度会略慢。

配置项一览

方法 默认值 说明
max_size 16 池最大连接数
min_idle None 预热的最小空闲连接数
connection_timeout 5s 借连接的等待超时
idle_timeout 600s 空闲连接最长存活
max_lifetime 1800s 单条连接最大生命周期
verify_role_on_checkout true 借出时额外发送 ROLE 校验当前节点仍是 master
max_retries 3 execute 系列的最大尝试次数
retry_backoff 100ms 重试退避的基线,每次重试乘以重试序号
enable_watcher true 是否启用后台 +switch-master watcher
watcher_reconnect_backoff 2s watcher 断开后的重连退避
redis_db / username / password None 目标 Redis 节点的鉴权信息
redis_protocol RESP2 Redis 协议版本
redis_tls_mode None 与 Redis 节点之间是否使用 TLS(需启用 tls feature)

运行示例

仓库自带一份 3 节点 Sentinel 的 docker compose,可以一键拉起本地环境 (无需改 hosts、跨平台、宿主机直接 127.0.0.1 可达):

docker compose -f docker/docker-compose.yml up -d
redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster

详见 docker/README.md

仓库自带 3 个 Rust 示例:

# 最简使用
RUST_LOG=info cargo run --example basic

# failover 演练:在循环里持续 set/get,期间手动触发 sentinel failover
RUST_LOG=info,redis_sentinel_pool=debug cargo run --example failover
# 另一个终端:
redis-cli -p 26379 SENTINEL FAILOVER mymaster

# 实时观察 watcher 和 epoch
RUST_LOG=info,redis_sentinel_pool=debug cargo run --example with_watcher

TLS 支持

启用 tls feature 后即可使用 TLS:

redis-sentinel-pool = { version = "0.1", features = ["tls"] }
let cfg = SentinelPoolConfig::new(vec!["redis://..."], "mymaster")
    .redis_tls_mode(redis::TlsMode::Secure);

License

双协议:MIT OR Apache-2.0。