基于Docker的Redis集群实践

单机版的Redis相信大家都比较熟悉了,这里介绍几种Redis的集群模式,并结合Docker来进行实践操作

abstract.png

准备工作

通过Docker下载最新的Redis镜像

1
2
# 获取redis镜像
docker pull redis

下载后续所需的相关配置文件

1
2
3
4
# 下载Redis配置文件
wget http://download.redis.io/redis-stable/redis.conf
# 下载Redis哨兵配置文件
wget http://download.redis.io/redis-stable/sentinel.conf

主从模式

众所周知,将数据放在一台服务器上风险巨大。为此Redis提供了Replication复制功能,可以将一台Redis服务器中的数据自动同步到其他多台Redis服务器中。在该模式下,存在两个角色:Master主数据库(主库)、Slave从数据库(从库)。主数据库可以进行读写操作,每次当主数据库发生变动即会自动同步至相应的从数据库中。故从数据库一般是只读的,不允许用户直接向其中写入数据。具体地,一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库,即一对多的关系。可以看到在主从模式下,一方面降低了数据丢失的风险,另一方面通过读写分离(主库写、从库读),提高了服务器的负载能力。下面我们搭个一主一从的Redis集群进行演示

配置主库

按下图建立主库所需的相关目录,并将Redis配置文件复制到 /Users/zgh/Docker/Redis/Redis-Master/Config 路径下。如下图所示

figure 1.jpeg

修改主库的Redis配置文件,修改如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
...
# 注释bind配置项
# bind 127.0.0.1

# 设为no, 关闭保护模式
protected-mode no

# 主库端口设为6379
port 6379

# 修改数据库数量, 用于验证配置文件是否生效
databases 10

# 设置工作目录
dir "/data"

# 设置Redis密码
requirepass 123456

# 设为no, 因为docker启动时会通过-d参数来让其实现后台运行
daemonize no
...

至此,就可以通过Docker来启动一个使用指定配置文件的Redis容器来作为主库了。命令如下所示

1
2
3
4
5
6
docker run \
-v /Users/zgh/Docker/Redis/Redis-Master/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Master/Data:/data \
-d -p 6379:6379 \
--name Redis-Master \
redis redis-server /etc/redis/redis.conf

命令选项说明如下

  • -v : 将宿主机的相关目录、文件挂载到容器中。具体地,冒号:前为宿主机目录,冒号:后为容器目录
  • -d : 指定容器后台运行
  • -p : 端口映射。将宿主机的端口映射到容器的端口。具体地,冒号:前为宿主机端口,冒号:后为容器端口
  • —name : 命名容器

从库配置

至此主库Redis—Master已经配置、启动完毕,现在让我们来配置一个从库。为了保证Redis从库能够自动同步主库中的数据,需要在从库的配置文件配置主库的IP、端口信息。这里,我们即可直接使用Docker分配Redis—Master容器的IP进行容器间通信,也可以通过宿主机IP(可通过ifconfig命令获取)来进行通信。这里我们选择前者,直接获取Docker分配给Redis—Master容器的IP

1
2
# 查看 Redis-Master 容器的详细信息
docker inspect Redis-Master

下图命令结果的IPAddress即为Redis-Master容器的IP——172.17.0.2

figure 2.jpeg

好了,知道了主库的IP信息。现在我们可以来配置从库了。按下图建立从库所需的相关目录,并将Redis配置文件复制到 /Users/zgh/Docker/Redis/Redis-Slave1/Config 路径下。如下图所示

figure 3.jpeg

修改从库的Redis配置文件,修改如下。值得一提的是,当主库设置密码时,必须在从库的配置文件通过masterauth配置项设置主库密码。否则,从库将无法正确连接、访问主库以进行数据同步

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
...
# 注释bind配置项
# bind 127.0.0.1

# 设为 no, 关闭保护模式
protected-mode no

# 从库端口设为6388
port 6388

# 修改数据库数量, 用于验证配置文件是否生效
databases 12

# 设置工作目录
dir "/data"

# 设置Redis密码
requirepass 123456

# 设为no, 因为docker启动时会通过-d参数来让其实现后台运行
daemonize no

# 设置主库的IP地址、端口
replicaof 172.17.0.2 6379

# 设置主库的密码
masterauth "123456"
...

至此,就可以通过Docker来启动一个使用指定配置文件的Redis容器来作为从库了。命令如下所示

1
2
3
4
5
6
docker run \
-v /Users/zgh/Docker/Redis/Redis-Slave1/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Slave1/Data:/data \
-d -p 6388:6388 \
--name Redis-Slave1 \
redis redis-server /etc/redis/redis.conf

实践

至此,我们演示所需的两个Redis服务就分别创建、启动了,且为一主一从。现在让我们进入各Redis容器内,通过redis-cli连接后,通过info replication查看主从同步状态。结果如下图所示,可以看到在Redis-Master主库中显示有一个从库已经连接上了,而在Redis-Salve1从库中则显示其同步的主库也成功上线了

figure 4.jpeg

现在,让我们实际验证下,看看在主库Redis-Master下写入的数据,是否可以通过从库获取到。测试过程、结果如下所示,符合预期

figure 5.jpeg

运行时配置主从

通过slaveof命令实现运行时配置同步所需的主库信息。具体地,如果该库之前是一个主库,则此时会变为从库;如果该库之前是一个其他主库的从库,则此时会停止与原来主库的同步,转而和新的主库同步

1
slaveof <新Redis主库IP> <新Redis从库端口>

特别地,还可通过下述命令将一个从库变为主库

1
2
# 将该库变为主库
slaveof no one

figure 6.jpeg

哨兵模式

利用Redis的Replication来构建一主多从的Redis集群虽然方便,但是有一个明显的弊端。一旦主数据库宕机停止服务了,需要我们手动地从若干个从数据库中重新选择一个作为新的主库,并让剩余的从库指向新主库进行同步。以保障整个系统可以继续对外提供服务。显然这个过程需要人工干预,非常麻烦。为此,Redis提供了一个哨兵Sentinel的功能。其可对整个主、从库进行监控,并且在主库异常时自动地选择一个从库作为主库,以继续提供服务

配置从库Redis-Slave2

在上节,我们配置了两个Reids数据库——Redis-Master、Redis-Salve1,分别使用了6379、6388端口。为了后续演示方便,我们再配置一个从库——Redis-Salve2

按下图建立从库所需的相关目录,并将Redis配置文件复制到 /Users/zgh/Docker/Redis/Redis-Slave2/Config 路径下。如下图所示

figure 7.jpeg

类似地,我们修改从库的Redis配置文件。这里,对于Redis-Slave2从库,我们使用6399端口

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
...
# 注释bind配置项
# bind 127.0.0.1

# 设为 no, 关闭保护模式
protected-mode no

# 从库端口设为6399
port 6399

# 修改数据库数量, 用于验证配置文件是否生效
databases 13

# 设置工作目录
dir "/data"

# 设置Redis密码
requirepass 123456

# 设为no, 因为docker启动时会通过-d参数来让其实现后台运行
daemonize no

# 设置主库的IP地址、端口
replicaof 172.17.0.2 6379

# 设置主库的密码
masterauth "123456"
...

然后可以通过Docker来启动一个使用指定配置文件的Redis容器来作为从库了。命令如下所示

1
2
3
4
5
6
docker run \
-v /Users/zgh/Docker/Redis/Redis-Slave2/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Slave2/Data:/data \
-d -p 6399:6399 \
--name Redis-Slave2 \
redis redis-server /etc/redis/redis.conf

至此,主库Redis-Master(端口号:6379)下有两个从库:Redis-Slave1(端口号:6388)、Redis-Slave2(端口号:6399)。如下图所示

figure 8.jpeg

配置哨兵Sentinel

在配置哨兵前,我们先修改下Redis-Master的配置文件,修改完成后重启该容器即可生效新配置

1
2
3
4
...
# 设置主库的密码
masterauth "123456"
...

按下图建立哨兵所需的相关目录,并将Sentinel的配置文件复制到 /Users/zgh/Docker/Redis/Redis-Sentinel1/Config 路径下。如下图所示

figure 9.jpeg

修改哨兵的配置文件,修改如下。值得一提的是,当主库设置密码时,必须在哨兵的配置文件通过sentinel auth-pass配置项设置主库密码。否则,哨兵将无法正确连接、访问主库以进行监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
...
# 设为no, 因为docker启动时会通过-d参数来让其实现后台运行
daemonize no

# 哨兵端口
port 26379

# 设置日志文件路径
logfile "/data/Log/sentinel.log"

# 设置哨兵所需监控的主库IP、端口, 并命名为myMaster
# 最低通过票数设为1
sentinel monitor myMaster 172.17.0.2 6379 1

# 设置哨兵访问myMaster所指示主库的密码
sentinel auth-pass myMaster 123456
...

然后可以通过Docker来启动一个使用指定配置文件的哨兵了。命令如下所示

1
2
3
4
5
docker run \
-v /Users/zgh/Docker/Redis/Redis-Sentinel1:/data \
-d -p 26379:26379 \
--name Redis-Sentinel1 \
redis redis-sentinel /data/Conifg/sentinel.conf

实践

现在,我们已经启动了一个Redis哨兵,用于监控我们的主库Redis-Master、从库Redis-Salve1、从库Redis-Salve2。现在,我们来看哨兵的日志输出

figure 10.jpeg

从上图可以看到,哨兵会自动发现主库下的所有从库。所以在哨兵的配置文件只需显式地配置需要被监控的各主库的信息即可。现在,我们人为停止主库Redis—Master容器,来看看会发生什么。通过哨兵的日志我们可以看出,哨兵发现使用6379端口的Redis-Master主库发生了异常,然后选择运行在6399端口的Redis-Slave2从库作为新的主库

figure 11.jpeg

同时对于使用6388端口的Redis-Slave1而言,其之前是以Redis-Master作为主库进行数据同步的,现在同样是以Redis-Slave2作为主库进行数据同步的,结果如下所示

figure 12.jpeg

可以看到,通过哨兵可以在主库发生异常时自动选择其中一个从库作为新的主库来继续提供服务,并让其他剩余的从库指向新主库进行数据同步。当然如果6379端口的Redis-Master恢复了,其同样也会作为新主库的从库来对外进行提供服务的。这一点可以从哨兵的日志看出。这也是为什么在前文我们需要在Redis-Master的配置文件中设置masterauth项,否则Redis-Master将无法访问、同步主库Redis-Salve2的数据。现在我们再次启动Redis-Master容器来验证下,哨兵的日志输出如下

figure 13.jpeg

现在,我们分别进入6379端口的Redis-Master容器、6399端口的Redis-Salve2容器验证下,结果如下

figure 14.jpeg

集群模式

主从+哨兵,看上去好像已经很完美了。但对于任意一个Redis服务而言,不论是主库还是从库,存储都是所有的数据。这样数据的总存储容量将会受到单台服务器内存容量的限制。换言之,我们需要对Redis进行水平扩容,为此集群模式应运而生。在Redis集群中共计有16384个Slot槽,集群下的每个节点负责若干个Slot槽。确定Key属于Redis哪个节点的具体过程:对键名Key使用CRC16算法计算出散列值,然后对16384取余。这样即可确定Key所在的Slot槽,进而确定到哪个Redis节点下进行处理。可以看到,在集群模式下,任何一个Redis节点都将不再存储全部的数据,而只是存储一部分。此举显然提高了容量上限。当然为了保证高可用,集群模式下还可以为Redis节点设置若干个从库。这样一旦主节点异常停机了,即选择其下的一个从节点自动切换为主库

配置节点

这里我们配置6个Redis节点,分别使用7001~7006端口。节点Redis-Cluster1的配置如下所示,其他节点配置同理

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
...
# 注释bind配置项
# bind 127.0.0.1

# 设为 no, 关闭保护模式
protected-mode no

# 端口设为7001
port 7001

# 修改数据库数量, 用于验证配置文件是否生效
databases 5

# 设置工作目录
dir "/data"

# 设置Redis密码
requirepass 123456

# 设为no, 因为docker启动时会通过-d参数来让其实现后台运行
daemonize no

# 使能集群模式
cluster-enabled yes

# 设置访问主库时的密码
masterauth "123456"
...

各节点的目录结构如下所示

figure 15.jpeg

现在通过Docker启动各Redis节点。值得一提的是,对于各Redis节点,我们不仅仅开放、映射供客户端访问的端口,例如节点1的7001端口。还需要开放、映射一个供集群内部通讯的端口,端口号默认为 客户端访问的端口号+10000,故节点1还映射了17001端口。其他节点同理

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
44
45
46
47
# 启动节点1
docker run \
-v /Users/zgh/Docker/Redis/Redis-Cluster1/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Cluster1/Data:/data \
-d -p 7001:7001 -p 17001:17001\
--name Redis-Cluster1 \
redis redis-server /etc/redis/redis.conf

# 启动节点2
docker run \
-v /Users/zgh/Docker/Redis/Redis-Cluster2/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Cluster2/Data:/data \
-d -p 7002:7002 -p 17002:17002\
--name Redis-Cluster2 \
redis redis-server /etc/redis/redis.conf

# 启动节点3
docker run \
-v /Users/zgh/Docker/Redis/Redis-Cluster3/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Cluster3/Data:/data \
-d -p 7003:7003 -p 17003:17003\
--name Redis-Cluster3 \
redis redis-server /etc/redis/redis.conf

# 启动节点4
docker run \
-v /Users/zgh/Docker/Redis/Redis-Cluster4/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Cluster4/Data:/data \
-d -p 7004:7004 -p 17004:17004\
--name Redis-Cluster4 \
redis redis-server /etc/redis/redis.conf

# 启动节点5
docker run \
-v /Users/zgh/Docker/Redis/Redis-Cluster5/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Cluster5/Data:/data \
-d -p 7005:7005 -p 17005:17005 \
--name Redis-Cluster5 \
redis redis-server /etc/redis/redis.conf

# 启动节点6
docker run \
-v /Users/zgh/Docker/Redis/Redis-Cluster6/Config/redis.conf:/etc/redis/redis.conf \
-v /Users/zgh/Docker/Redis/Redis-Cluster6/Data:/data \
-d -p 7006:7006 -p 17006:17006 \
--name Redis-Cluster6 \
redis redis-server /etc/redis/redis.conf

创建集群

在主从模式一章中,我们是通过Docker分配容器的IP进行容器间通信。这里,我们使用宿主机IP来进行通信。通过ifconfig命令获取宿主机IP为192.168.0.112。然后利用redis-cli来创建含有6个节点的集群,其中 —cluster-replicas 1 表示每个主库拥有的从库数量

1
2
3
4
5
6
docker exec -it Redis-Cluster1 \
redis-cli -p 7001 -a 123456 --cluster create \
192.168.0.112:7001 192.168.0.112:7002 \
192.168.0.112:7003 192.168.0.112:7004 \
192.168.0.112:7005 192.168.0.112:7006 \
--cluster-replicas 1

执行命令后,会分配这个6个Redis节点各自的角色(主库,从库),及各主库所属的对应Slot范围。然后输入yes确认,即会创建一个含有6个节点(3主3从)的Redis集群

figure 16.jpeg

实践

集群已经搭建完毕,可从任意一个节点来获取集群的节点信息

1
2
# 查看集群的节点信息
cluster nodes

效果如下所示

figure 17.jpeg

可通过下述命令查看各Slot所对应的节点

1
2
# 查看Slot分配情况
cluster slots

效果如下所示,其中每个Slot范围的第一个节点信息为主库

figure 18.jpeg

通过redis-cli连接访问时,可通过添加-c参数实现自动重定向到Key所在的Redis节点,效果如下所示

figure 19.png

Note

  • 本文所使用的Redis版本为6.0.8
  • 在Windows系统下使用-v选项挂载目录时,需要注意路径的写法。例如,Windows系统的路径 D:\Docker\Redis 应该写为 /D/Docker/Redis
  • 挂载的宿主机目录前,需要先在Docker中进行如下设置

figure 20.jpeg

参考文献

  1. Redis入门指南·第2版 李子骅著
0%