Redis 集群

前面是理论知识, 实操直接拉到 9. Linux下创建Redis集群

什么是Redis集群 ?

Redis 集群是一个可以在多个 Redis 节点之间进行数据共享的设施(installation)。

Redis 集群是一个分布式(distributed)、容错(fault-tolerant)的Redis实现, 集群可以使用的功能是普通单机Redis所能使用的功能的一个子集(subset).

Redis 集群中不存在 中心(central)节点 或者 代理(proxy)节点, 集群的其中一个主要设计目标是达到线性可扩展性(linear scalability)

Redis 集群为了保证一致性(consistency)而牺牲了一部分容错性: 系统会在保证对网络断线(net split)和节点失效(node failure)具有有限(limited)抵抗力的前提下, 尽可能地保持数据的一致性。

Redis集群实现的功能子集

Redis 集群实现了单机 Redis 中, 所有处理单个数据库键的命令。

针对多个数据库键的复杂计算操作, 比如集合的并集操作、合集操作没有被实现, 那些理论上需要使用多个节点的多个数据库键才能完成的命令也没有被实现。

在将来, 用户也许可以通过 MIGRATE COPY 命令, 在集群的计算节点(computation node)中执行针对多个数据库键的只读操作, 但集群本身不会去实现那些需要将多个数据库键在多个节点中移来移去的复杂多键命令。

Redis 集群不像单机 Redis 那样支持多数据库功能, 集群只使用默认的 0 号数据库, 并且不能使用 SELECT index 命令。

Redis集群协议中的客户端和服务器

Redis 集群中的节点有以下责任:

  • 持有键值对数据。
  • 记录集群的状态,包括键到正确节点的映射(mapping keys to right nodes)。
  • 自动发现其他节点,识别工作不正常的节点,并在有需要时,在从节点中选举出新的主节点。

为了执行以上列出的任务, 集群中的每个节点都与其他节点建立起了“集群连接(cluster bus)”, 该连接是一个 TCP 连接, 使用二进制协议进行通讯。

节点之间使用 Gossip协议 来进行以下工作:

  • 传播 (propagate) 关于集群的信息,以此来发现新的节点。
  • 向其他节点发送 PING 数据包,以此来检查目标节点是否正常运作。
  • 在特定事件发生时,发送集群信息。

除此之外, 集群连接还用于在集群中发布或订阅信息。

因为集群节点不能代理(proxy)命令请求, 所以客户端应该在节点返回 -MOVED 或者 -ASK 转向(redirection)错误时, 自行将命令请求转发至其他节点。

因为客户端可以自由地向集群中的任何一个节点发送命令请求, 并可以在有需要时, 根据转向错误所提供的信息, 将命令转发至正确的节点, 所以在理论上来说, 客户端是无须保存集群状态信息的。

不过, 如果客户端可以将键和节点之间的映射信息保存起来, 可以有效地减少可能出现的转向次数, 籍此提升命令执行的效率。

Redis集群键分布模型

Redis 集群的键空间被分割为 16384 个槽(slot), 集群的最大节点数量也是 16384 个。

推荐的最大节点数量为 1000 个左右。

每个主节点都负责处理 16384 个哈希槽的其中一部分。

当我们说一个集群处于“稳定”(stable)状态时, 指的是集群没有在执行重配置(reconfiguration)操作, 每个哈希槽都只由一个节点进行处理。

以下是负责将键映射到槽的算法:

1
HASH_SLOT = CRC16(key) mod 16384

以下是该算法所使用的参数:

  • 算法的名称: XMODEM (又称ZMODEM 或者 CRC-16/ACORN)
  • 结果的长度: 16位
  • 多项数 (poly): 1021 (也即是 x16 + x12 + x5 + 1)
  • 初始化值: 0000
  • 反射输入字节 (Reflect Input byte): False
  • 发射输出CRC (Reflect Output CRC): False
  • 用于 CRC 输出值的异或常量 (Xor constant to output CRC) : 0000
  • 该算法对于输入 "123456789" 的输出: 31C3

Redis 集群数据共享

Redis 集群使用数据分片 (sharding) 而非一致性哈希 (consistency hashing) 来实现: 一个 Redis 集群包含 16384 个哈希槽(hash slot), 数据库中的每个键都属于这 16384 个哈希槽的其中一个, 集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 keyCRC16 校验和 。

集群中的每个节点负责处理一部分哈希槽。 举个例子, 一个集群可以有三个哈希槽, 其中:

  • 节点 A 负责处理 0 号至 5500 号哈希槽。
  • 节点 B 负责处理 5501 号至 11000 号哈希槽。
  • 节点 C 负责处理 11001 号至 16384 号哈希槽。

种将哈希槽分布到不同节点的做法使得用户可以很容易地向集群中添加或者删除节点。 比如说:

  • 如果用户将新节点 D 添加到集群中, 那么集群只需要将节点 ABC 中的某些槽移动到节点 D 就可以了。
  • 与此类似, 如果用户要从集群中移除节点 A , 那么集群只需要将节点 A 中的所有哈希槽移动到节点 B 和节点 C , 然后再移除空白(不包含任何哈希槽)的节点 A 就可以了。

因为将一个哈希槽从一个节点移动到另一个节点不会造成节点阻塞, 所以无论是添加新节点还是移除已存在节点, 又或者改变某个节点包含的哈希槽数量, 都不会造成集群下线。

Redis集群中的主从复制

为了使得集群在一部分节点下线或者无法与集群的大多数(majority)节点进行通讯的情况下, 仍然可以正常运作, Redis 集群对节点使用了主从复制功能: 集群中的每个节点都有 1 个至 N 个复制品(replica), 其中一个复制品为主节点(master), 而其余的 N-1 个复制品为从节点(slave)。

在之前列举的节点 ABC 的例子中, 如果节点 B 下线了, 那么集群将无法正常运行, 因为集群找不到节点来处理 5501 号至 11000 号的哈希槽。

另一方面, 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。另一方面, 假如在创建集群的时候(或者至少在节点 B 下线之前), 我们为主节点 B 添加了从节点 B1 , 那么当主节点 B 下线的时候, 集群就会将 B1 设置为新的主节点, 并让它代替下线的主节点 B , 继续处理 5501 号至 11000 号的哈希槽, 这样集群就不会因为主节点 B 的下线而无法正常运作了。

不过如果节点 BB1 都下线的话, Redis 集群还是会停止运作。

Redis 集群的一致性保证(guarantee

Redis 集群不保证数据的强一致性strong consistency): 在特定条件下, Redis 集群可能会丢失已经被执行过的写命令。

使用异步复制(asynchronous replication)是 Redis 集群可能会丢失写命令的其中一个原因。 考虑以下这个写命令的例子:

  • 客户端向主节点 B 发送一条写命令。
  • 主节点 B 执行写命令,并向客户端返回命令回复。
  • 主节点 B 将刚刚执行的写命令复制给它的从节点 B1 、 B2 和 B3 。

如你所见, 主节点对命令的复制工作发生在返回命令回复之后, 因为如果每次处理命令请求都需要等待复制操作完成的话, 那么主节点处理命令请求的速度将极大地降低 —— 我们必须在性能和一致性之间做出权衡。

Redis 集群另外一种可能会丢失命令的情况是, 集群出现网络分裂 (network partition), 并且一个客户端与至少包括一个主节点在内的少数 (minority) 实例被孤立。

创建并使用Redis集群

Redis 集群由多个运行在集群模式 (cluster mode) 下的 Redis 实例组成, 实例的集群模式需要通过配置来开启, 开启集群模式的实例将可以使用集群特有的功能和命令。

以下是一个包含了最少选项的集群配置文件示例:

1
2
3
4
5
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

文件中的 cluster-enabled 选项用于开实例的集群模式, 而 cluster-conf-file 选项则设定了保存节点配置文件的路径, 默认值为 nodes.conf

节点配置文件无须人为修改, 它由 Redis 集群在启动时创建, 并在有需要时自动进行更新。

要让集群正常运作至少需要三个主节点, 不过在刚开始试用集群功能时, 强烈建议使用六个节点: 其中三个为主节点, 而其余三个则是各个主节点的从节点。

首先, 让我们进入一个新目录, 并创建六个以端口号为名字的子目录, 稍后我们在将每个目录中运行一个 Redis 实例:

1
2
3
mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005

在文件夹 70007005 中, 各创建一个 redis.conf 文件, 文件的内容可以使用上面的示例配置文件, 但记得将配置中的端口号从 7000 改为与文件夹名字相同的号码。

现在, 从 Redis Github 页面 的 unstable 分支中取出最新的 Redis 源码, 编译出可执行文件 redis-server , 并将文件复制到 cluster-test 文件夹, 然后使用类似以下命令, 在每个标签页中打开一个实例:

1
2
cd 7000
../redis-server ./redis.conf

实例打印的日志显示, 因为 nodes.conf 文件不存在, 所以每个节点都为它自身指定了一个新的 ID :

1
[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1

实例会一直使用同一个 ID , 从而在集群中保持一个独一无二 (unique) 的名字。

每个节点都使用 ID 而不是 IP 或者端口号来记录其他节点, 因为 IP 地址和端口号都可能会改变, 而这个独一无二的标识符 (identifier) 则会在节点的整个生命周期中一直保持不变。

我们将这个标识符称为节点 ID

创建集群

现在我们已经有了六个正在运行中的 Redis 实例, 接下来我们需要使用这些实例来创建集群, 并为每个节点编写配置文件。

通过使用 Redis 集群命令行工具 redis-trib , 编写节点配置文件的工作可以非常容易地完成: redis-trib 位于 Redis 源码的 src 文件夹中, 它是一个 Ruby 程序, 这个程序通过向实例发送特殊命令来完成创建新集群, 检查集群, 或者对集群进行重新分片 (reshared) 等工作。

我们需要执行以下命令来创建集群:

1
2
./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 程序的命令是 create , 这表示我们希望创建一个新的集群。
  • 选项 --replicas 1 表示我们希望为集群中的每个主节点创建一个从节点。
  • 后跟着的其他参数则是实例的地址列表, 我们希望程序使用这些地址所指示的实例来创建新集群。

简单来说, 以上命令的意思就是让 redis-trib 程序创建一个包含三个主节点和三个从节点的集群。

接着, redis-trib 会打印出一份预想中的配置给你看, 如果你觉得没问题的话, 就可以输入 yesredis-trib 就会将这份配置应用到集群当中:

输入 yes 并按下回车确认之后, 集群就会将配置应用到各个节点, 并连接起 (join) 各个节点 —— 也即是, 让各个节点开始互相通讯:

如果一切正常的话, redis-trib 将输出以下信息:

1
2
3
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

这表示集群中的 16384 个槽都有至少一个主节点在处理, 集群运作正常。

Linux下创建Redis集群

创建目录

1
2
3
4
[root@caoxl redis]# cd /usr/local/redis/
[root@caoxl redis]# mkdir cluster
[root@caoxl redis]# cd cluster/
[root@caoxl cluster]# mkdir 7000 7001 7002 7003 7004 7005

复制和修改配置文件

1
[root@caoxl cluster]# cp ../bin/redis.conf ./7000

修改配置文件 redis.conf,将下面的选项修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 端口号
port 7000
# 后台启动
daemonize yes
# 开启集群
cluster-enabled yes
#集群节点配置文件
cluster-config-file nodes-7000.conf
# 集群连接超时时间
cluster-node-timeout 5000
# 进程pid的文件位置
pidfile /var/run/redis-7000.pid
# 开启aof
appendonly yes
# aof文件路径
appendfilename "appendonly-7000.aof"
# rdb文件路径
dbfilename dump-7000.rdb

6个配置文件按照对应的端口分别修改配置文件

创建启动脚本

/usr/local/redis 目录下创建一个 start.sh

1
2
3
4
5
6
7
#!/bin/bash
bin/redis-server cluster/7000/redis.conf
bin/redis-server cluster/7001/redis.conf
bin/redis-server cluster/7002/redis.conf
bin/redis-server cluster/7003/redis.conf
bin/redis-server cluster/7004/redis.conf
bin/redis-server cluster/7005/redis.conf
  • 这个时候我们查看一下进程看启动情况
1
2
[root@caoxl redis]# ps -ef | grep redis
root 20142 19734 0 11:29 pts/2 00:00:00 grep --color=auto redis
  • 执行 start.sh
1
2
3
4
[root@caoxl redis]# sh start.sh 
20169:C 30 Sep 2019 11:31:39.052 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
20169:C 30 Sep 2019 11:31:39.052 # Redis version=5.0.1, bits=64, commit=00000000, modified=0, pid=20169, just started
...
  • 这个时候我们查看一下进程看启动情况
1
2
3
4
5
6
7
8
[root@caoxl redis]# ps -ef | grep redis
root 20155 1 0 11:31 ? 00:00:00 bin/redis-server 127.0.0.1:7000 [cluster]
root 20162 1 0 11:31 ? 00:00:00 bin/redis-server 127.0.0.1:7001 [cluster]
root 20174 1 0 11:31 ? 00:00:00 bin/redis-server 127.0.0.1:7002 [cluster]
root 20176 1 0 11:31 ? 00:00:00 bin/redis-server 127.0.0.1:7003 [cluster]
root 20181 1 0 11:31 ? 00:00:00 bin/redis-server 127.0.0.1:7004 [cluster]
root 20189 1 0 11:31 ? 00:00:00 bin/redis-server 127.0.0.1:7005 [cluster]
root 20194 19734 0 11:31 pts/2 00:00:00 grep --color=auto redis

有6个Redis进程在开启,说明我们的Redis就启动成功了

创建/开启集群

这里我们只是开启了6个redis进程而已,它们都还只是独立的状态,还么有组成集群这里我们使用官方提供的工具redis-trib,不过这个工具是用ruby写的,要先安装ruby的环境

1
yum install ruby rubygems -y
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@caoxl redis]# redis-cli --cluster create 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
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 2730
Master[1] -> Slots 2731 - 5460
Master[2] -> Slots 5461 - 8191
Master[3] -> Slots 8192 - 10922
Master[4] -> Slots 10923 - 13652
Master[5] -> Slots 13653 - 16383
M: ec6743b088fafa480bb6f06f2f5189df1ede2b1d 127.0.0.1:7000
slots:[0-2730] (2731 slots) master
M: 61d89b9786b7785633224c5950eef2aa5cee7dfa 127.0.0.1:7001
slots:[2731-5460] (2730 slots) master
M: 4449a1b0bd65e9688995907215c4ef1311dbdfc8 127.0.0.1:7002
slots:[5461-8191] (2731 slots) master
M: 6ad224baedff1134cd443a249d5eb13a80c51017 127.0.0.1:7003
slots:[8192-10922] (2731 slots) master
M: 797ad1ef54e859f724bc153223284a85c1afef44 127.0.0.1:7004
slots:[10923-13652] (2730 slots) master
M: 32158e5b4e5815ffd253d8eb94ace27c8c96e84e 127.0.0.1:7005
slots:[13653-16383] (2731 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
...
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: ec6743b088fafa480bb6f06f2f5189df1ede2b1d 127.0.0.1:7000
slots:[0-2730] (2731 slots) master
M: 32158e5b4e5815ffd253d8eb94ace27c8c96e84e 127.0.0.1:7005
slots:[13653-16383] (2731 slots) master
M: 797ad1ef54e859f724bc153223284a85c1afef44 127.0.0.1:7004
slots:[10923-13652] (2730 slots) master
M: 61d89b9786b7785633224c5950eef2aa5cee7dfa 127.0.0.1:7001
slots:[2731-5460] (2730 slots) master
M: 4449a1b0bd65e9688995907215c4ef1311dbdfc8 127.0.0.1:7002
slots:[5461-8191] (2731 slots) master
M: 6ad224baedff1134cd443a249d5eb13a80c51017 127.0.0.1:7003
slots:[8192-10922] (2731 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

redis-cli –cluster create 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

连接集群

这里我们使用 reids-cli 连接集群,使用时加上-c参数,就可以连接到集群
连接 7000 端口的节点

1
2
3
4
5
6
7
8
9
10
11
[root@caoxl redis]# redis-cli -c -p 7000
127.0.0.1:7000> set key_7000 value_7000
-> Redirected to slot [10168] located at 127.0.0.1:7003
OK
127.0.0.1:7003> get key_7000
"value_7000"
127.0.0.1:7003> set key_7003 value_7003
-> Redirected to slot [6107] located at 127.0.0.1:7002
OK
127.0.0.1:7002> get key_7003
"value_7003"

我们发现数据会在 7000-70023个节点之间来回跳转

测试集群中的节点挂掉

上面我们建立了一个集群,3个主节点和3个从节点,7000-7002负责存取数据,7003-7005负责把7000-7005的数据同步到自己的节点上来。我们现在来模拟一下一台matser服务器宕机的情况

1
2
3
4
5
6
7
8
[root@caoxl redis]# ps -ef | grep redis
root 20155 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7000 [cluster]
root 20162 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7001 [cluster]
root 20174 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7002 [cluster]
root 20176 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7003 [cluster]
root 20181 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7004 [cluster]
root 20189 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7005 [cluster]
root 20571 19734 0 11:49 pts/2 00:00:00 grep --color=auto redis
  • 杀死一个redis master进程
1
2
3
4
5
6
7
8
[root@caoxl redis]# kill 20155
[root@caoxl redis]# ps -ef | grep redis
root 20162 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7001 [cluster]
root 20174 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7002 [cluster]
root 20176 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7003 [cluster]
root 20181 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7004 [cluster]
root 20189 1 0 11:31 ? 00:00:01 bin/redis-server 127.0.0.1:7005 [cluster]
root 20578 19734 0 11:50 pts/2 00:00:00 grep --color=auto redis
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@caoxl bin]# redis-cli --cluster check 127.0.0.1:7001
Could not connect to Redis at 127.0.0.1:7000: Connection refused
127.0.0.1:7001 (61d89b97...) -> 0 keys | 2730 slots | 0 slaves.
127.0.0.1:7004 (797ad1ef...) -> 0 keys | 2730 slots | 0 slaves.
127.0.0.1:7002 (4449a1b0...) -> 1 keys | 2731 slots | 0 slaves.
127.0.0.1:7005 (32158e5b...) -> 0 keys | 2731 slots | 0 slaves.
127.0.0.1:7003 (6ad224ba...) -> 1 keys | 2731 slots | 0 slaves.
[OK] 2 keys in 5 masters.
0.00 keys per slot on average.
>>> Performing Cluster Check (using node 127.0.0.1:7001)
M: 61d89b9786b7785633224c5950eef2aa5cee7dfa 127.0.0.1:7001
slots:[2731-5460] (2730 slots) master
M: 797ad1ef54e859f724bc153223284a85c1afef44 127.0.0.1:7004
slots:[10923-13652] (2730 slots) master
M: 4449a1b0bd65e9688995907215c4ef1311dbdfc8 127.0.0.1:7002
slots:[5461-8191] (2731 slots) master
M: 32158e5b4e5815ffd253d8eb94ace27c8c96e84e 127.0.0.1:7005
slots:[13653-16383] (2731 slots) master
M: 6ad224baedff1134cd443a249d5eb13a80c51017 127.0.0.1:7003
slots:[8192-10922] (2731 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[ERR] Not all 16384 slots are covered by nodes.

注意: redis-trib.rb是redis 5.0以下的版本创建集群所使用的脚本,注意,redis5.0版本已不需要再使用此脚本,而是使用自带的redis-cli创建
[root@caoxl redis]# redis-cli -v
redis-cli 5.0.1

Redis集群命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@caoxl bin]# redis-cli --cluster help
Cluster Manager Commands:
create host1:port1 ... hostN:portN
--cluster-replicas <arg>
check host:port
info host:port
fix host:port
reshard host:port
--cluster-from <arg>
--cluster-to <arg>
--cluster-slots <arg>
--cluster-yes
--cluster-timeout <arg>
--cluster-pipeline <arg>
rebalance host:port
--cluster-weight <node1=w1...nodeN=wN>
--cluster-use-empty-masters
--cluster-timeout <arg>
--cluster-simulate
--cluster-pipeline <arg>
--cluster-threshold <arg>
add-node new_host:new_port existing_host:existing_port
--cluster-slave
--cluster-master-id <arg>
del-node host:port node_id
call host:port command arg arg .. arg
set-timeout host:port milliseconds
import host:port
--cluster-from <arg>
--cluster-copy
--cluster-replace
help

For check, fix, reshard, del-node, set-timeout you can specify the host and port of any working node in the cluster.

参考

Powered by Hexo and Hexo-theme-hiker

Copyright © 2017 - 2023 Keep It Simple And Stupid All Rights Reserved.

访客数 : | 访问量 :