主从、哨兵与 Cluster

单机 Redis 有容量与可用性上限。复制解决可用性,哨兵解决自动切换,Cluster 解决水平扩展。

主从复制

replicaof 10.0.0.1 6379
replica-read-only yes
  • 复制是异步的:主节点写入后异步同步给从节点。
  • 首次同步会做全量同步RDB + 复制缓冲区),之后是增量同步(offset 续传)。
  • 从节点默认只读,可分担读请求(需客户端配置)。
INFO replication    # role、connected_slaves、master_repl_offset
Warning

异步复制意味着主节点故障时可能丢失尚未同步的写入。Redis 默认不保证强一致——若缓存数据丢失可接受,这没问题;若把 Redis 当唯一存储,需评估数据丢失风险。

Sentinel:自动故障切换

Sentinel 是独立进程集群,负责监控、通知与自动选主

sentinel monitor mymaster 10.0.0.1 6379 2   # 至少 2 个哨兵同意才判定故障
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
  • 哨兵以奇数个部署(如 3 个),避免投票僵局。
  • 判定主观下线(单个哨兵)→ 客观下线(达到 quorum)→ 选举 leader → 执行故障转移 → 通知客户端新主。

客户端需支持 Sentinel,才能自动感知主节点变更:

rdb := redis.NewFailoverClient(&redis.FailoverOptions{
    MasterName:    "mymaster",
    SentinelAddrs: []string{"10.0.0.1:26379", "10.0.0.2:26379", "10.0.0.3:26379"},
})

Cluster:分片集群

当单机内存放不下数据时,用 Cluster 把数据分片到多个主节点。

  • 数据划分为 16384 个哈希槽(slot)slot = CRC16(key) % 16384
  • 每个主节点负责一部分槽,每个主节点可带从节点做高可用。
  • 客户端请求到错误节点时,会收到 MOVED/ASK 重定向。
redis-cli --cluster create \
  10.0.0.1:6379 10.0.0.2:6379 10.0.0.3:6379 \
  10.0.0.4:6379 10.0.0.5:6379 10.0.0.6:6379 \
  --cluster-replicas 1

多键操作的槽限制:涉及多个 key 的命令要求它们在同一槽。可用 hash tag 强制同槽:

# {user1} 会被作为哈希输入,两个 key 落到同一槽
SET {user1}:name "Alice"
SET {user1}:age 30
Warning

Cluster 模式下跨槽的多键命令会失败(如 MGET k1 k2、跨槽事务/Lua)。设计 key 时用 hash tag 把需要一起操作的 key 绑定到同一槽。另外集群的节点数一旦确定,扩容需做槽迁移,规划时留出余量。

Go 客户端:go-redis

go get github.com/redis/go-redis/v9
package main

import (
    "context"
    "log"
    "time"

    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()

    // 单机
    rdb := redis.NewClient(&redis.Options{
        Addr:         "127.0.0.1:6379",
        Password:     "", // 从环境变量读取
        DB:           0,
        PoolSize:     20,
        MinIdleConns: 5,
    })
    defer rdb.Close()

    if err := rdb.Ping(ctx).Err(); err != nil {
        log.Fatal("连接失败:", err)
    }

    // 基本操作
    if err := rdb.Set(ctx, "user:1:name", "Alice", time.Hour).Err(); err != nil {
        log.Fatal(err)
    }
    name, err := rdb.Get(ctx, "user:1:name").Result()
    if err == redis.Nil {
        log.Println("key 不存在")
    } else if err != nil {
        log.Fatal(err)
    } else {
        log.Println("name =", name)
    }

    // 计数器
    rdb.Incr(ctx, "counter")

    // 集群客户端:NewClusterClient(&redis.ClusterOptions{Addrs: []string{...}})
}
Tip

redis.Nil正常的「key 不存在」信号,不是错误,必须单独判断。此外,所有操作都应带 context 超时,避免慢请求拖垮调用方。连接池大小按并发量与实例规格调整。

方案选择

方案适用特点
单机开发、小流量简单,无高可用
主从读扩展、基本容灾需手动切换
Sentinel高可用、数据量可控自动切换,容量仍受限单机
Cluster大数据量、高并发自动分片与高可用,运维复杂
云托管生产首选免运维,成本较高

小结

  • 主从复制异步,用于读扩展与容灾,但可能丢少量数据。
  • Sentinel 解决自动故障切换,需奇数部署并配置 quorum。
  • Cluster 用 16384 槽分片,跨槽操作受限,用 hash tag 绑定相关 key。
  • Go 用 go-redis,注意 redis.Nil 与 context 超时;生产优先云托管。