yield-bytes

沉淀、分享与无限进步

redis-cluster原理及其部署测试

1、Part I

这里主要讨论redis集群数据分片的内容

1.1 为何使用redis-cluster模式?

1)首先避免单点故障,本人项目中用了主从模式,但因并发量不高,而且在redis不可用条件下,可以直接去数据库拿数据,所以还未部署集群模式。

2)redis官方给出单服务最高可以达到每秒执行10万条命令的性能,其实这对于绝大部分项目都够用了,这里给出集群模式的讨论,一为了深入了解redis,二是如果有一种需求需要百万/s的操作,显然redis单服务无法满足需求

3)内存吃满,redis首先将数据写在内存中,同时不定时异步持久化存在硬盘,一般服务器32G、64G、128G内存,若redis接收的数据量高达1T,128G内存的高性能服务器也会崩溃,故需要做

数据分片(sharding),分别存储在多个redis服务器中,这就需要redis集群模式支持了,这不就是经典的分布式数据库思想吗!

1.2 客户端分片

redis集群采用P2P模式,完全去中心化,将redis所有的key分成了16384个槽位,每个redis实例负责一部分slot,集群中的所有信息通过节点数据交换而更新。redis实例集群主要思想是将redis数据的key进行散列,通过hash函数特定的key会映射到指定的redis节点上

1.3 数据分布基础

分布式数据库首要解决把整个数据集按照分区规则映射到多个节点的问题,即把数据集划分到多个节点上,每个节点负责整个数据的一个子集。常见的分区规则有哈希分区和顺序分区。Redis Cluster采用哈希分区规则,因此接下来会讨论哈希分区规则。

  • 节点取余分区
  • 一致性哈希分区
  • 虚拟槽分区(redis-cluster采用的方式)

1)顺序分片

顺序分片按数据大小比较后进行分片

1
2
3
4
graph TD
id1((数据1-100)) --> id2((数据1-33))
id1((数据1-100)) --> id3((数据34-66))
id1((数据1-100)) --> id4((数据67-100))

2)节点取余分区算法

以三个节点为例:1~100对3取余后有三种情况:余数为0,余数为1,余数2;(除数n,余数情况为n-1种)

1
2
3
4
graph TD
id1((数据1-100)) -->|hashkey后取余%3|id2((3,6,..99))
id1((数据1-100)) -->|hashkey后取余%3| id3((1,4,..100))
id1((数据1-100)) -->|hashkey后取余%3| id4((2,5,..98))

对于N个redis节点,那么数据分片为:hash(key)%N,节点取余分区算法显然是简单高效的

1.4 虚拟槽分区

虚拟槽分区巧妙地使用了哈希空间,使用分散度良好的哈希函数把所有的数据映射(通过计算CRC16(key)%16383)到一个固定范围内的整数集合,redis将这种整数定义为槽(slot),Redis Cluster槽的范围是0~16383,总共有16384个哈希槽,槽是集群内数据管理和迁移的基本单位,每个节点负责一定数量的槽,例如有三个redis节点,那么槽位范围分配为:

redis node 1==>slots:[0-5460]

redis node 2==>slots:[5461-10922]

redis node 2==>slots:[10923-16383]

文章后面在部署集群创建槽位分区会看到这个配置

当有某个key被映射到某个Master节点负责的槽,那么这个Master节点负责为这个key提供服务,只有Master节点才拥有槽的所有权,如果是某个Master的slave,这个slave节点只负责槽的使用,但是没有所有权。

1.5 redis的槽位数量为什么是16384(2^14)个?

参考redis的作者:在redis节点发送心跳包时需要把所有的槽放到这个心跳包里,以便让节点知道当前集群信息,16384=16k,在发送心跳包时使用char进行bitmap压缩后是2k(2 * 8 (8 bit) * 1024(1k) = 2K),也就是说使用2k的空间创建了16k的槽数。

虽然使用CRC16算法最多可以分配65535(2^16-1)个槽位,65535=65k,压缩后就是8k(8 * 8 (8 bit) * 1024(1k) = 8K),也就是说需要需要8k的心跳包,作者认为这样做不太值得;并且一般情况下一个redis集群不会有超过1000个master节点,所以16k的槽位是个比较合适的选择。

1.6 redis-cluster小结:

1)所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽.

2)节点的fail是通过集群中超过半数的节点检测失效时才生效.通过投票机制

3)客户端与redis节点直连,不需要中间proxy层.客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可

4)redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster 负责维护node<->slot<->value

5)redis 集群中内置了 16384 个哈希槽,当需要在 Redis 集群中放置一个 key-value 时,redis客户端先对 key 使用 crc16 算法算出一个结果,然后把结果对 16384 求余数,这样每个 key 都会对应一个编号在 0-16383 之间的哈希槽,redis 会根据节点数量大致均等的将哈希槽映射到不同的节点

2、Part 2

2.1 redis单服务安装和配置

这里先给出单个redis服务安装和部署过程,之后会基于多个实例集合redis自身的集群工具搭建redis集群

安装stable版本:目前未redis5.05版本,下载地址

这里以安装在/usr/local目录下为例啊,安装和编译,当然需要服务器已经具备c环境yum install gcc-c++

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
[root@localhost local]# tar -xzf redis-5.0.5.tar.gz
[root@localhost local]# cd redis-5.0.5
[root@localhost redis-5.0.5]# make
[root@localhostredis-5.0.5]# cd ./src #进入到redis-5.0.5/src 文件目录下
[root@localhost src]# make test #
Hint: It's a good idea to run 'make test' ;)
INSTALL install
INSTALL install
INSTALL install
INSTALL install
INSTALL install
#redis 可执行脚本都在 utils目录下
[root@localhost redis-5.0.5]# ls utils/
build-static-symbols.tcl hashtable redis_init_script.tpl
cluster_fail_time.tcl hyperloglog redis-sha1.rb
corrupt_rdb.c install_server.sh releasetools
create-cluster lru speed-regression.tcl
generate-command-help.rb redis-copy.rb whatisdoing.sh
graphs redis_init_script

# 为方便将集群中多个配置文件放在一块管理,可以新建一个redis-conf目录,放置多个redis实例的启动配置文件
[root@localhost redis-conf]# ls
redis.conf
[root@localhost redis-conf]# vi redis.conf # 将redis改为后台运行
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize yes
#容许远程访问(宿主机访问)
#bind 127.0.0.1
# 设置密码
requirepass foofoo

# 启动一个实例
[root@localhost redis-5.0.5]# redis-server redis-conf/redis.conf
12797:C 12 Sep 2019 15:20:41.243 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
12797:C 12 Sep 2019 15:20:41.243 # Redis version=5.0.5, bits=64, commit=00000000, modified=0, pid=12797, just started
12797:C 12 Sep 2019 15:20:41.243 # Configuration loaded

# 本地客户端登录测试
[root@localhost redis-5.0.5]# redis-cli
127.0.0.1:6379>
127.0.0.1:6379> set test foo
(error) NOAUTH Authentication required # 提示需要密码认证
127.0.0.1:6379> AUTH foofoo #输入密码
OK
127.0.0.1:6379> set test foo
OK
127.0.0.1:6379> get test
"foo"

设置redis开机启动

1
2
[root@localhost redis-conf]# vi /etc/rc.d/rc.local 
redis-server /usr/local/redis-5.0.5/redis-conf

以上完成redis单服务部署,显然部署过程相当简单,用redis-server 启动一个配置文件,则相应启动一个实例,==若需要在单服务器启动多个redis实例,则只需把整个redis5.0.5目录拷贝多份,修改配置文件redis.conf相应监听端口号等某几个设置项即可==,考虑到后面文章会实现高可用的redis分布式锁,这里会启动6个redis实例集群,以模拟真实集群服务。

为什么是6个实例?

因Redis的容错投票机制是集群中过半数的节点认为某个节点检测失效时才生效,因此最小集群模式至少需要三个节点,而为了高可用,还需要为这三个节点各增加一个slave节点,因此这里就需要6个。而在redis分布式锁算法中,需要在超过半数的redis实例中设置锁成功才能认为获得锁。

2.2 启动多个redis实例的配置

创建放置集群redis目录,方便管理/usr/local/redis-cluster

首先设置redis1,这里给出每个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
[root@localhost redis-cluster]# vi redis1/redis-conf/redis.conf
# 放通全网访问(当然是这内网访问)
bind 0.0.0.0
# 后台运行
daemonize yes
# 打开远程访问
protected-mode no
#设置监听端口
port 23451
# 设置redis进程pid路径
pidfile /var/run/redis_23451.pid
# 设置集群密码
masterauth foofoo
# 设置本服务密码
requirepass foofoo
#设置开启AOF模式
appendonly yes
# 指定redis日志文件,方便查看启动日志或者出错日志,需自行创建,每个实例使用不同日志文件
logfile "/var/log/redis/redis1.log"
# 启用集群模式
cluster-enabled yes
# 指定集群节点配置文件路径,redis实例会创建该文件,每个实例使用不同nodes.conf
cluster-config-file /usr/local/redis-cluster/nodes-conf/nodes-1.conf
cluster-node-timeout 5000

将redis1拷贝多份到 /usr/local/redis-cluster目录下,并命名

1
2
3
4
5
6
7

[root@localhost redis-cluster]# pwd
/usr/local/redis-cluster
[root@localhost redis-cluster]# mv redis-5.0.5/ redis1
[root@localhost redis-cluster]# ls
[root@localhost redis-cluster]# ls
redis1 redis2 redis3 redis4 redis5 redis6

在redis2-redis6目录,修改redis.conf文件,只需修改端口号、pid文件、logfile、cluster-config-file,其他设置项一致。端口也可以按业务规定划分,这里设为五位数端口23452~23456。

手工一个个去启动实例未免麻烦,可以在写个start_all_redis.sh,批量启动,作为测试环境,这里不再将其随机启动。

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost redis-cluster]# ls
appendonly.aof nodes-6379.conf redis1 redis2 redis3 redis4 redis5 redis6 start_all_redis.sh

[root@localhost redis-cluster]# vi start_all_redis.sh
redis-server /usr/local/redis-cluster/redis1/redis-conf/redis.conf
redis-server /usr/local/redis-cluster/redis2/redis-conf/redis.conf
redis-server /usr/local/redis-cluster/redis3/redis-conf/redis.conf
redis-server /usr/local/redis-cluster/redis4/redis-conf/redis.conf
redis-server /usr/local/redis-cluster/redis5/redis-conf/redis.conf
redis-server /usr/local/redis-cluster/redis6/redis-conf/redis.conf

# 最加执行权限
[root@localhost redis-cluster]# chmod +x start_all_redis.sh

启动六个实例

1
2
3
4
5
6
7
8
9
[root@localhost redis-cluster]# sh start_all_redis.sh 
[root@localhost redis-cluster]# ps -ef|grep redis
root 7130 1 0 16:55 ? 00:00:00 redis-server *:23451 [cluster]
root 7132 1 0 16:55 ? 00:00:00 redis-server *:23452 [cluster]
root 7137 1 0 16:55 ? 00:00:00 redis-server *:23453 [cluster]
root 7142 1 0 16:55 ? 00:00:00 redis-server *:23454 [cluster]
root 7147 1 0 16:55 ? 00:00:00 redis-server *:23455 [cluster]
root 7152 1 0 16:55 ? 00:00:00 redis-server *:23456 [cluster]
root 7160 7091 0 16:55 pts/2 00:00:00 grep --color=auto redis

去日志路径查看启动日志,这里截取了一部分日志内容,可以看到redis是集群模式,而且后面还有redis给出3项优化建议

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost redis]# pwd
/var/log/redis
[root@localhost redis]# cat redis4.log
Redis 5.0.5 (00000000/0) 64 bit
Running in cluster mode
Port: 23454
PID: 7142

# WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

# WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

# WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
* Ready to accept connections

2.3 测试redis集群情况

登录其中一个redis实例并测试,提示没有slot

1
2
3
4
5
[root@localhost redis-cluster]# redis-cli -p 23451 -c
127.0.0.1:23451> AUTH foofoo
OK
127.0.0.1:23451> set test foo
(error) CLUSTERDOWN Hash slot not served

==以上只是启动六个redis,但redis集群的slot还未分配到每个redis上,还需最后一步分配slot!==

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
[root@localhost redis-cluster]# redis-cli  --cluster create 127.0.0.1:23451 127.0.0.1:23452 127.0.0.1:23453 127.0.0.1:23454 127.0.0.1:23455 127.0.0.1:23456  --cluster-replicas 1 -a foofoo
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 127.0.0.1:23455 to 127.0.0.1:23451
Adding replica 127.0.0.1:23456 to 127.0.0.1:23452
Adding replica 127.0.0.1:23454 to 127.0.0.1:23453
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master

# redis1实例作为master角色
M: 920db62a3c29e3cb28dcbb575d4a438761563feb 127.0.0.1:23451
slots:[0-5460] (5461 slots) master

# redis2实例作为master角色
M: 3c54734e178ca585477c8fb8b78d87d0d7a1e038 127.0.0.1:23452
slots:[5461-10922] (5462 slots) master

# redis3实例作为master角色
M: beb60be98bdc8ce50825b9d42e5efd819b98dc34 127.0.0.1:23453
slots:[10923-16383] (5461 slots) master

# redis4实例作为slave角色
S: 4d451a885c1974679fe7c193ecac0373bd5cd808 127.0.0.1:23454
replicates 3c54734e178ca585477c8fb8b78d87d0d7a1e038

# redis5实例作为slave角色
S: 6ea365783619da802cd917b10fafd6096b4166a5 127.0.0.1:23455
replicates beb60be98bdc8ce50825b9d42e5efd819b98dc34

# redis6实例作为slave角色
S: 120026cbf95be79f4a3b2ed32c6867e238b7f8ec 127.0.0.1:23456
replicates 920db62a3c29e3cb28dcbb575d4a438761563feb
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:23451)
M: 920db62a3c29e3cb28dcbb575d4a438761563feb 127.0.0.1:23451
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: 4d451a885c1974679fe7c193ecac0373bd5cd808 127.0.0.1:23454
slots: (0 slots) slave
replicates 3c54734e178ca585477c8fb8b78d87d0d7a1e038
M: 3c54734e178ca585477c8fb8b78d87d0d7a1e038 127.0.0.1:23452
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: 120026cbf95be79f4a3b2ed32c6867e238b7f8ec 127.0.0.1:23456
slots: (0 slots) slave
replicates 920db62a3c29e3cb28dcbb575d4a438761563feb
M: beb60be98bdc8ce50825b9d42e5efd819b98dc34 127.0.0.1:23453
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 6ea365783619da802cd917b10fafd6096b4166a5 127.0.0.1:23455
slots: (0 slots) slave
replicates beb60be98bdc8ce50825b9d42e5efd819b98dc34
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
# 提示16384个槽位已经分区好
[OK] All 16384 slots covered.

登录其中一台,注意这是集群模式登录,如果不带密码-a,则在set值时,分配到其它节点槽号将提示认证失败

1
2
3
4
5
6
[root@localhost redis-cluster]# redis-cli -p 23451 -c
127.0.0.1:23451> AUTH foofoo
OK
127.0.0.1:23451> set test foo
-> Redirected to slot [6918] located at 127.0.0.1:23452
(error) NOAUTH Authentication required

启动客户端需要带上密码选项,可以看到成功set值,通过设置不同的key,可以看到数据被hash后重定向到不同节点上的槽号

1
2
3
4
5
6
7
8
9
10
[root@localhost redis-cluster]# redis-cli -p 23451 a foofoo -c
127.0.0.1:23451> set test foo
-> Redirected to slot [6918] located at 127.0.0.1:23452
OK
127.0.0.1:23452> set cluster-test 11
-> Redirected to slot [14783] located at 127.0.0.1:23453
OK
127.0.0.1:23453> get test
-> Redirected to slot [6918] located at 127.0.0.1:23452
"foo"

以上完成了cluster模式的部署和测试,接下将给出redis主从模式,以及使用docker部署redis服务,之后给出了基于redis集群实现的高可用分布式锁Redlock的过程,在此之前已经给出了zookeeper的分布式锁实现。