编者注:本文为历史博文归档;涉及 JDK、框架与工具链版本请以当前官方文档为准。引用外链图片可能失效,阅读时请注意时效性。

简介

  • Redis Cluster 是 Redis 提供的分布式数据库方案,通过在多个 Redis 节点之间进行分片(Sharding)来实现数据共享,并能在部分节点故障的情况下继续运行。
  • Redis 集群的基本存储单位是槽(Slot)。一个集群共有 $2^{14}=16384$ 个槽。每一个槽的 Key 存放在集群中唯一的 Master 节点中,每一个槽还可以拥有 0~N 个 Slave 节点。

集群简介

集群简介

  • Redis 集群的每个节点都与其他节点保持连接,需要占用两个端口:

    • 数据端口(例如 7000):用来为客户端提供服务以及同其他服务端交换数据。
    • 总线端口(数据端口号 + 10000,其中偏移量 10000 是固定的):用来在节点之间传送控制信息。
  • 要让 Redis 集群系统正常运行,需要满足以下网络可见性要求:

    • 每个节点的数据端口对 Client 以及 其他集群节点 可见。
    • 每个节点的总线端口对 其他节点 可见。

数据分片(Sharding)

  • Redis 集群有 16384 个哈希槽(Hash Slot),散列算法是简单的取 Key 的 CRC16 模 16384。
  • 集群中的每一个节点负责哈希槽的一个子集。
  • Slot 可以动态地迁移、删除和分配。
  • Slot 的设计使得集群中能动态地添加和删除节点,例如:

    • 扩容:当添加新的节点 NodeD 时,只需要从节点 NodeA、NodeB、NodeC 中移动一些 Slot 到 NodeD 即可。
    • 缩容:当需要删除节点 NodeA 时,将 NodeA 中的 Slot 移动到 NodeB 和 NodeC 中。当 NodeA 为空之后,可以从集群中删除 NodeA。
  • Redis Cluster 支持多键(MGETMSET 等)操作,但前提是单次命令执行(Command Execution)或者整个事务(Transaction)中的 Key 属于同一个哈希槽。我们可以使用 Hash Tag 来强制多个 Key 使用同一个哈希槽。

    • Hash Tag 的简单语法是:只有 Key 中 {} 中的部分才被用来做 Hash。
    • 例如:Key {user1000}.following{user1000}.followers 会被 Hash 到同一个哈希槽中去,因为 {} 中的内容相同。

主从模式

  • Redis Cluster 采用主从模式,其中每一个槽有 1(主节点本身)~ N(N-1 个从节点)。
  • 故障转移:当主节点故障之后,系统会从该主节点的从节点中选举出一个从节点作为新的主节点,接管故障主节点负责处理的槽。当故障的节点恢复后,自动变为从节点。
  • 不可用情况:当一个哈希槽的所有节点(主节点和从节点)都故障之后,系统不能正常运行。

配置参数

  • 在 Redis 集群配置文件中,需要修改的最小配置项包括:

    • port: 端口
    • cluster-enabled: 开启 Cluster 模式
    • cluster-config-file: 集群配置文件(节点配置文件)
    • cluster-node-timeout: 节点超时时间 (ms)

集群搭建

  • Redis 的 src 目录下提供了 create-cluster 脚本来创建简单的 Demo:

    1. create-cluster start
    2. create-cluster create // 命令 1 和 2 开启集群
    3. create-cluster stop // 停止集群
  • 手动创建 Redis Cluster

    1. 创建目录结构:

      mkdir cluster-test
      cd cluster-test
      mkdir 7000 7001 7002 7003 7004 7005
    2. 在每一个文件夹中创建一个 redis.conf,替换其中的端口号为 7000~7005,修改 Cluster 相关配置。
    3. 创建启动脚本 start_cluster.sh

      #!/bin/bash
      cd 7000/
      nohup ./redis-server redis.conf &
      cd ../7001
      nohup ./redis-server redis.conf &
      cd ../7002
      nohup ./redis-server redis.conf &
      cd ../7003
      nohup ./redis-server redis.conf &
      cd ../7004
      nohup ./redis-server redis.conf &
      cd ../7005
      nohup ./redis-server redis.conf &
      cd ../
      ps -aux | grep redis
    4. 关闭集群(重启集群需要 kill 掉所有进程):

      ps -aux | grep redis-server | grep -v grep | cut -c 9-15 | xargs kill -9
    5. 创建集群(使用 redis-trib.rb 工具):

      ./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
      127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005

集群管理

  • redis-trib.rb 是 Redis 作者用 Ruby 实现的 Redis Cluster 管理工具。用此工具,我们能轻松地完成节点的添加、删除、Slot 的管理等。常用命令如下:

    create          host1:port1 ... hostN:portN        // 创建集群    
    check           host:port                          // 检查集群
    info            host:port                          // 查看集群
    fix             host:port                          // 修复集群
    reshard         host:port                          // 迁移 slot
    rebalance       host:port                          // 平衡节点 slot 数量
    add-node        new_host:new_port existing_host:existing_port    // 添加节点
    del-node        host:port node_id                  // 删除节点
    set-timeout     host:port milliseconds             // 设置节点心跳超时时间
  • 此外也能通过 CLUSTER 相关命令来管理集群,使用这些命令需要登录节点:

    // 集群 (cluster)  
    CLUSTER INFO                 // 打印集群的信息  
    CLUSTER NODES                // 列出集群当前已知的所有节点(node),以及这些节点的相关信息   
    
    // 节点 (node)  
    CLUSTER MEET <ip> <port>     // 将 ip 和 port 所指定的节点添加到集群当中,让它成为集群的一份子  
    CLUSTER FORGET <node_id>     // 从集群中移除 node_id 指定的节点  
    CLUSTER REPLICATE <node_id>  // 将当前节点设置为 node_id 指定的节点的从节点  
    CLUSTER SAVECONFIG           // 将节点的配置文件保存到硬盘里面   
    
    // 槽 (slot)  
    CLUSTER ADDSLOTS <slot> [slot ...]           // 将一个或多个槽(slot)指派(assign)给当前节点  
    CLUSTER DELSLOTS <slot> [slot ...]           // 移除一个或多个槽对当前节点的指派  
    CLUSTER FLUSHSLOTS                           // 移除指派给当前节点的所有槽,让当前节点变成一个没有指派任何槽的节点  
    CLUSTER SETSLOT <slot> NODE <node_id>        // 将槽 slot 指派给 node_id 指定的节点,如果槽已经指派给另一个节点,那么先让另一个节点删除该槽,然后再进行指派  
    CLUSTER SETSLOT <slot> MIGRATING <node_id>   // 将本节点的槽 slot 迁移到 node_id 指定的节点中  
    CLUSTER SETSLOT <slot> IMPORTING <node_id>   // 从 node_id 指定的节点中导入槽 slot 到本节点  
    CLUSTER SETSLOT <slot> STABLE                // 取消对槽 slot 的导入(import)或者迁移(migrate)   
    
    // 键 (key)  
    CLUSTER KEYSLOT <key>                        // 计算键 key 应该被放置在哪个槽上  
    CLUSTER COUNTKEYSINSLOT <slot>               // 返回槽 slot 目前包含的键值对数量  
    CLUSTER GETKEYSINSLOT <slot> <count>         // 返回 count 个 slot 槽中的键  

客户端使用原理

  • Redis Cluster 需要客户端能够解析 Cluster 协议,主要包括:

    1. MOVEASK 命令的重定向,连接超时的处理。
    2. 槽、节点缓存的维护,连接的管理等。

客户端处理流程

  • 向 Redis 集群发送的任何含 Key 命令(如 GET, SET, LLEN, MGET, MSET, RENAME, RPOPLPUSH 等)时,先计算 Key 的槽位编号,将指令发送给对应槽位的 Master 节点。

    • 如果 Key 存在,返回结果。
    • 如果指令发送到了错误的节点,该节点并不会处理请求,而是会返回重定向(MOVED, ASK)错误信息。
  • 客户端内部结构示例(Go 语言示意):

    const kClusterSlots = 16384
    type Cluster struct {
        slots [kClusterSlots]*redisNode        // 槽对应的节点信息
        nodes map[string]*redisNode
        // ...
    }
  • 重定向错误示例:

    MOVED 16384 127.0.0.1:7001
    ASK 16384 127.0.0.1:7001
  • MOVED:代表槽 i 的负责权已经从节点 A 转移到节点 B。当客户端收到了槽的 MOVED 错误之后,应该将本地缓存的节点和槽的对应信息也更新。即下次遇到槽 i 的命令请求,直接向 Slot-B 发送命令。
  • ASK:代表槽 i 正在从节点 A 转移到节点 B。所以当客户端收到了 ASK 之后,只是这次命令向节点 B 请求,而且必须先发送 ASKING 命令。接下来槽 i 的命令仍然向节点 A 请求。(注:原文误写为 ACK,此处已修正为 ASK)
  • 超时:超时后随机向新的节点更新槽的信息。

使用示例

  • 当搭建好 Redis Cluster 后,使用 Redis Cluster 就变得很简单。当前有多种 Client Libraries 实现,以 redis-go-cluster 为例:
  • 安装 redis-go-cluster

    go get github.com/chasex/redis-go-cluster
  • 使用示例:

    import "github.com/chasex/redis-go-cluster"
    
    cluster, err := redis.NewCluster(
        &redis.Options{
            StartNodes:   []string{"127.0.0.1:7000", "127.0.0.1:7001", "127.0.0.1:7002"},
            ConnTimeout:  50 * time.Millisecond,
            ReadTimeout:  50 * time.Millisecond,
            WriteTimeout: 50 * time.Millisecond,
            KeepAlive:    16,
            AliveTime:    60 * time.Second,
        })
    
    _, err := cluster.Do("SET", key, value)
    value, err := redis.Int(cluster.Do("GET", key))
    // ...

参考文档

说明

  • 版本时效性:本文内容基于较早期的 Redis Cluster 版本整理。

    • 管理工具redis-trib.rb 已在 Redis 5.0 后被移除,官方推荐使用内置的 redis-cli --cluster 命令替代。
    • 客户端库:文中提到的 redis-go-cluster 库可能已不再维护,生产环境建议使用官方推荐或社区活跃更新的 Redis 客户端库。
    • 配置与命令:具体配置参数与命令行为请以当前使用的 Redis 版本官方文档为准。