由于数据量过大,单个master-slave模式难以承担,当出现master节点故障的一瞬间,哨兵重新选举新的master节点之前,这一小段时间将会导致Redis服务不可用,因此需要对多个master-slave主从复制集进行集群,形成水平扩展,每个master-slave主从复制只负责存储整个数据集的一部分,这就是Redis的集群,其作用是提供在多个Redis节点之间共享数据的程序集。
Redis集群是一个提供在多个Redis节点间共享数据的程序集,Redis集群可以支持多个master。
(1)Redis集群支持多个master,每个master又可以挂载多个slave;
(2)由于Cluster自带Sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能;
(3)客户端与Redis的节点连接,不再需要连接集群中的所有节点,只需要任意连接集群中的一个可用节点即可;
(4)槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系;
Redis集群没有使用一致性hash,而是引入了哈希槽的概念。Redis集群中有16384个哈希槽,每个Key通过CRC16算法校验后对16384取模,进而决定要存储的数据放在哪个槽位中,集群中的每一个节点负责一部分hash槽,例如:当前集群有3个节点,那么:
使用Redis集群时,我们会将存储的数据分散到多台Redis机器上,这即被称为分片,换言之,集群中的每个Redis实例都被认为是整个数据集的一个分片。
为了找到给定key的分片,我们对key进行CRC16(key)算法处理,并通过对总分片数量(16384)取模,然后使用确定性哈希函数,这意味着给定的key将多次始终映射到同一个分片,我们据此可以推断将来读取key所在的分片。
方便扩容缩容和数据分片查找。这种结构很容易的添加或者删除节点,比如如果我想添加一个节点D,我需要从节点A、B、C上得到部分槽位给到D,如果我想移除节点A,那么需要先将A中的槽位移到B和C上,然后将没有任何槽位的A节点从集群中移除即可,由于从一个节点将哈希槽移动到另外一个节点的过程中并不会停止服务,所以无论添加节点还是删除节点或者改变某个节点的哈希槽数量都不会造成集群不可用的状态。
2亿条数据就是2亿个k:v,我们单机不行,必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:hash(key)%N(机器数量),计算出哈希值,用来决定将数据映射到哪一个节点上。
简单粗暴,直接有效。只需要预估好数据,规划好节点,例如:3台、8台、10台,就能保证一段时间的数据支撑。使用hash算法让固定的一部分请求落到同一台机器上,这样每台机器固定处理一部分请求(并维护这些请求信息),起到负载均衡+分而治之的作用。
原来规划好的节点,进行扩容或者缩容就比较麻烦了,不管扩容还是缩容,每次数据变动导致节点有变动,映射关系就需要重新进行计算,在服务器的数量固定不变时没有问题,如果需要弹性扩容或者故障停机的情况下,原来的取模公式就会发生变化:hash(key)/3 ===> hash(key)/?,此时地址经过取余运算的结果将会发生很大变化,根据公式获取的服务器也会变得不可控。某个Redis机器宕机了,由于机器的数量发生变化,会导致hash取余的全部数据重新洗牌。
一致性哈希算法是1997年由麻省理工学院提出,设计目的是为了解决分布式缓存数据变动和映射问题,当某个机器宕机了,分母数量改变了,自然余数不是预期结果了。
当服务器个数发生变化时,尽量减少影响客户端到服务器的映射关系。
容错性
扩展性
为了在节点数目发生改变时尽可能少的迁移数据,将所有的存储节点排列在首尾相接的Hash环上,每个key在计算Hash后会顺时针找到临近的存储节点进行存放。而当有节点加入或退出时仅影响该节点在Hash环上顺时针相邻的后续节点。
优点:加入和删除节点只影响哈希环中顺时针方向的相邻的节点,对其他节点无影响。
缺点:数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。
一致性哈希算法存在数据倾斜的问题。哈希槽实质上就是一个数组[0-2^14-1],形成hash slot空间。
解决均匀分配的问题,在数据和节点之间又增加了一层,被增加的这层称为哈希槽(slot),用于管理数据和节点之间的关系,现在就相当于节点上放的是槽,槽里边放的是数据。
槽解决的是粒度的问题,相当于把粒度变大了,这样便于数据的移动。哈希解决的是映射的问题,使用key的哈希值来计算所在槽,便于数据分配。
一个集群只能有16384个槽,区间为[0-2^14-1],这些槽会分配给集群中的所有主节点,分配策略没有要求。集群会记录节点和槽的对应关系,解决了节点和槽的关系后,接下来就需要对key求hash值,然后对16384取模,余数是多少,key就落到对应的槽里,HSAH_SLOT = CRC16(key)%16384。以槽位单位移动数据,因为槽的数目是固定的,处理起来比较容易,这样数据移动的问题就解决了。
Redis集群并没有使用一致性hash,而是引入了哈希槽的概念,Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放在哪个槽里面,集群的每个节点负责维护一部分hash槽,所有的节点维护的哈希槽共同组成一个完整的哈希槽。但是为什么哈希槽的数量是16384(2^14)个,而不是其他的值呢?CRC算法产生的哈希值有16bit,该算法可以产生2^16即65536个值,换句话说值是分布在0-65535之间,有更大的65536不用,为什么只用16384就够?作者在取模的时候,为什么不%65536,而选择%16384?HASH_SLOT = CRC16(key)%65536为什么没启用?
https://github.com/redis/redis/issues/2576
(1)如果槽位为65536,发送的心跳信息的消息头达8KB,发送的心跳包过于庞大
在消息头中最占用空间的是myslots[CLUSTER_SLOTS/8],当槽位为65536时,这块的大小为:65536/8/1024=8KB
当槽位为16384时,这块的大小为:16384/8/1024=2KB
由于Redis节点每秒钟需要发送一定数量的PING消息作为心跳包,如果槽位为65536,这个PING消息的消息头太大了,浪费带宽。
(2)Redis集群的主节点数量基本不可能超过1000个
集群节点越多,心跳包的消息体内携带的数据越多,如果节点超过1000个,将会导致网络拥堵。因此Redis作者不建议Redis Cluster节点的数量不超过1000个,那么对于节点数量在1000以内的Redis Cluster集群,16384个槽位足够用了,没有必要扩展到65536个。
(3)槽位越小,节点少的情况下,压缩比高,容易传输
Redis主节点的配置信息中,它负责的哈希槽是通过一张bitmap的形式来保存的,在传输过程中会对bitmap进行压缩,但是如果bitmap的填充率slots/N很高的话(N表示节点数),bitmap的压缩率就很低,如果节点数很少,而哈希槽的数量很多的话,bitmap的压缩率就很高。
Redis集群不保证强一致性。这意味着在特定条件下,Redis集群可能会丢弃掉一些被系统收到的写入命令。