由于数据量的增加,单个master不一定能承但全部的写任务;所以,可以对多个复制集进行集群,水平扩展每个复制集值存储数据集中的一部分,这就是Redis集群,可以在Redis节点间共享数据;
Redis集群是一个提供在多个Redis节点间空闲数据的程序集,Redis集群支持多个Master
- Redis集群支持多个Master,每个Master可以挂载多个Slave
- 由于Cluster自带哨兵的故障转移机制,无需再使用哨兵
- 客户端与Redis的节点连接不需要连接集群中的全部节点,只需要连接集群中任意节点即可
- 槽位Slot负责分配到各个物理节点,由对应的集群哦来负责维护节点、插槽和数据之间的关系
集群分片
槽位Slot
Redis集群没有使用一致性哈希,而是使用了哈希槽的概念;Redis哈希槽有16384个槽位,当存储时使用CRC16算法值和16384取模,得到0-16383之间的一个槽位值;再根据配置的Redis数量,将0-16383的位置按区间平均映射到集群中的Redis服务器,就确定了访问的是集群中的哪台master机器了。
Redis建议最多配置1000台Redis主服务器
分片
使用Redis集群就会将存储的数据分配到多台Redis机器上,这就是分片,每个Redis实例中的数据只是整个数据集的一个分片。
为了找到给定key对应的分片,只需要对key进行CRC16算法并且取模就可以得到对应分片的位置,因为这是一种确定性哈希算法,所以必定可以找到存放数据的Redis实例。
优势
- 可以方便进行扩容和缩容,添加和删除节点都只需要调整槽位即可
- 方便进行数据的查找
Slot槽位映射方法
- 哈希取余算法:将键值得到的哈希值直接对Redis服务器数量取余
- 简单有效,但是需要规划好数据和节点数量,保证数据访问一致
- 但是扩容缩容问题较大,如果服务器数量发生变化,就会导致映射数据访问位置变化,导致数据变化,很难维护
- 一致性哈希算法:设计目标是为了解决分布式数据的变动和映射问题,尽量固定哈希取模的分母,要尽量少影响客户端到服务器的映射关系
- 算法构架一致性哈希环:定义一个哈希空间,使其区间的头尾相连,使得其在逻辑上相连,即哈希算法的所有计算结果都落在一个区间内;
- 一致性哈希算法对2^32取模,使得哈希值一定落在0到2^32-1之间
- Redis服务器IP节点映射:将各个服务器的标识(如IP或主机名)通过哈希函数映射,落到0 - 2^32 -1 的哈希环上
- key到服务器的落下规则:对key进行哈希计算,得到哈希值后就可以确定在哈希环上的位置,之后每次从此顺时针(服务器哈希-key哈希为正)寻找服务器,寻找到第一台服务器(服务器哈希-key哈希最小)就是要访问的服务器
- 优点:
- 容错性:如果一台服务器宕机,则只是这台服务器到前一台服务器之间哈希值的数据不可访问,且数据写入被放入了下一台服务器;
- 扩展性:如果需要增加服务器,只需要将增加位置前一台服务器到增加服务器的数据从增加位置的下一台服务器转移即可
- 缺点:
- 数据倾斜:可能存在服务器聚集在一段空间内,使得聚集位置的顺时针第一台服务器负载较大,数据分布不均匀
- 哈希槽
一致性哈希算法存在数据倾斜问题,而哈希槽实际上就是一个数组,在 0 - 2^14 -1 个空间内存放数据,形成了哈希槽空间;
为了解决均匀分配问题,就引入了哈希槽,使得key的哈希值取模得到的槽位直接对应一个Redis服务器,这样就只需要将槽位给Redis服务器分配就可以了;如果出现单位数据移动,因为槽位固定,所以也容易处理,只需要把对应哈希槽位的数据进行迁移即可
为什么Redis集群的哈希槽最大是16384
Redis集群使用的哈希槽算法使用了CRC16算法,CRC16得到的哈希值最大是2^16位。但是:
- Redis的心跳包是需要包含这个哈希槽位数组的,如果是2^16大小的数组,就会使得数据大小从2kb变成8kb,而每秒Redis都要发送一些ping消息和心跳包,这样的话导致这个包的消息过大而导致浪费带宽
- Redis的集群主节点基本上不推荐1000个,因为可能导致网络拥堵;对于1000左右的Redis集群,16384个槽位也完全够用了
- 槽位越少,节点少的情况下,压缩比高;因为Redis主节点配置信息中哈希槽是一个bitmap,传输过程中是可以压缩的,压缩率是槽位数量除以节点数量,如果节点多或者槽位数量多,就会导致压缩率低
数据丢失
Redis集群不能保证强一致性,在特定的情况下,Redis集群可能丢失一些被系统收到的写入请求命令;
简单来说,如果集群中的master写入的数据没有来得及同步给它的slave就宕机了,那么slave会顶替master的位置,但是就没有这条未同步的数据,这样就写丢失了
集群搭建
集群环境配置
新建一些独立的Redis独立实例
1. Redis的集群配置文件存在一些不同,需要单独配置一些内容
- cluster-enabled 集群是否打开,配置 yes
- cluster-config-file 对应的集群配置文件 一般设置为 nodes- + 端口号 .conf
- cluster-node-timeout 集群超时时间
2. 配置完之前内容,服务器仍然不是集群,启动后可以使用 ps -ef | grep redis 查看redis实例是否集群启动
3. 使用redis-cli为Redis服务器构建集群
redis-cli -a password -p port --cluster create --cluster-replicas 1 ip1:port1 ip2:port2 ip3:port3 ...
cluster-replicas x 表示每个master配置对应的x个slave节点,这个配置会自动进行,实际的配置需要通过后续命令查看。
输入之后,确定创建集群,之后就会开始发送消息,并等待其他节点加入集群;等待其他节点加入集群后,对应的集群配置文件会按照之前配置的文件名称生成;
可以使用info replication来显示主从信息,使用cluster node可以查看集群的基本信息,cluster info可以查看自己在集群中的信息;
集群读写
配置集群后,如果set的key的hash值计算后不属于自己的槽位范围,这时连接到的服务器会返回error(Move to)表示需要移动到对应槽位范围的服务器才能存储该键;此时非常不方便;
此时,为了避免路由失效,在连接入一个集群内的master写入时,需要加上 -c参数,表示路由到对应的服务器范围,不需要手动转移到对应槽位的服务器;此时再写入数据,就会返回重定向到槽位范围的Redis服务器,实际上就存入了真正的对应的服务器;
redis-cli -a password -p 6379 -c
注意,如果在集群中任意一个服务器上执行操作,都会被定向到可以执行该命令的服务器,如在slave上使用set命令也会被重定向到master上;
也可以使用 CLUSTER KEYSLOT key 来查看该key对应的槽位;
容错切换
- 如果集群中的一个master宕机了,其对应的slave将会转变成为master,可以正常写入和读取
- 如果宕机的master再次回到集群,就会重新被配置成新master的slave了
- 集群是不能保证数据完全的一致的,因为可能在容错切换的时候丢失一定的写入请求
- 如果希望宕机的master上线后重新变为回master,则需要进行节点从属调整,则需要在宕机节点恢复后,手动执行CLUSTER FAILOVER,使得当前执行命令的节点再次变成master
集群扩容
当需要新加入一些节点进入集群时
1. 新建一些Redis实例,将其配置为集群的设置启动;此时新节点还未加入集群
2. 将一个新节点加入集群作为master节点,需要找到一个已经在集群中的节点,使用客户端登录并携带 add-node 参数,包括自己的ip和端口以及在集群中的一个节点的ip和端口;执行完毕后,新的master暂时不会被分配槽位;
redis-cli -a password --cluster add-node myip:port clusternodeip:port
3. 重新分配哈希槽的区段,使用客户端启动
redis-cli -a password --cluster reshard clusternodeip:port
之后将会提示输入分配的多少号,一般设置为16384除以主机数量;输入完成后,还需要输入接收槽位的新主机id,使用info replication可以看到;之后输入all和yes完成默认设置即可完成槽位分配;但是这样分配的槽位不是连续的,而是从之前集群中的主节点中分出的;
4. 将新主节点的从节点加入集群,需要输入指令
redis-cli -a password --cluster add-node myip:port newmasterip:port --cluster-slave --cluster-master-id newmasterid
集群缩容
如果需要删除一些主节点和其从节点
1. 先清除从节点,使用命令
redis-cli -a password --cluster del-node slaveip:port
2. 清空主节点的槽号,并归还给集群中其他主节点
redis-cli -a password --cluster reshard masterip:port
根据提示,清除该主节点自己的全部槽号,并且输入接收槽号的节点;再选择由哪个节点通知接收槽号的节点,设置为需要删除的主节点即可;
3. 此时,如果把主节点的全部槽位交给其余主节点后,自己将会变成一个从节点,这时就可以删除了
redis-cli -a password --cluster del-node masterip:port masterid
操作之后,需要删除的主从机器都被删除出集群
其他
- 不再同一个槽位之下,多键操作是无法使用的;必须要使用 {} 定义一个组的概念,才能使得key中{}内相同内容的键值放到一个slot槽位中;
所以,在使用多键操作时,需要指定一个组作为映射槽位的基准,使得同一个组的键映射到同一个槽位,如下就可以正常取数据
mset k1{k} v1 k2{k} v2 k3{k} v3 mget k1{z} k2{z} k3{z}
- CRC16的使用,在Redis源码中的cluster.c中的keyHashSlot函数计算的,其中会先检测是否有大括号形成的组,然后对键值进行CRC16计算
- 其他命令
- cluster-require-full-coverage 参数设置的是是否只要Redis集群完整时才向外提供服务(即如果至少一个主机和其全部从机都宕机时,任务集群已经不完整,无法提供完整的数据访问了),默认值为yes(不完整就不提供服务)
- CLUSTER COUNTKEYSINSLOT slotnum 可以查看某一个槽位是否存在至少一个键值
- CLUSTER KEYSLOT key 查看某个键在哪个槽位上