通过 Docker 部署 Redis 6.x 集群

通过 Docker 部署 Redis 6.x 集群

文章目录

  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。


系统环境:

  • Redis 版本:6.0.8
  • Docker 版本:19.03.12
  • 系统版本:CoreOS 7.8
  • 内核版本:5.8.5-1.el7.elrepo.x86_64

参考地址:

一、什么是 Redis 集群模式

在 Redis 3.0 版本后正式推出 Redis 集群模式,该模式是 Redis 的分布式的解决方案,是一个提供在多个 Redis 节点间共享数据的程序集,且 Redis 集群是去中心化的,它的每个 Master 节点都可以进行读写数据,每个节点都拥有平等的关系,每个节点都保持各自的数据和整个集群的状态。

Redis 集群设计的主要目的是让 Redis 数据存储能够线性扩展,通过分区来提供一定程度的可用性,在实际环境中当某个节点宕机或者不可达的情况下其可以继续处理命令。但是,如果发生较大故障(例如,大多数主站不可用时)时集群会停止运行,即 redis 集群不能保证数据的强一致性。

二、为什么需要 Redis 集群

  • 高可用性: Redis 在集群模式下每个 Master 都是主从复制模式,其 Master 节点上的数据会实时同步到 Slave 节点上,当 Master 节点不可用时,其对应的 Slave 节点身份会更改为 Master 节点,保证集群的可用性。
  • 数据横向扩展: Redis 是一个内存数据库,所有数据都存在内存中,在单节点中所在服务器能给与的内存是有一定限制。当数据量达到一定程度后,内存将不足以支撑这么多的数据存储,这时候需要将数据进行分片存储,而 Redis 集群模式就是将数据分片存储,非常方便横向扩展。

三、Redis 集群的数据分片

Redis 集群没有使用一致性 Hash, 而是引入了"哈希槽"的概念。Redis 集群有 16384 个哈希槽,每个 key 通过 CRC16 校验后对 16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。

举个例子,比如当前集群有3个节点,那么:

  • 节点 A 包含 0 到 5460 号哈希槽;
  • 节点 B 包含 5461 到 10922 号哈希槽;
  • 节点 C 包含 10923 到 16383 号哈希槽;

这种结构很容易"添加"或者"删除"节点. 比如如果我想新添加个节点 D,我需要从节点 A, B, C 中得部分槽转移到节点 D 上, 如果我想移除节点 A,则需要将 A 中的槽移到 B 和 C 节点上,然后将没有任何槽的 A 节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。

Redis 支持多个 key 操作,只要这些 key 在一个单个命令中执行(或者一个事务,或者 Lua 脚本执行),那么它们就属于相同的 Hash 槽。你也可以用 hash tags 命令强制多个 key 都在相同的 hash 槽中。

四、Redis 集群相关概念

集群中的主从模型

在 Redis 集群模式下,为了防止集群部分节点因宕机等情况造成不可用,故而 Redis 集群使用了主从复制模式。在该模式下要求 Redis 集群至少要存在六个节点,其中三个节点为主节点,能够对外提供读写。还有三个节点为从节点,会同步其对应的主节点的数据。当某个主节点出现问题不可用时,Redis 将通过选举算法从主节点对应的从节点中选择一个节点(主节点存在多个从节点的情况下),将其更改为一个新的主节点,且能够对外提供服务。

例如,在存在 A,B,C 三个主节点和其对应的 (A1、A2),(B1、B2),(C1、C2) 六个从节点,共九个节点中,如果节点 A 节点挂掉,那么其对应的从节点 A1、A2 节点将通过选举算法,选择其中一个节点提升为主节点,以确保集群能够正常服务。不过当 A1、A2 两个从节点或者或者半数以上主节点不可用时,那么集群也是不可用的。

在部署 Redis 集群模式时,至少需要六个节点组成集群才能保证集群的可用性。

Redis 集群中的一致性

Redis 在官方文档中提及,其并不能保证数据的强一致性,即 Redis 集群在特定的条件下写入的数据可能会丢失,主要原因是因为 Redis 集群使用了异步复制模式,其写入的操作过程如下:

执行顺序如下:

  • ① 客户端向任意一台主节点写入一条命令;
  • ② 主节点响应客户端,回复命令执行的状态;
  • ③ 主节点将写操作命令传递给它的从节点;

Redis 集群对性能和一致性之间做出权衡,设置主节点在接收到客户端写命令后再将执行的命令发送到各个从节点进行同步,这个过程是异步操作,且主节点不会等待从节点回复信息就立即回复客户端命令的执行状态。这样减少同步操作,可以很大提高系统的执行速度,避免等待从节点回复消息这个过程成为系统性能的瓶颈。

然而,因为主节点不等待从节点收到信息后进行回复,就将命令的执行状态回复给了客户端,那么在节点出现问题时很可能导致出现数据丢失问题,比如客户端向主节点发送一条命令,然后主节点将该命令异步发送到它的从节点进行数据备份,然后立即回复客户端命令的执行状态。如果在这个过程中,主节点出现问导致宕机,且从节点在处理主节点发送过来的同步数据时,也发生错误。这时正好赶上主节点宕机等不可用情况,那么从节点将转换为新的主节点,在之前主节点执行的命令将丢失。

Redis 集群间通信机制

在 Redis 集群中,数据节点提供两个 TCP 端口,在配置防火墙时需要同时开启下面两类端口:

  • 普通端口: 即客户端访问端口,如默认的 6379;
  • 集群端口: 普通端口号加 10000,如 6379 的集群端口为 16379,用于集群节点之间的通讯;

集群的节点之间通讯采用 Gossip 协议,节点根据固定频率(每秒10次)定时任务进行判断,当集群状态发生变化,如增删节点、槽状态变更时,会通过节点间通讯同步集群状态,使集群收敛。集群间发送的 Gossip 消息有下面五种消息类型:

  • MEET: 在节点握手阶段,对新加入的节点发送 meet 消息,请求新节点加入当前集群,新节点收到消息会回复 pong 消息;
  • PING: 节点之间互相发送 ping 消息,收到消息的会回复 pong 消息。ping 消息内容包含本节点和其他节点的状态信息,以此达到状态同步;
  • PONG: pong 消息包含自身的状态数据,在接收到 ping 或 meet 消息时会回复 pong 消息,也会主动向集群广播 pong 消息;
  • FAIL: 当一个主节点判断另一个主节点进入 fail 状态时,会向集群广播这个消息,接收到的节点会保存该消息并对该 fail 节点做状态判断;
  • PUBLISH: 当节点收到 publish 命令时,会先执行命令,然后向集群广播 publish 消息,接收到消息的节点也会执行 publish 命令;

Redis 集群失败状态

在 Redis 集群模式下也不可能百分百保证集群可用性,当发生不可预知的事件导致 Redis 集群将进入失败状态,在这种状态下 Redis 集群将不能正常提供服务。其中进入失败状态的条件主要为:

  • ① 全部节点都宕机,集群将进入 fail 状态;
  • ② 半数以上主节点不可用,集群将进入 fail 状态;
  • ③ 任意主节点挂掉,且该主节点没有对应的从节点或者从节点也全部挂掉,集群将进入 fail 状态;

Redis 集群重新分片机制

Redis 集群重新分片(新增/移除节点)机制:

  • 新增节点:别的节点上的槽分一些出来给新的节点
  • 删除节点:删除节点的槽分给别的节点

但这些操作是需要手动完成的,可以在不停止服务器的情况下执行。

Redis 集群的不足

  • 复制结构只支持单层结构,不支持树型结构。
  • 不支持多数据库,只能使用 0 数据库,执行 select 0 命令;
  • 键是数据分区的最小粒度,不能将一个很大的键值对映射到不同的节点;
  • 键事务支持有限,当多个键分布在不同节点时无法使用事务,同一节点才能支持事务;
  • 键的批量操作支持有限,比如 mset, mget 命令,如果多个键映射在不同的槽中,就不能正常使用这些命令了;

Redis 群集配置参数

我们即将创建一个示例集群部署。在继续之前,让我们介绍 Redis Cluster 在 redis.conf 文件中引入的配置参数。

  • cluster-config-file: 设置 Redis 集群配置信息及状态的存储位置,该文件由 Redis 集群生成,我们只能指定其存储的位置。
  • cluster-node-timeout: 设置 Redis 群集节点的通信的超时时间;
  • cluster-migration-barrier: 主节点需要的最小从节点数,只有达到这个数,主节点失败时,它从节点才会进行迁移。
  • cluster-enabled: 是否开启 Redis 集群模式。
    • yes:启用 Redis 群集;
    • no:不启用集群模式;
  • cluster-require-full-coverage: 设置集群可用性。
    • yes:表示当负责一个插槽的主库下线,且没有相应的从库进行故障恢复时,集群不可用。
    • no:表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,集群仍然可用。
  • cluster-slave-validity-factor:
    • 0:则无论从节点与主节点失联多久,从节点都会尝试升级成主节点。
    • 正数:则 cluster-node-timeout * cluster-slave-validity-factor 得到的时间,是从节点与主节点失联后,此从节点数据有效的最长时间,超过这个时间,从节点不会启动故障迁移。假设 cluster-node-timeout=5cluster-slave-validity-factor=10,则如果从节点跟主节点失联超过50秒,此从节点不能成为主节点。

五、Docker 部署 Redis 集群

1、Redis 部署机器分配

这里对待部署的 Redis 集群的节点进行分配,将其部署到不同的机器上,安排如下:

服务器IP 端口 CPU & Memory 数据存储位置
192.168.2.11 - 7000
- 7003
4C & 8G - /var/lib/redis/7000
- /var/lib/redis/7003
192.168.2.12 - 7001
- 7004
4C & 8G - /var/lib/redis/7001
- /var/lib/redis/7004
192.168.2.13 - 7002
- 7005
4C & 8G - /var/lib/redis/7002
- /var/lib/redis/7005

2、创建数据存储目录

提前创建好用于存储 Redis 的配置文件和持久化数据的目录:

第一台服务器 192.168.2.11 中执行创建存储目录命令:

1$ mkdir -p /var/lib/redis/7000 & mkdir -p /var/lib/redis/7003

第二台服务器 192.168.2.12 中执行创建存储目录命令:

1$ mkdir -p /var/lib/redis/7001 & mkdir -p /var/lib/redis/7004

第三台服务器 192.168.2.13 中执行创建存储目录命令:

1$ mkdir -p /var/lib/redis/7002 & mkdir -p /var/lib/redis/7005

3、创建 Redis 配置文件

第一台服务器 192.168.2.11 配置文件:

 1## 7000 端口配置文件
 2$ cat > /var/lib/redis/7000/redis.conf << EOF
 3port 7000
 4cluster-enabled yes
 5cluster-config-file nodes.conf
 6cluster-node-timeout 5000
 7appendonly yes
 8daemonize no
 9protected-mode no
10pidfile  /data/redis.pid
11EOF
12
13## 7003 端口配置文件
14$ cat > /var/lib/redis/7003/redis.conf << EOF
15port 7003
16cluster-enabled yes
17cluster-config-file nodes.conf
18cluster-node-timeout 5000
19appendonly yes
20daemonize no
21protected-mode no
22pidfile  /data/redis.pid
23EOF

第二台服务器 192.168.2.12 配置文件

 1## 7001 端口配置:redis.conf
 2$ cat > /var/lib/redis/7001/redis.conf << EOF
 3port 7001
 4cluster-enabled yes
 5cluster-config-file nodes.conf
 6cluster-node-timeout 5000
 7appendonly yes
 8daemonize no
 9protected-mode no
10pidfile  /data/redis.pid
11EOF
12
13## 7004 端口配置:redis-7004.conf
14$ cat > /var/lib/redis/7004/redis.conf << EOF
15port 7004
16cluster-enabled yes
17cluster-config-file nodes.conf
18cluster-node-timeout 5000
19appendonly yes
20daemonize no
21protected-mode no
22pidfile  /data/redis.pid
23EOF

第三台服务器 192.168.2.13 配置文件

 1## 7002 端口配置:redis-7002.conf
 2$ cat > /var/lib/redis/7002/redis.conf << EOF
 3port 7002
 4cluster-enabled yes
 5cluster-config-file nodes.conf
 6cluster-node-timeout 5000
 7appendonly yes
 8daemonize no
 9protected-mode no
10pidfile  /data/redis.pid
11EOF
12
13## 7005 端口配置:redis-7005.conf
14$ cat > /var/lib/redis/7005/redis.conf << EOF
15port 7005
16cluster-enabled yes
17cluster-config-file nodes.conf
18cluster-node-timeout 5000
19appendonly yes
20daemonize no
21protected-mode no
22pidfile  /data/redis.pid
23EOF

4、提前拉取 Redis 镜像

三台服务器中提前拉取 Redis 镜像,避免在执行运行时候还得拉取镜像,导致运行慢。

1$ docker pull redis:6.0.8

5、运行启动 Redis 镜像

三台服务器分别执行 Docker 运行命令来启动 Redis 镜像。这里需要注意的是,不同服务器间的 Docker 是不能相互通信的,所有这里我们设置启动的容器网络模式为 host 模式,这样容器不会创建虚拟网卡,而是使用宿主机的网络。

  • -d:设置容器后台运行;
  • -v:指定挂载的宿主机存储目录;
  • --name:指定运行后的容器名称;
  • --cpus:指定容器使用 CPU 数量;
  • --memory:限制容器使用内存数量;
  • --memory-swap:指定交换内存大小,这里设置为 0,即不用交换内存;
  • --net:指定 Docker 使用的网络模式;
  • --restart:指定 Docker 重启时容器的重启策略;
  • --privileged:设置容器拥有特权,能够获取宿主机 Root 权限;

第一台服务器 192.168.2.11 执行如下命令

 1## 运行 Redis 镜像 7000 端口 
 2$ docker run -d -v /var/lib/redis/7000:/data \
 3--cpus=1 --memory=2GB --memory-swap=0 \
 4--privileged=true \
 5--restart=always \
 6--net host \
 7--name redis-7000 \
 8redis:6.0.8 redis-server /data/redis.conf
 9
10## 运行 Redis 镜像 7003 端口 
11$ docker run -d -v /var/lib/redis/7003:/data \
12--cpus=1 --memory=2GB --memory-swap=0 \
13--privileged=true \
14--restart=always \
15--net host \
16--name redis-7003 \
17redis:6.0.8 redis-server /data/redis.conf

第二台服务器 192.168.2.12 执行如下命令

 1## 运行 Redis 镜像 7001 端口 
 2$ docker run -d -v /var/lib/redis/7001:/data \
 3--cpus=1 --memory=2GB --memory-swap=0 \
 4--privileged=true \
 5--restart=always \
 6--net host \
 7--name redis-7001 \
 8redis:6.0.8 redis-server /data/redis.conf
 9
10## 运行 Redis 镜像 7004端口 
11$ docker run -d -v /var/lib/redis/7004:/data \
12--cpus=1 --memory=2GB --memory-swap=0 \
13--privileged=true \
14--restart=always \
15--net host \
16--name redis-7004 \
17redis:6.0.8 redis-server /data/redis.conf

第三台服务器 192.168.2.13 执行如下命令:

 1## 运行 Redis 镜像 7002 端口 
 2$ docker run -d -v /var/lib/redis/7002:/data \
 3--cpus=1 --memory=2GB --memory-swap=0 \
 4--privileged=true \
 5--restart=always \
 6--net host \
 7--name redis-7002 \
 8redis:6.0.8 redis-server /data/redis.conf
 9
10## 运行 Redis 镜像 7005 端口 
11$ docker run -d -v /var/lib/redis/7005:/data \
12--cpus=1 --memory=2GB --memory-swap=0 \
13--privileged=true \
14--restart=always \
15--net host \
16--name redis-7005 \
17redis:6.0.8 redis-server /data/redis.conf

6、创建 Redis 集群

随意进入一台服务器,使用 Redis 镜像的 redis-cli 工具执行创建集群命令使各个 Redis 组成集群,这里本人进入第一台服务器 192.168.2.11 中,使用端口为 7000Redis 端镜像,可以执行下面命令:

  • -p:指定连接 Redis 的端口;
  • create:创建 Redis 集群;
  • --cluster:使用 Redis 集群模式命令;
  • --cluster-replicas:指定副本数(slave 数量);
1$ docker exec -it redis-7000 \
2redis-cli -p 7000 --cluster create \
3192.168.2.11:7000 192.168.2.12:7001 192.168.2.13:7002 \
4192.168.2.11:7003 192.168.2.12:7004 192.168.2.13:7005 \
5--cluster-replicas 1

然后会看到下面信息:

 1>>> Performing hash slots allocation on 6 nodes...
 2Master[0] -> Slots 0 - 5460
 3Master[1] -> Slots 5461 - 10922
 4Master[2] -> Slots 10923 - 16383
 5Adding replica 192.168.2.12:7004 to 192.168.2.11:7000
 6Adding replica 192.168.2.13:7005 to 192.168.2.12:7001
 7Adding replica 192.168.2.11:7003 to 192.168.2.13:7002
 8M: 5e50824c55d4df42db4d2987796f0c0b468c273f 192.168.2.11:7000
 9   slots:[0-5460] (5461 slots) master
10M: 36565e0273fd62921aa1f2d85c5f7ac98a5b9466 192.168.2.12:7001
11   slots:[5461-10922] (5462 slots) master
12M: 0cc1aaf960defae7332e9256dd25ee5e5c99e65f 192.168.2.13:7002
13   slots:[10923-16383] (5461 slots) master
14S: 42d6e3979395ba93cd1352b6d17044f6b25d9379 192.168.2.11:7003
15   replicates 0cc1aaf960defae7332e9256dd25ee5e5c99e65f
16S: ac5d34b57a8f73dabc60d3a56469055ec64fcde7 192.168.2.12:7004
17   replicates 5e50824c55d4df42db4d2987796f0c0b468c273f
18S: 470b7ff823f10a309fb07311097456210506f6d8 192.168.2.13:7005
19   replicates 36565e0273fd62921aa1f2d85c5f7ac98a5b9466
20Can I set the above configuration? (type 'yes' to accept): yes
21>>> Nodes configuration updated
22>>> Assign a different config epoch to each node
23>>> Sending CLUSTER MEET messages to join the cluster
24Waiting for the cluster to join
25.
26>>> Performing Cluster Check (using node 192.168.2.11:7000)
27M: 5e50824c55d4df42db4d2987796f0c0b468c273f 192.168.2.11:7000
28   slots:[0-5460] (5461 slots) master
29   1 additional replica(s)
30S: 470b7ff823f10a309fb07311097456210506f6d8 192.168.2.13:7005
31   slots: (0 slots) slave
32   replicates 36565e0273fd62921aa1f2d85c5f7ac98a5b9466
33S: 42d6e3979395ba93cd1352b6d17044f6b25d9379 192.168.2.11:7003
34   slots: (0 slots) slave
35   replicates 0cc1aaf960defae7332e9256dd25ee5e5c99e65f
36S: ac5d34b57a8f73dabc60d3a56469055ec64fcde7 192.168.2.12:7004
37   slots: (0 slots) slave
38   replicates 5e50824c55d4df42db4d2987796f0c0b468c273f
39M: 0cc1aaf960defae7332e9256dd25ee5e5c99e65f 192.168.2.13:7002
40   slots:[10923-16383] (5461 slots) master
41   1 additional replica(s)
42M: 36565e0273fd62921aa1f2d85c5f7ac98a5b9466 192.168.2.12:7001
43   slots:[5461-10922] (5462 slots) master
44   1 additional replica(s)
45[OK] All nodes agree about slots configuration.
46>>> Check for open slots...
47>>> Check slots coverage...
48[OK] All 16384 slots covered.

7、查看集群信息

进入 Redis 镜像内部并折佣 redis-cli 命令:

  • -p:指定连接 Redis 的端点;
  • -c:使用集群模式;
1$ docker exec -it redis-7000 redis-cli -p 7000 -c

查看集群信息:

 1> cluster info
 2
 3cluster_state:ok
 4cluster_slots_assigned:16384
 5cluster_slots_ok:16384
 6cluster_slots_pfail:0
 7cluster_slots_fail:0
 8cluster_known_nodes:6
 9cluster_size:3
10cluster_current_epoch:6
11cluster_my_epoch:1
12cluster_stats_messages_ping_sent:866
13cluster_stats_messages_pong_sent:854
14cluster_stats_messages_sent:1720
15cluster_stats_messages_ping_received:849
16cluster_stats_messages_pong_received:866
17cluster_stats_messages_meet_received:5
18cluster_stats_messages_received:1720

查看集群节点信息:

1> cluster nodes
2
3470b7ff823f10a309fb07311097456210506f6d8 192.168.2.13:7005@17005 slave 36565e0273fd62921aa1f2d85c5f7ac98a5b9466 0 1600267217000 2 connected
442d6e3979395ba93cd1352b6d17044f6b25d9379 192.168.2.11:7003@17003 slave 0cc1aaf960defae7332e9256dd25ee5e5c99e65f 0 1600267218171 3 connected
5ac5d34b57a8f73dabc60d3a56469055ec64fcde7 192.168.2.12:7004@17004 slave 5e50824c55d4df42db4d2987796f0c0b468c273f 0 1600267216161 1 connected
60cc1aaf960defae7332e9256dd25ee5e5c99e65f 192.168.2.13:7002@17002 master - 0 1600267218070 3 connected 10923-16383
736565e0273fd62921aa1f2d85c5f7ac98a5b9466 192.168.2.12:7001@17001 master - 0 1600267217163 2 connected 5461-10922
85e50824c55d4df42db4d2987796f0c0b468c273f 192.168.2.11:7000@17000 myself,master - 0 1600267217000 1 connected 0-5460

---END---


  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。