1.基本概述
因为哨兵加主从复制,并不能保证数据零丢失,如果master宕机,而在未选举出master时,无法处理写操作,而可能导致数据丢失。
集群就来了。
允许多个master

2.哈希槽算法分区
那么Client通过写入数据时,怎么知道写入哪个master或从哪个slave读呢?
就有了通过哈希取余算法分区,一致性哈希算法分区,和哈希槽算法分区。
最优解就是哈希槽算法分区。
哈希取余分区:
N指多少台实例

虽然这样简单操作,但是不方便扩容。节点数即N值变动会导致hash取余全部数据变化。
一致性哈希算法分区:
可以看看我的另一篇博客
缺点主要是数据倾斜问题。
就有了哈希槽算法分区:
redis集群的每个节点负责一部分hash槽,而Client通过写入数据时,每个key通过CRC16校验后对16384取模来决定放置哪个槽,这是固定的,所以读也可以根据这个算法找到。
哈希槽实质就是一个数组,数组[0,2^14-1]形成hash slot空间。
官网描述要求

大致意思就是:
集群的键空间被分成16384个槽,有效地为集群大小设置了16384个主节点的上限(然而,建议的最大节点大小约为~ 1000个节点)。
集群中的每个主节点处理16384个哈希槽的一个子集。当没有进行集群重新配置时(即哈希槽从一个节点移动到另一个节点),集群是稳定的。当集群稳定时,单个哈希槽将由单个节点提供服务(然而,服务节点可以有一个或多个副本,在网络分裂或故障的情况下取代它,并且可以用于扩展读取操作,在可以接受读取陈旧数据的情况下)。
用于将键映射到哈希槽的基本算法如下:
HASH_SLOT = CRC16(key) mod 16384
CRC16算法产生的hash值有16bit,该算法可以产生2^16=65536个值。
那么为什么要使用16384呢?
在redis源码中

在消息头中最占空间的是myslots[CLUSTER_SLOTS/8]。
- 当槽位为65536时,这块的大小是: 65536÷8÷1024=8kb
- 当槽位为16384时,这块的大小是: 16384÷8÷1024=2kb
因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,过大会浪费带宽。
redis cluster节点数量不建议超过1000个。否则消息传递会耗时严重。 所以对于节点数在1000以内的redis cluster集群,16384个槽位够用了。
Redis主节点的配置信息中它所负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots / N很高的话(N表示节点数),bitmap的压缩率就很低。压缩率越高,传输效率就会越高。 所以如果节点数很少,而哈希槽数量越少,bitmap的压缩率越高,传输效率才越高。
大致流程如下:
- redis 会根据节点数量大致均等的将哈希槽映射到不同的节点。
- 对key求哈希值,然后对16384取模,余数是几,key就落入对应的槽里。

而分片就是使用Redis集群将存储的数据分散到多台redis机器上,而集群中的每个Redis实例都存储了一部分数据,而称为分片。
优势:如果想添加新的节点,只要转移一点槽位即可。而删除节点也只需要转移槽位到其他节点即可。
3.集群配置
集群搭建
配置文件启动
6台主机创建工作目录,不过我没有那么多主机,所以在一台上实验,不过操作一样,以下各主机使用redis加端口号称呼。
mkdir -p /myredis/cluster cd /myredis/cluster/
在redis6381主机编写配置文件
vim /myredis/cluster/redisCluster6381.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6381 logfile "/myredis/cluster/cluster6381.log" pidfile /myredis/cluster6381.pid dir /myredis/cluster dbfilename dump6381.rdb appendonly yes appendfilename "appendonly6381.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6381.conf cluster-node-timeout 5000
cluster-enabled参数表示打开集群
cluster-config-file表示集群配置文件
cluster-node-timeout表示超时时间
在redis6382主机编写配置文件
vim /myredis/cluster/redisCluster6382.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6382 logfile "/myredis/cluster/cluster6382.log" pidfile /myredis/cluster6382.pid dir /myredis/cluster dbfilename dump6382.rdb appendonly yes appendfilename "appendonly6382.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6382.conf cluster-node-timeout 5000
在redis6383主机编写配置文件
vim /myredis/cluster/redisCluster6383.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6383 logfile "/myredis/cluster/cluster6383.log" pidfile /myredis/cluster6383.pid dir /myredis/cluster dbfilename dump6383.rdb appendonly yes appendfilename "appendonly6383.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6383.conf cluster-node-timeout 5000
在redis6384主机编写配置文件
vim /myredis/cluster/redisCluster6384.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6384 logfile "/myredis/cluster/cluster6384.log" pidfile /myredis/cluster6384.pid dir /myredis/cluster dbfilename dump6384.rdb appendonly yes appendfilename "appendonly6384.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6384.conf cluster-node-timeout 5000
在redis6385主机编写配置文件
vim /myredis/cluster/redisCluster6385.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6385 logfile "/myredis/cluster/cluster6385.log" pidfile /myredis/cluster6385.pid dir /myredis/cluster dbfilename dump6385.rdb appendonly yes appendfilename "appendonly6385.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6385.conf cluster-node-timeout 5000
在redis6386主机编写配置文件
vim /myredis/cluster/redisCluster6386.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6386 logfile "/myredis/cluster/cluster6386.log" pidfile /myredis/cluster6386.pid dir /myredis/cluster dbfilename dump6386.rdb appendonly yes appendfilename "appendonly6386.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6386.conf cluster-node-timeout 5000
全部启动
redis-server /myredis/cluster/redisCluster6381.conf
redis-server /myredis/cluster/redisCluster6382.conf
redis-server /myredis/cluster/redisCluster6383.conf
redis-server /myredis/cluster/redisCluster6384.conf
redis-server /myredis/cluster/redisCluster6385.conf
redis-server /myredis/cluster/redisCluster6386.conf
查看redis服务,当然我是在同一台主机上启动才能看到所有redis服务,不同主机应该只能看到一个后接[cluster]的服务,cluster就是表示以集群方式启动。
ps -ef | grep redis

构建主从关系
现在构建主从关系。–cluster create表示创建集群,–cluster-replicas 1 表示为每个master创建一个slave节点,-a后接密码
redis-cli -a 123456 --cluster create --cluster-replicas 1 8.134.133.76:6381 8.134.133.76:6382 8.134.133.76:6383 8.134.133.76:6384 8.134.133.76:6385 8.134.133.76:6386
顺便一台主机执行


这里ip相同是因为我在同一台主机上做实验,实际应该不一样,不过操作一样的。

输入yes同意

这时就生成了node节点信息文件,我这里有这么多node信息文件是我在同一台主机上做实验,实际应该只有一个。

进入一个客户端,比如6381
redis-cli -a 123456 -p 6381

查看主从关系
info replication

查看集群端口关系
可以看到,redis6381是master,对应slave是redis6384;redis6382是master,对应slave是redis6385;redis6383是master,对应slave是redis6386。
cluster nodes



查看集群信息
cluster info

好了,问题来了
在集群上,并不能顺便创建key
比如,他会提示我们他算出的HASH_SLOT是12706,提示我们应该在6383上执行

可以看到在6383上成功

路由分配
所以使用集群时需要路由分配,不过实际只要接入客户端加入-c参数即可
redis-cli -a 123456 -p 6381 -c


多建操作
不在同一个slot槽位下的键值无法使用mset、mget等多键操作

使用{}来定义同一个组,key中{}内相同内容的键值对会被放到同一个slot槽位去

查看某个key的槽位
cluster keyslot k1

查看某槽位上是否被占用
1表示占用,0表示未占用
cluster countkeysinslot 12706

自动主从切换
关闭一个master,如6381,上面可以看到其对应的slave主机是redis6384。

可以看到自动升级为master节点
info replication

这里也检测到原master节点redis6381已经fail了
cluster nodes

重新启动redis6381,变成redis6384的slave节点
redis-server /myredis/cluster/redisCluster6381.conf
redis-cli -a 123456 -p 6381 -c
info replication

手动主从切换
redis6381当前是slave

手动主从切换
cluster failover

集群扩容
当前槽位分配

在redis6387主机编写配置文件
vim /myredis/cluster/redisCluster6387.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6387 logfile "/myredis/cluster/cluster6387.log" pidfile /myredis/cluster6387.pid dir /myredis/cluster dbfilename dump6387.rdb appendonly yes appendfilename "appendonly6387.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6387.conf cluster-node-timeout 5000
在redis6388主机编写配置文件
vim /myredis/cluster/redisCluster6388.conf
配置如下:
bind 0.0.0.0 daemonize yes protected-mode no port 6388 logfile "/myredis/cluster/cluster6388.log" pidfile /myredis/cluster6388.pid dir /myredis/cluster dbfilename dump6388.rdb appendonly yes appendfilename "appendonly6388.aof" requirepass 123456 masterauth 123456 cluster-enabled yes cluster-config-file nodes-6388.conf cluster-node-timeout 5000
各自启动,启动后他们都是master,且是独立的master
redis-server /myredis/cluster/redisCluster6387.conf
redis-server /myredis/cluster/redisCluster6388.conf
加入集群
redis-cli -a 密码 --cluster add-node 自己实际IP地址:6387 自己实际IP地址:6381
如:
redis-cli -a 123456 --cluster add-node 8.134.133.76:6387 8.134.133.76:6381

查看集群分配情况
redis-cli -a 123456 --cluster check 8.134.133.76:6381

重新分配槽位
redis-cli -a 密码 --cluster reshard IP地址:端口号
redis-cli -a 123456 --cluster reshard 8.134.133.76:6381

这里需要输入几个值

最后还要输入yes确认,然后就是移动了
等待它移动完就好

再次检查槽位号,这个时候redis6387还没有slave

如图,不过由于重新分配成本太高,所以分配给redis6387的槽位并不是连续的

给master节点添加新的slave
redis-cli -a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点ID
如:
redis-cli -a 密码 --cluster add-node 8.134.133.76:6388 8.134.133.76:6387 --cluster-slave --cluster-master-id 0ea3502ee6dd0466f2a948a36f7ce982b6f77e86

添加成功

集群缩容
获取需要下线ID
redis-cli -a 123456 --cluster check 8.134.133.76:6388

先删除从节点
redis-cli -a 密码 --cluster del-node ip:从机端口 从机6388节点ID
redis-cli -a 123456 --cluster del-node 8.134.133.76:6388 71f52dd8a927d506434763e5c057232cd3ffd9e5

重新分配槽位
重新分配6387的槽位,这里直接都给redis6381
redis-cli -a 123456 --cluster reshard 8.134.133.76:6381
填入多个值,这里直接都给redis6381,否则要多输入几次。

输入yes

这时发现redis6387变成了slave

删除6387节点
redis-cli -a 密码 --cluster del-node ip:端口 6387节点ID
redis-cli -a 123456 --cluster del-node 8.134.133.76:6387 0ea3502ee6dd0466f2a948a36f7ce982b6f77e86

缩容成功

docker搭建
使用docker搭建集群,事先说明,这只是学习才这样,毕竟一下子也无法搞到6台主机。
docker run -d --net host --name redis-6381 --privileged=true --restart=always -v /docker_data/redis/data6381:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6381 docker run -d --net host --name redis-6382 --privileged=true --restart=always -v /docker_data/redis/data6382:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6382 docker run -d --net host --name redis-6383 --privileged=true --restart=always -v /docker_data/redis/data6383:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6383 docker run -d --net host --name redis-6384 --privileged=true --restart=always -v /docker_data/redis/data6384:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6384 docker run -d --net host --name redis-6385 --privileged=true --restart=always -v /docker_data/redis/data6385:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6385 docker run -d --net host --name redis-6386 --privileged=true --restart=always -v /docker_data/redis/data6386:/data redis:6.2.6 --cluster-enabled yes --appendonly yes --port 6386
其余命令同上。
4.注意
打开redis配置文件
vim redis.conf

这个参数表示需要保证集群完整才能对外提供服务,默认yes,即当为3主3从时,一个master挂了,整个集群就不完整了,在这种情况下,redis全部数据是不会对外提供服务的。
将该参数设置为no ,那挂了的那个小集群(一主一从)不会对外提供服务,但是其他的小集群仍然可以对外提供服务。
注意:redis集群也不是完全不会丢失数据的
也就是Redis集群不保证强一致性!
当一个写操作到达主节点,但是这个主节点还未通过主节点和副本节点之间使用的异步复制传播到副本节点时死亡且在一定时间内不可达,所以它的一个副本将会被升为主节点,那么这个写操作将永远丢失。不过主节点一般大约在同一时间回复客户端(承认写入,写操作成功)和写入副本。所以可能也不常见。


