Redis集群
一、定义
由于数据量过大,单个master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展,每个复制集只负责存储整个数据集的一部分,这就是redis的集群。
Redis集群是一个提供在多个redis节点间共享数据的程序集,可以支持多个master
-
Redis集群支持多个master,每个master又可以挂载多个slave
-
由于Cluster自带的Sentinel的故障转移机制,内置了高可用的支持,无需再使用哨兵功能
-
客户端与redis的节点连接,不再需要连接集群中所有节点,只需要任意连接集群中一个可用节点即可
-
槽位slot负责分配到各个物理服务节点,由对应的集群负责维护节点、插槽和数据之间的关系
二、槽位slot
redis集群没有使用一致性hash,而是引入哈希槽的概念
redis集群有16384个哈希槽,当读或写数据时,每个key都通过 CRC16校验后,对16384取模来决定放置到哪个槽或从哪个槽读取,集群的每个节点负责一部分hash槽。
如果集群有三个节点:
分片:使用redis集群时,我们会将存储的数据分散到多台redis机器,这称为分片,集群中每个redis实例都被认为是整个数据的一个分片
插槽和分片的优势:方便扩缩容,和数据分派查找
由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
那么写一个数据,到底应该落到哪个槽位?
slot槽位映射有三种方案:
-
哈希取余分区
把写进来的key进行hash算法,然后对已有redis机器的数量进行取余,如果有3个机器,公式:hash(key) / 3
优点:比较简单,只需要预估好数据,规划好节点数量,就能保证一段时间的数据支撑。使用hash算法让固定的一部分请求落到同一台服务器上,这样每台服务器固定处理一部分请求,起到负载均衡+分而治之的作用
缺点:原来规划好的节点,进行扩容或缩容时就比较麻烦了,之前的公式hash(key) / 3 变成了 hash(key) / N ,此时地址经过取余运算的结果将发生很大变化。
-
一致性哈希算法分区
为解决服务器个数发生变化,尽量减少影响客户端到服务器的映射关系
-
算法构建一致性哈希环
-
一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能的哈希值会构成一个全量集,这个集合可以成为一个hash空间[0,2^32-1],把这个线性空间通过逻辑控制将它首位相连(0=2^32),这样让它逻辑上形成了一个环形空间
-
它也是按照使用取模的方法,对2^32取模。一致性hash算法将整个哈希值空间组成一个虚拟的圆环
-
-
redis服务器IP节点映射
-
将集群中各个ip节点映射到环上的某一个位置
-
将各个服务器使用hash进行一个哈希,具体可以选择服务器的ip或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置
-
-
key落到服务器的落键规则
-
当我们需要存储一个kv键值对时,首先计算key的hash值, hash(key),将这个key使用相同的hash函数计算出hash值并确定此数据在环上的位置,从此位置沿着环顺时针行走,第一台遇到的服务器就是其应该定位到的服务器,并将该键值对存储在该节点上
如下图,ObjectA应该存储到NodeA 服务器上,以此类推
-
一致性哈希算法的优点:具有容错性,如果C服务器宕机,那么ABD服务器不会收到影响。受影响的数据仅仅是C 和 B 之间的数据,并且这些数据会转移到D进行存储
还有就是扩展性,当需要增加一台服务器时,比如在A和B中间加一台X服务器,那么受影响的数据只是A和X之间的数据,重新把数据录到X即可
缺点:Hash环的数据倾斜问题,一致性Hash算法在服务节点太少时,容易因为节点分布不均匀导致数据倾斜,即被缓存的数据大部分集中在某一台服务器上
-
-
哈希槽分区
哈希槽分区为了解决一致性哈希算法的数据倾斜问题
哈希槽实质就是一个数组,[0,2^14-1] 也就是 [0,16383]形成hash slot 空间
为了解决均匀分配的问题,在数据和节点之间又加入了一层,把这层成为 哈希槽,用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里放的是数据
一个集群只能由16384个槽,这些槽会分配给集群中的所有主节点,集群会记录节点和槽的对应关系,解决了节点和槽的关系后,就需要对key求哈希值,然后对16384取模,余数是几就落入对应的槽中
HASH_SLOT = CRC16(key) mod 16384
为什么最大槽数是16384个?
上图是发送的心跳包信息
-
如果槽位为65536,发送心跳信息的消息头达到8k,发送的心跳包过于庞大
在消息头中最占空间的是myslots[CLUSTER_SLOTS/8] 当槽位是65536时,这块的大小是 65536/8/1024=8kb
如果槽位是16384,这块大小是 16384/8/1024=2kb
每秒钟redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping消息的消息头太大,浪费带宽,槽位是16384可以节约带宽
-
redis的集群主节点数量基本不可能超过1000
集群节点越多,心跳包的消息体内携带的数据越多,如果超过1000,会导致网络拥堵,因此节点不超过1000,16384个槽位就够用了
-
槽位越小,节点越少,压缩比高,容易传输
redis集群不保证强一致性,这意味着在特定条件下,redis集群可能会丢掉一些被系统收到的写入请求命令
三、三主三从集群搭建
在一台虚拟机上,分别有一个主机和一个从机,
他们的配置文件:
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 111111
masterauth 111111
cluster-enabled yes
cluster-config-file nodes-6381.conf
cluster-node-timeout 5000
比之前多了cluster的配置
将六台redis服务都启动
可以发现,和之前不同的是,启动的服务后面都多了 cluster,说明是集群
构建主从关系命令:
redis-cli -a 111111 --cluster create --cluster-replicas 1 xxip xxip xxip xxip xxip xxip
-
--cluster create 表示集群形式创建
-
--cluster-replicas 1 表示为每个master创建一个slave节点
执行成功后,可以看到相应的主从服务器
查看并检验集群状态:
首先连接上某一台redis服务器
-
info replication 查看该服务器的角色及主从关系
-
cluster nodes 查看集群中所有节点的关系,以及每个主节点对应的槽位
-
cluster info 查看当前节点的连接状态
四、三主三从集群读写
成功创建集群后,去写入key
我们发现在6381上去写k1,报错了,要求移动到6385上去执行该操作
报错的原因就是,我们需要注意槽位的范围区间,需要路由到位
也就是说k1的槽位是12706,该槽位属于6385,所以要到6385执行该操作
要解决这一问题需要在 连接redis时,加一个参数 -c
可以看到,加上-c,重新连接后,自动定位到了6385,再去6385查看也是有这个key的
查看key属于哪个槽位 cluster keyslot 键名
当主机下线后,从机会变成新的主机
6381的状态变成了 disconnected,它之前的从机6384变成了新的主机
当6381再次上线,它就变成了 6384的从机
如果想要变成之前的主从关系,可以执行cluster failover 进行从属调整
五、集群扩容
目前有三主三从,如果像再加一主一从,变成四主四从呢
-
首先创建两个redis服务器6387和6388,并启动,6387作为主机
-
将新增的6387节点,加入到集群
-
redis-cli -a 密码 --cluster add node ip地址:6387 原集群ip地址:6381
-
--cluster add node 表示节点加入到该集群
-
6387表示作为master要加入到集群的节点
-
6381是原集群中的节点
-
-
redis-cli -a 密码 --cluster check ip地址:6381,该命令查看集群的状态
-
端口号只要是集群中的任意一个即可
可以看到6387还没有被分配槽号slot,也还没有slave
-
-
-
槽号分配
-
redis-cli -a 密码 --cluster reshard ip地址:6381
提示:How many slots do you want to move ?
选择移动多少槽位到6387,这里可以选择4096,正好是将16384四等分
提示:What is the receiving node ID?
要把这些槽位移动到哪个节点,请输入节点id,这里输入新加入的6387节点的id即可
然后输入all ,开始重新分配,all表示从所有节点中拿出一部分来分配
-
现在可以看到6387有了槽位
6387的槽位并不是连续的,说明是从6381、6383、6385上分别移动了一部分
-
-
为6387分配从节点6388
redis-cli -a 密码 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新主机节点id
可以看到6388已经加入到集群并且成为6387的从机
六、集群缩容
-
清除从节点6388
-
redis-cli -a 密码 --cluster del-node ip:slave端口 slaver的id
-
清除后6387没有了从节点
-
-
清除6387的槽位,重新分配给6381
-
和之前分配槽位的命令相同 redis-cli -a 密码 --cluster reshard ip地址:6381
-
只是在选择槽位数量是6387的全部槽位,并且要传给的节点变成了6381节点
-
在最后不能输入all,要输入6387的节点id,表示从6387拿出槽位,最后输入done
这样就把6387的所有槽位都给了6381
-
-
清除完6387的槽位后,6387变成了6381的从节点
然后清除6387节点即可
redis-cli -a 密码 --cluster del-node ip:slave端口 slaver的id
6387和6388主从节点完全清除
七、其他
-
不在同一个slot槽位下的多键操作如mget mset等,会报错
解决方案:可以通过用{} 来定义同一个组的概念,使key中{} 内相同内容的键值对放到同一个slot槽位
也就是说{} 的内容相同即可表示在一个槽位
这里全部到了16287槽位
-
redis集群有16384个哈希槽,每个key通过crc16校验后对16384取模来决定放置到哪个槽
那么crc16的算法在哪里调用的?
在redis源码中的 cluster.c 中的keyHashSlot 函数中
-
常用命令
-
当一主一从全部down机后,redis是否还能继续对外提供服务
-
在redis配置文件中 cluster-require-full-coverage ,默认值是yes,即需要集群完整性,这种情况下一主一从down机,那么redis就不会对外提供服务了
-
-
cluster countkeysinslot
-
判断某个槽位是否被占用,被占用返回key的数量,没有占用返回0
-
-
cluster keyslot 键名
-
该键应该存在哪个槽位上
-
-