Redis 3.0 集群的数据分片方式为按 key 散列. 具体地, 集群中的每个节点持有一些 hash 槽位, 一个集群总共有 16384 个槽位, 可以以任意方式分布在各个节点上.
当应用程序需要访问一条数据时, 要使用 CRC16 函数计算出该数据 key 的一个散列值, 用此散列值对总槽位数 16384 取模, 得出槽位编号, 然后, 将此请求发送到持有该槽位的 Redis 节点上去. 对于有多个 key 的请求, 如果这多个 key 不在同一个槽位, 该指令不可以在集群中执行. 这一点限制极其苛刻, 因为两个随机指定的 key 名想要在同一个槽位上的概率几乎就是 1/16384 了. 虽然可以通过一种特殊手段强制一些 key 占据相同的槽位, 但这样又损害了集群设计的初衷, 即让 key 分散到不同的节点上去.
以上所述的每个节点为一个独立的 redis-server 进程, 进程启动时需要在配置文件中标明启用集群功能 (否则该进程的行为如同一个 2.8 的单点 Redis). 同一个集群中的不同节点可以分布在不同的机器上, 当然, 它们互相之间可以建立 TCP 连接进行必要的通信.
集群要求客户端自行判断请求该发往哪一个槽位, 且该槽位处在哪一个节点上. 因此在应用程序切换到使用 Redis 3.0 集群之前, 需要检查 Redis 库是否已经支持这些, 从而能正常使用集群.
或者, 使用我厂生产的 Redis-Cerberus 作为集群代理, 从而像操作单点 Redis 一样操作集群.
槽位是 Redis 3.0 集群中数据迁移的最小单位, 这也是 Redis 本身支持的集群与 Twemproxy 之间的区别, 后者并不能很好地支持运行时数据迁移.
git clone https://github.com/antirez/redis.git && cd redis
git checkout 3.0
make
编译完成后, src 目录下会有 redis-server 可执行文件. 继续使用
make install
可以将改可执行文件安装到 /usr/local/bin 下.
将以下内容保存到配置文件 redis-7000.conf
port 7000
cluster-enabled yes
cluster-config-file nodes-autogen-7000.conf
cluster-node-timeout 5000
然后以
redis-server redis-7000.conf
启动进程.
但此时只启动了一个空的节点, 并不具备集群功能, 也无法实现任何数据访问 (例如, 使用 redis-cli 连接到 7000 端口, 执行 GET 指令会返回 CLUSTERDOWN 错误).
在此配置文件里, port
参数指出其监听的端口, 这在以往的 redis 中也会有. 而 cluster-config-file nodes-autogen
参数也要求不同节点该值不能相同, 此参数指出的文件由节点自动生成, 用于记录节点所在集群的情况. 在节点进程意外崩溃重启之后, 节点会尝试读取该文件, 以便回到原来所处的集群中.
所以, 如果还需要在诸如 7001 端口上启动新节点, 需要更改以上两个参数.
要使新的空节点进入集群状态, 需要执行一个有 16384 个参数的 cluster addslots 命令, 将所有 16384 个槽位赋予该节点, 显然这不是人力可及的事情. 可以借助脚本来完成这个工作, 譬如使用以下的 python 脚本
import socket
REDIS_ADDR = ('127.0.0.1', 7000)
SYM_STAR = '*'
SYM_DOLLAR = '$'
SYM_CRLF = '\r\n'
def pack_command(*args):
output = [SYM_STAR, str(len(args)), SYM_CRLF]
for arg in args:
output.extend((SYM_DOLLAR, str(len(arg)), SYM_CRLF, arg, SYM_CRLF))
return ''.join(output)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(REDIS_ADDR)
s.sendall(pack_command('cluster', 'addslots', *[str(i) for i in xrange(16384)]))
然后稍等三五秒, 监听 7000 端口的 redis-server 进程就进入正常集群服务状态. 使用 redis-cli 连上去可以正常地发送数据指令.
关于这一部分内容, 可以参考我之前的一篇文章.
基本的集群组建和数据迁移功能已经由我厂生产的 Redis-Trib.py 项目封装完毕, 并上传到 pypi, 可以通过
pip install redis-trib
easy_install redis-trib
之一来安装. 安装后, 在命令行可以用以下指令来控制集群
# start: 从一个空节点上建立一个集群
# 参数: 1 个, 该节点的地址, 端口, 以冒号连接
# 要求: 指定节点并没有被分配槽位, 且没有已经在集群中
# 例
redis-trib.py start 127.0.0.1:7000
# start_multi: 指定多个空节点, 建立集群, 并将 16384 个槽位近似平分给这些节点
# 参数: 任意多个, 每个参数各表示一个节点的地址, 端口
# 要求: 每个指定节点都没有被分配槽位, 且没有已经在集群中
# 例
redis-trib.py start_multi 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002
# join: 指定一个空节点和一个集群, 使该节点加入此集群, 另外, 自动为该节点均摊一部分槽位
# join_no_load: 指定一个空节点和一个集群, 使该节点加入此集群, 但不分配任何槽位
# 参数: 2 个, 指定集群中任何一个节点的地址, 端口; 空节点的地址, 端口
# 例
redis-trib.py join 127.0.0.1:7000 127.0.0.1:7003
redis-trib.py join_no_load 127.0.0.1:7000 127.0.0.1:7004
# migrate_slots: 迁移槽位
# 参数: 至少 3 个, 原槽位所有者的地址, 端口; 迁移目标节点的地址端口; 一组槽位或槽位段
# 其中, 槽位或槽位段参数中的每一个参数可以有两种格式
# 单独一个非负整数, 表示一个槽位, 如 "0", "42"
# 一小一大两个非负整数, 以横线连接, 如 "2-3", 表示这个闭区间内的全部槽位
# 要求: 指定的两个节点必须在同一个集群中; 原槽位所有者对应的节点必须持有全部指定的槽位
# 例
redis-trib.py migrate_slots 127.0.0.1:7000 127.0.0.1:7001 0 2 4-7
# 这样从 127.0.0.1:7000 对应的节点迁移 0, 2, 4, 5, 6, 7 共 6 个槽位到 127.0.0.1:7001
更多指令请参阅项目首页的 README.
至此, 集群创建和数据迁移相关的操作已经简述完毕. 与集群使用相关的一些内容将在后续文章中继续说明.