(三)redis docker 集群配置

背景

当业务场景需要足够大量的Redis存储时,主从结构+哨兵可以实现高可用故障切换+冗余备份,但是并不能解决数据容量的问题,用哨兵Redis每个势力也是全量存储,每个Redis存储的内容都是完整的数据,良妃内存且存在木桶效应。
为了最大化利用内存,可以采用cluster集群,就是分布式存储。即每台Redis存储不同的内容。

分布式方案:

  1. 客户端分区方案:优点是分区逻辑可控,缺点是需要自己处理数据路由、高可用、故障转移等问题。在redis2.8之前使用key的hashcode取余分布到不同的节点,一旦节点的增删,都会导致key无法命中。
  2. 代理方案:简化客户端分布式逻辑和升级维护遍历。缺点是加重架构部署复杂度和性能损耗,如:twemproxy,codis
  3. 官方为我们提供了专有的集群方案:redis cluster

redis集群提供了以下两个好处:

  1. 将数据自动切分到多个节点的能力
  2. 当集群中的一部分节点失效或者无法进行通讯时,仍然可以继续处理名利请求的能力,拥有自动故障转移的能力
  3. 客户端可以与任何一个节点相连接,然后就可以访问集群中的任何一个节点,对其进行存取和其它操作

redis cluster vs replication + sentinal 如何选择

  1. 如果数据很少,主要是承载高并发高性能的场景,单机即可
  2. replication:一个master,多个slave,要几个slave跟你要求的读吞吐量有关系,结合sentinal集群,去保证Redis主从架构的高可用性,就可以了
  3. redis cluster:主要是针对海量数据+高并发+高可用的场景

分布式是如何进行的

顺序分区和哈希分区

数据倾斜 + 数据迁移 + 节点伸缩

顺序分区:会导致数据倾斜,主要是访问倾斜。每次点击会重点访问某台机器,这就导致最后数据都到这台机器上了。
哈希分区:但需要扩容时,专业上称之为”节点伸缩“,这个时候,因为是哈希算法,会导致数据迁移。
一致性哈希:加减节点会造成哈希环中部分数据无法命中,需要手动处理或者忽略这部分数据,因此一致性哈希常用于缓存场景。
虚拟槽分区:使用CRC16算法,钱庙的使用了哈希空间,使用分散度良好的哈希函数把所有数据映射到一个固定范围的整数集合中,整数定义为槽。redis cluster槽的范围是0~16383,槽是集群内数据管理和迁移的基本单位。采用大范围槽的主要母的是为了方便数据拆分和集群扩展。每个节点会负责一定数量的槽。每当key访问过来,redis cluster会计算哈希值是否在这个区间里。它们彼此都知道对应的槽在哪台机器上,这样就能做到平均分配了。

集群限制

key批量操作,mget()

docker-compose

Docker Compose 是 Docker 官方编排(Orchestration)项目之一,负责快速的部署分布式应
用。Compose 允许用户通过一个单独的 docker-compose.yml 模板文件
(YAML 格式)来定义一组相关联的应用容器为一个项目(project)。
Compose 中有两个重要的概念:

  • 服务 ( service ):一个应用的容器,实际上可以包括若干运行相同镜像的容器实例。
  • 项目 ( project ):由一组关联的应用容器组成的一个完整业务单元,在 dockercompose.yml 文件中定义。

搭建集群

配置

1
2
3
4
5
6
7
8
9
10
11
#节点端口
port 6379

#开启集群模式
cluster-enabled yes

#节点超时时间,单位毫秒
cluster-node-timeout 15000

#集群内部配置文件
cluster-config-file ”nodes-6379.conf“

构建redis-cluster镜像

文件路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@VM_0_6_centos usr]# tree /usr/docker/
/usr/docker/
└── redis
├── config
│   ├── nodes-6391.conf
│   ├── nodes-6392.conf
│   ├── nodes-6393.conf
│   ├── nodes-6394.conf
│   ├── nodes-6395.conf
│   ├── nodes-6396.conf
│   ├── redis.sh
│   └── redis-trib.rb
└── Dockerfile

2 directories, 9 files

Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM centos:latest
RUN groupadd -r redis && useradd -r -g redis redis
RUN yum -y install wget \
&& wget http://mirrors.sohu.com/fedora-epel/epel-release-latest-8.noarch.rpm \
&& rpm -ivh epel-release-latest-8.noarch.rpm \
&& yum clean all && yum makecache && yum -y update && yum -y install epel-release \
&& yum -y install redis && yum -y install wget \
&& yum -y install net-tools \
&& yum -y install ruby && yum -y install rubygems
RUN wget https://nuoyit.oss-cn-hangzhou.aliyuncs.com/package/redis-3.2.1.gem && gem install -l ./redis-3.2.1.gem \
&& rm -f redis-3.2.1.gem
COPY ./config/redis-trib.rb /usr/bin
COPY ./config/redis.sh /usr/bin
RUN mkdir -p /config && chmod 775 /usr/bin/redis.sh

dockerfile处理了在腾讯云装epel-release源问题,参考文章:
CentOS安装epel源详解

构建镜像
docker build -t redis-cluster .

1
2
3
[root@VM_0_6_centos redis]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
redis-cluster latest cda424fce4bf 2 minutes ago 391MB

准备节点

docker-compose.yml

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
version: "3.6"
services:
redis-cluster-master1:
image: redis-cluster
container_name: redis-cluster-master1
working_dir: /config
environment:
- PORT=6391
ports:
- "6391:6391"
- "16391:16391"
stdin_open: true
networks:
redis-master:
ipv4_address: 172.50.0.2
tty: true
privileged: true
volumes: ["/usr/docker/redis/config:/config"]
entrypoint:
- /bin/bash
- redis.sh
redis-cluster-master2:
image: redis-cluster
working_dir: /config
container_name: redis-cluster-master2
environment:
- PORT=6392
networks:
redis-master:
ipv4_address: 172.50.0.3
ports:
- "6392:6392"
- "16392:16392"
stdin_open: true
tty: true
privileged: true
volumes: ["/usr/docker/redis/config:/config"]
entrypoint:
- /bin/bash
- redis.sh
redis-cluster-master3:
image: redis-cluster
container_name: redis-cluster-master3
working_dir: /config
environment:
- PORT=6393
networks:
redis-master:
ipv4_address: 172.50.0.4
ports:
- "6393:6393"
- "16393:16393"
stdin_open: true
tty: true
privileged: true
volumes: ["/usr/docker/redis/config:/config"]
entrypoint:
- /bin/bash
- redis.sh
redis-cluster-slave1:
image: redis-cluster
container_name: redis-cluster-slave1
working_dir: /config
environment:
- PORT=6394
networks:
redis-slave:
ipv4_address: 172.30.0.2
ports:
- "6394:6394"
- "16394:16394"
stdin_open: true
tty: true
privileged: true
volumes: ["/usr/docker/redis/config:/config"]
entrypoint:
- /bin/bash
- redis.sh
redis-cluster-slave2:
image: redis-cluster
working_dir: /config
container_name: redis-cluster-slave2
environment:
- PORT=6395
ports:
- "6395:6395"
- "16395:16395"
stdin_open: true
networks:
redis-slave:
ipv4_address: 172.30.0.3
tty: true
privileged: true
volumes: ["/usr/docker/redis/config:/config"]
entrypoint:
- /bin/bash
- redis.sh
redis-cluster-slave3:
image: redis-cluster
container_name: redis-cluster-slave3
working_dir: /config
environment:
- PORT=6396
ports:
- "6396:6396"
- "16396:16396"
stdin_open: true
networks:
redis-slave:
ipv4_address: 172.30.0.4
tty: true
privileged: true
volumes: ["/usr/docker/redis/config:/config"]
entrypoint:
- /bin/bash
- redis.sh
networks:
redis-master:
driver: bridge
ipam:
driver: default
config:
-
subnet: 172.50.0.0/16
redis-slave:
driver: bridge
ipam:
driver: default
config:
-
subnet: 172.30.0.0/16
redis-test:
external:
name: mynetwork

redis.sh

1
redis-server  /config/nodes-${PORT}.conf

操作步骤

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
[root@VM_0_6_centos redis]# docker-compose -p redis-cluster up -d
WARNING: Some networks were defined but are not used by any service: redis-test
Creating redis-cluster-master1 ... done
Creating redis-cluster-salve2 ... done
Creating redis-cluster-master2 ... done
Creating redis-cluster-slave1 ... done
Creating redis-cluster-slave3 ... done
Creating redis-cluster-master3 ... done

[root@VM_0_6_centos redis]# docker exec -it redis-cluster-master1 bash
[root@7ba6993e9fcf config]# pwd
/config
# 文件已共享
[root@7ba6993e9fcf config]# ls
nodes-6391.conf nodes-6392.conf nodes-6393.conf nodes-6394.conf nodes-6395.conf nodes-6396.conf redis-trib.rb redis.sh
[root@7ba6993e9fcf config]#
# 查看节点ID
[root@7ba6993e9fcf config]# cat /var/lib/redis/nodes-6379.conf
df23468b9980f0814c35acdc4fb83968c8263d50 :0@0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
[root@7ba6993e9fcf config]#

# 登录客户端,参看集群信息
[root@7ba6993e9fcf config]# echo $PORT
6391
[root@7ba6993e9fcf config]# redis-cli -p 6391
127.0.0.1:6391> cluster info
cluster_state:fail
cluster_slots_assigned:0
cluster_slots_ok:0
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:1
cluster_size:0
cluster_current_epoch:0
cluster_my_epoch:0
cluster_stats_messages_sent:0
cluster_stats_messages_received:0
127.0.0.1:6391> cluster nodes
df23468b9980f0814c35acdc4fb83968c8263d50 :6391@16391 myself,master - 0 0 0 connected

节点握手

节点握手是指一批运行在集群模式下的节点通过Gossip协议彼此通信,到达感知对方的过程。由客户端发起命令:cluster meet {ip} {port}

cluster meet 127.0.0.1 6380

注意

  1. 每个redis cluster节点会占用两个TCP端口,一个监听客户端的请求,默认是6379,另一个在端口上加上10000,比如16379,来监听数据的请求,节点和节点之前会监听第二个端口,用一套二进制协议来通信。
    节点之间会通过这套协议来进行失败检测,配置更新,failover认证等等。
    为了保证节点之间的正常访问,需要注意防火墙的配置。
  2. 节点简历握手之后集群还不能正常工作,这时集群处于下线状态,所有的数据读写都被禁止
    1
    2
    127.0.0.1:6391> set age 18
    (error) CLUSTERDOWN Hash slot not served
  1. 作为一个完整的集群,需要主从节点,保证当他出现故障时可以自动进行故障转移。集群模式下,redis节点角色分为主节点和从节点。首次启动的节点和被分配槽的节点都是主节点,从节点负责复制主节点槽信息和相关数据

使用 cluster replicate {nodeid} 命令让一个节点称为从节点。其中命令执行必须在对应的从节点上执行,将当前节点设置为node_id指定的节点的从节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
127.0.0.1:6391> CLUSTER MEET 118.xx.xxx.54 6392
OK
127.0.0.1:6391> CLUSTER MEET 118.xx.xxx.54 6393
OK
127.0.0.1:6391> CLUSTER MEET 118.xx.xxx.54 6394
OK
127.0.0.1:6391> CLUSTER MEET 118.xx.xxx.54 6395
OK
127.0.0.1:6391> CLUSTER MEET 118.xx.xxx.54 6396
OK
127.0.0.1:6391> CLUSTER NODES
5689df453773f99399a2d93285c6906c0d7f7154 118.xx.xxx.54:6395@16395 master - 0 1574519726000 5 connected
bbba975c724f6ad8c734bcce627f0ae6304f446f 118.xx.xxx.54:6393@16393 master - 0 1574519728000 2 connected
90990230d7a0181a0a07144d9172004a737072f7 118.xx.xxx.54:6396@16396 master - 0 1574519728000 4 connected
6c8fa041a2c71a668b5ab07618c5bec4d1eadad8 118.xx.xxx.54:6392@16392 master - 0 1574519728576 1 connected
b920e563a56577786094bd940536f291b342495c 118.xx.xxx.54:6394@16394 master - 0 1574519729577 3 connected
df23468b9980f0814c35acdc4fb83968c8263d50 118.xx.xxx.54:6391@16391 myself,master - 0 1574519729000 0 connected

配置主从

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@cd4ac43057f5 config]# redis-cli -h 118.xx.xxx.54 -p 6394 CLUSTER REPLICATE df23468b9980f0814c35acdc4fb83968c8263d50
OK
[root@cd4ac43057f5 config]# redis-cli -h 118.xx.xxx.54 -p 6395 CLUSTER REPLICATE 6c8fa041a2c71a668b5ab07618c5bec4d1eadad8
OK
[root@cd4ac43057f5 config]# redis-cli -h 118.xx.xxx.54 -p 6396 CLUSTER REPLICATE bbba975c724f6ad8c734bcce627f0ae6304f446f
OK

[root@cd4ac43057f5 config]# redis-cli -h 118.xx.xxx.54 -p 6391
127.0.0.1:6391> CLUSTER NODES
5689df453773f99399a2d93285c6906c0d7f7154 118.xx.xxx.54:6395@16395 slave 6c8fa041a2c71a668b5ab07618c5bec4d1eadad8 0 1574520112000 5 connected
bbba975c724f6ad8c734bcce627f0ae6304f446f 118.xx.xxx.54:6393@16393 master - 0 1574520111000 2 connected
90990230d7a0181a0a07144d9172004a737072f7 118.xx.xxx.54:6396@16396 slave bbba975c724f6ad8c734bcce627f0ae6304f446f 0 1574520112276 4 connected
6c8fa041a2c71a668b5ab07618c5bec4d1eadad8 118.xx.xxx.54:6392@16392 master - 0 1574520113277 1 connected
b920e563a56577786094bd940536f291b342495c 118.xx.xxx.54:6394@16394 slave df23468b9980f0814c35acdc4fb83968c8263d50 0 1574520111273 3 connected
df23468b9980f0814c35acdc4fb83968c8263d50 118.xx.xxx.54:6391@16391 myself,master - 0 1574520110000 0 connected

分配槽

redis集群把所有的数据映射到16384个槽中。每个key会映射为一个固定的槽,只有当节点分配了槽,才能响应和这些槽关联的键命令。通过cluster addslots命令为节点分配槽

利用bash特性批量设置槽(slots),命令如下:

1
2
3
redis-cli -h 118.xx.xxx.54 -p 6391 cluster addslots {0..5461}
redis-cli -h 118.xx.xxx.54 -p 6392 cluster addslots {5462..10922}
redis-cli -h 118.xx.xxx.54 -p 6393 cluster addslots {10923..16383}

我们依照redis协议手动建立一个集群。它由6个节点组成,3个主节点负责处理漕河相关数据,3个从节点负责故障转移。redis官方提供了redis-trib.rb 工具方便我们快速搭建集群

测试

1
2
3
4
5
6
7
8
9
10
11
[root@7ba6993e9fcf config]# redis-cli -p 6391
118.xx.xxx.54:6391> set age 18
OK

[root@cd4ac43057f5 config]# redis-cli -h 118.xx.xxx.54 -p 6394
118.xx.xxx.54:6394> get age
(error) MOVED 741 118.xx.xxx.54:6391
118.xx.xxx.54:6394> exit
[root@cd4ac43057f5 config]# redis-cli -h 118.xx.xxx.54 -p 6391
118.xx.xxx.54:6391> get age
"18"

操作集群 -c 集群模式 redis-cli -c -p 6391 get age

1
2
3
4
5
[root@7ba6993e9fcf config]# redis-cli -c -h 118.xx.xxx.54 -p 6391 get age
"18"
[root@7ba6993e9fcf config]# redis-cli -c -h 118.xx.xxx.54 -p 6392 get age
"18"
[root@7ba6993e9fcf config]# redis-cli -c -h 118.xx.xxx.54 -p 6393 get age

redis-trib.rb 搭建集群

redis-trib.rb 是采用 Ruby实现的redis集群管理工具。内部通过cluster相关命令帮我们建立集群创建、检查、槽迁移和均衡等常见运维操作,使用之前需要安装Ruby依赖环境。

启动好6个节点之后,使用redis-trib.rb create命令完成节点握手和槽分配过程

1
redis-trib.rb create --replicas 1  118.xx.xxx.54:6391 118.xx.xxx.54:6392 118.xx.xxx.54:6393 118.xx.xxx.54:6394 118.xx.xxx.54:6395 118.xx.xxx.54:6396

--replicas 参数指定集群中每个主节点配备几个从节点,这里设置为1,redis-trib.rb 会尽可能保证主从节点不分配在同一机器下,因此会重新排序节点列表顺序。节点列表顺序用语确定主从角色,先主节点之后是从节点。创建过程中首先会给出主从节点角色分配的计划,并且会生成报告

操作记录

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
48
49
50
51
52
53
54
55
56
57
58
59
[root@544b4c16b923 config]# redis-trib.rb create --replicas 1 118.xx.xxx.54:6391 118.xx.xxx.54:6392 118.xx.xxx.54:6393 118.xx.xxx.54:6394 118.xx.xxx.54:6395 118.xx.xxx.54:6396
>>> Creating cluster
/usr/local/share/gems/gems/redis-3.2.1/lib/redis/client.rb:443: warning: constant ::Fixnum is deprecated
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
118.xx.xxx.54:6391
118.xx.xxx.54:6392
118.xx.xxx.54:6393
Adding replica 118.xx.xxx.54:6394 to 118.xx.xxx.54:6391
Adding replica 118.xx.xxx.54:6395 to 118.xx.xxx.54:6392
Adding replica 118.xx.xxx.54:6396 to 118.xx.xxx.54:6393
M: 561782045ab4a9332859c532ba5f5e8163b527b5 118.xx.xxx.54:6391
slots:0-5460 (5461 slots) master
M: daf938ff3ed7f571fcb63b2ad2cd952208ede80a 118.xx.xxx.54:6392
slots:5461-10922 (5462 slots) master
M: 4f2769409d9353896847337a04f09b5254ca00cf 118.xx.xxx.54:6393
slots:10923-16383 (5461 slots) master
S: 6a5973e8a0dc6c7db1831d0ef7322101be4bf109 118.xx.xxx.54:6394
replicates 561782045ab4a9332859c532ba5f5e8163b527b5
S: 4059332e853bb8d381ecaff6d802566f549932c2 118.xx.xxx.54:6395
replicates daf938ff3ed7f571fcb63b2ad2cd952208ede80a
S: d0e0b01e0a9becac7e6dd02cf52f5c28b316af83 118.xx.xxx.54:6396
replicates 4f2769409d9353896847337a04f09b5254ca00cf
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 118.xx.xxx.54:6391)
M: 561782045ab4a9332859c532ba5f5e8163b527b5 118.xx.xxx.54:6391
slots:0-5460 (5461 slots) master
1 additional replica(s)
M: daf938ff3ed7f571fcb63b2ad2cd952208ede80a 118.xx.xxx.54:6392@16392
slots:5461-10922 (5462 slots) master
1 additional replica(s)
S: d0e0b01e0a9becac7e6dd02cf52f5c28b316af83 118.xx.xxx.54:6396@16396
slots: (0 slots) slave
replicates 4f2769409d9353896847337a04f09b5254ca00cf
S: 6a5973e8a0dc6c7db1831d0ef7322101be4bf109 118.xx.xxx.54:6394@16394
slots: (0 slots) slave
replicates 561782045ab4a9332859c532ba5f5e8163b527b5
M: 4f2769409d9353896847337a04f09b5254ca00cf 118.xx.xxx.54:6393@16393
slots:10923-16383 (5461 slots) master
1 additional replica(s)
S: 4059332e853bb8d381ecaff6d802566f549932c2 118.xx.xxx.54:6395@16395
slots: (0 slots) slave
replicates daf938ff3ed7f571fcb63b2ad2cd952208ede80a
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

127.0.0.1:6391> CLUSTER NODES
daf938ff3ed7f571fcb63b2ad2cd952208ede80a 118.xx.xxx.54:6392@16392 master - 0 1574524576000 2 connected 5461-10922
d0e0b01e0a9becac7e6dd02cf52f5c28b316af83 118.xx.xxx.54:6396@16396 slave 4f2769409d9353896847337a04f09b5254ca00cf 0 1574524575566 6 connected
561782045ab4a9332859c532ba5f5e8163b527b5 172.50.0.2:6391@16391 myself,master - 0 1574524575000 1 connected 0-5460
6a5973e8a0dc6c7db1831d0ef7322101be4bf109 118.xx.xxx.54:6394@16394 slave 561782045ab4a9332859c532ba5f5e8163b527b5 0 1574524575000 4 connected
4f2769409d9353896847337a04f09b5254ca00cf 118.xx.xxx.54:6393@16393 master - 0 1574524577570 3 connected 10923-16383
4059332e853bb8d381ecaff6d802566f549932c2 118.xx.xxx.54:6395@16395 slave daf938ff3ed7f571fcb63b2ad2cd952208ede80a 0 1574524577000 5 connected

集群功能限制

redis集群相对淡季在功能上存在一些限制:

  1. key批量操作支持有限。如mset,mget。目前只支持具有相同slot值的key执行批量操作。
  2. key事务操作支持有限。同理只支持多key在同一节点上的事务操作。
  3. 不支持多数据库空间。单机下redis可以支持16个数据库,集群模式下只能使用一个数据库空间,即db0。
  4. 复制结构只支持一层,从节点只能复制主节点,不支持嵌套树状复制结构

利用redisC扩展支持Mset,Mget操作。 redisCluster
Predis包不支持Mset,Mget等批量操作