文章目录
一. 定义
由于数据量过大,当个master复制集难以承担,因此需要多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Redis集群,其作用时在多个Redis节点间共享数据的程序集。
- Redis集群支持多个Master,每个Master又可以挂载多个slave(读写分离、支持数据的高可用、支持海量数据的读写存储操作)
- 由于集群自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能
- 客户端于Redis的节点连接,不再需要连接集群中所有节点只需要任意连接集群中的一个可用节点即可
- 槽位slot负责分配各个物理服务节点,由对应的就去那来负责维护节点、插槽和数据之间的关系。
二. 集群算法-分片-槽位slot
1. Redis集群的数据分片
Redis集群没有使用一致性Hash,而是引入了哈希槽的概念。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置在哪个槽,集群的每一个节点负责一部分hash槽。如下:
2. Redis集群的分片
使用Redis集群时我们会将存储的数据分散到多台Redis机器上,这称为分片,简言之,集群中的每个Redis实例都被认为是整个数据的一个分片。为了找到给定key的分片,我们对key进行CRC16(key)算法处理并通过对总分片数量取模,然后使用正确性哈希函数,这意味着给定的key将多次始终映射到同一个分片,我们可以推断将来读取特定key的位置。
Redis集群分片的最大优势是方便扩缩容和数据分派查找。这种结构很容易添加或者删除节点,比如如果我想添加个节点D,我需要从节点A,B,C中得部分槽到D上,如果我想移除及诶单A,需要将A中槽移动到B和C节点上,然后将没有任何槽的A节点从集群中移除即可,由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加删除或者改变某个节点的哈希槽的数量都不会造成集群不可用状态。
3. slot槽位映射方法
哈希取余分区
2亿条记录就是2亿个k,v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式,hash(key)%N个机器台数,计算出hash值,用来决定数据映射在哪一个节点上
该方法简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台、8台和10台,就能保证一段时间内的数据支撑。使用Hash算法让固定的一部分请求落在同一台服务器上,这样每台服务器固定处理一部分请求(并维护这些请求信息),起到负载均衡+分而治之的作用。但是原来规划好的节点,进行扩容或者缩容就比较麻烦了,不管扩缩,每次数据变动导致节点有变化,映射关系需要重新进行计算,在服务器个数固定不变的时候没有问题,如果需要弹性扩容或故障停机的情况下,原来的取模公式就会发生变化。此时某个redis机器宕机了,由于台数数量变化,会导致hash取余全部数据重新洗牌。
一致性hash算法
一致性hash算法在1997年由麻省理工学院提出的,设计目标是为了解决分布式缓存数据发生变动和映射问题,某个机器宕机了,分母数量发生了改变,自然取余数不OK了。该算法的主要步骤分为三步:
- 算法构建一致性哈希环
一致性hash算法必然有个hash函数并安装算法产生hash值,这个算法的所有可能会构成一个全量集,这个集合可以成为一个hash空间 [ 0 , 2 32 − 1 ] [0,2^{32-1}] [0,232−1],这个世一个线性空间,我们通过适当的逻辑控制将它首尾相连 ( 0 = 2 32 ) (0=2^{32}) (0=232),这样就让它逻辑上形成一个环形空间。
hash值只会落在这个环上面,这个环就是hash环。
- 服务器IP节点映射
将集群中的各个ip节点映射到环上的某一个位置,将各个服务器使用Hash进行一个哈希,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在hash环上的位置,假如4个节点A,B,C,D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址hash后在环空间的位置如下:
- key落在服务器的落键规则
当我们需要存储一个kv时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出hash值并确定在环上的位置,此位置需要沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值存储在该节点上。
该算法具有容错性。假设Node C宕机了,可以看到此时A,B,D不会受到影响,一般的在一致性Hash算法中,如果一台服务器不可用了,则受影响的仅仅是此服务器到其环空间前的一台服务器之间的数据,其它不会受到影响。
该算法具有扩展性。数据量增加了,需要增加一台NodeX,X的位置在A和B之间,那受到影响的是A到X之间的数据,重新把A到X的数据录入到X上即可,不会导致hash取余全部数据重新洗牌。
该算法的缺点是存在Hash环的数据倾斜问题。一致性hash算法在服务节点太少时,容易业务节点分布不均匀导致数据倾斜(被缓冲的对象大部分集中缓存在一台服务器上)问题。
hash槽算法
该算法的出现是为了解决一致性hash算法的倾斜问题。hash槽本质上是一个数组,数组
[
0
,
2
14
−
1
]
[0,2^{14}-1]
[0,214−1]形成的hash slot空间。它在数据和节点之间加入了一层,这层就称为槽,用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽中放的是数据。
槽解决的事粒度问题,相当于把粒度变大,这样便于数据移动,hash解决的是映射问题,使用key的hash值来计算所在的槽,便于数据分配。
一个集群最多只能有16384个槽,这些槽会分配给集群中的所有主节点,分配策略没有要求。集群会记录和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求hash值,然后对16384取模,余数就是key落入的对应的槽。 H a s h _ S l o t = C R C 16 ( k e y ) m o d 16384 Hash\_Slot=CRC16(key) mod 16384 Hash_Slot=CRC16(key)mod16384。以槽的单位移动数据,因为槽的数量是固定的,处理起来容易,这样数据移动问题就解决了。
为什么redis集群的最大槽数为16384个?
答:CRC16算法产生的hash值有16bit,可以产生65536个值,为什么只用16384就够了(2的14次方)?这是因为如果槽位为65536,发送心跳信息的消息头达8K,发生的心跳包过于庞大。在消息头中最占空间的是myslots[CLUSTER_SLOTS/8],当槽位为65536时,这块的大小为65536/8/1024=8kb,当槽位为16384时,这块的大小为2kb,因为每秒钟,redis节点需要发送一定数量的ping消息作为心跳包,如果槽位为65536,这个ping的消息头就太大了,浪费带宽。其次,集群节点越多,心跳包的消息体携带的数据越多,如果节点超过1000个,也会导致网络赌赛,因此redis作者不建议redis cluster节点数量超过1000个,那么,对于节点数在1000以内的redis cluster,16384个槽位够用了,没有必要扩展到65536个槽。最后,槽位越小,节点少的情况下,压缩比高,容易传输。Redis主节点的配置信息中它负责的哈希槽是通过一张bitmap形式来保存的,在传输过程中会对bitmap进行压缩,如果bitmap的填充率slots/N很高的话(N表示节点数),bitmap的压缩率就比较低,如果节点数很少,而hash槽数量多的话,bitmap的压缩率就很高。
Redis集群不保证强一致性,这意味着在特定的条件下,Redis集群肯会丢掉一些被系统受到的写入请求命令。
三. 集群环境搭建
1. 3主3从redis集群配置
- 找3台真实虚拟机,各自新建
mkdir -p /myredis/cluster
- 创建每个redis服务的配置文件
vim /myredis/cluster/redisCluster6381.conf
vim /myredis/cluster/redisCluster6382.conf
# 6381
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
appendiflename "appendonly6381.aof"
requirepass 111111
masterauth 111111
#开启集群
cluster-enabled yes
cluster-config-file nodes-6381.conf
cluster-mode-timeout 5000
# 6382
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
appendiflename "appendonly6382.aof"
requirepass 111111
masterauth 111111
#开启集群
cluster-enabled yes
cluster-config-file nodes-6382.conf
cluster-mode-timeout 5000
- 启动6个redis实例
redis-server /myredis/cluster/redisCluster6381.conf
redis-server /myredis/cluster/redisCluster6382.conf
- 使用redis-cli命令为6台机器构建集群关系
redis-cli -a 111111 --cluster create --cluster-replicas 1 192.168.111.185:6381 192.168.111.185:6382 192.168.111.172:6383 192.168.111.172:6384 192.168.111.184:6385 192.168.111.184:6386
# -a 111111 密码
#--cluster create以集群形式创建
#--cluster-replicas 1 表示为每个master创建一个slave节点
CLUSTER NODES
# 查看集群关系
致此3主3从的集群就搭建完了。
2. 3主3从redis集群读写
首先我在6381上插入数据:
发现k1插入失败,k2插入成功了
然后我看看6385机器:
发现k1插入成功,k2插入失败了
出现上面问题的原因是,我们在配置集群时没有注意到槽位的范围,需要路由到位。即k1的槽位不属于6381而是属于6385。为了解决上面问题我们在启动redis-cli时加一个-c
redis-cli -a 111111 -p 6389 -c
然后我们再插入数据:
CLUSTER KEYSLOT k1
#查看k1属于哪一个槽位
3. redis集群分片之主从容错切换
SHUTDOWN
#在6381的redis-cli执行该命令
可以发现6384从slave变成master了
重启6381,查看6381是否可以重新上位
可以发现6381不会上位,并成为6384的从节点
现在有新的需求,我还是想要6381作为master(节点从属调整)
CLUSTER FAILOVER
#在6381机器上执行
4. redis集群分片之集群扩容
两个关键问题,首先新的机器是否会被原集群接纳?槽位是否需要重新洗牌?
- 新建两个服务实例配置文件并启动新的两个redis实例(此时两个redis实例都是自己的master)
配置文件内容和启动流程和上面一样
- 将新增的6387作为master节点加入原有集群
redis-cli -a 111111 --cluster add-node [ip地址]:6387 [ip地址]:6381
6387就是要加入的作为新的master,6381就是原来集群节点的领路人
- 检查当前集群的情况
redis-cli -a 111111 --cluster check [id地址]:6381
- 重新分配槽号
redis-cli -a 111111 --cluster reshard [ip地址]:6381
这里推荐平均分配
输入6387的id
槽位是各个机器匀过来的,至此槽位分配完毕
- 为主节点6387分配从节点6388
redis-cli -a 111111 --cluster add-node ip:新slave端口 ip:新master端口 --cluster-slave --cluster-master-id 新master节点ID
5. redis集群分片之集群缩容
- 从集群中将6388删除
redis-cli -a 111111 --cluster del-node ip:6388 6388的节点ID
- 检查集群情况
子节点6388成功删除
- 6387的槽号请空,重新分配,本例将清出来的槽好都给6381
redis-cli -a 111111 --cluster reshard [ip地址]:6381
- 再次查看集群情况
发现6387成为了6381的slave,现在可以直接使用 r e d i s − c l i − a 111111 − − c l u s t e r d e l − n o d e i p : 63876387 的节点 I D redis-cli -a 111111 --cluster del-node ip:6387 6387的节点ID redis−cli−a111111−−clusterdel−nodeip:63876387的节点ID删除6387了