1. 是什么
由于数据量过大,单个Master复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展每个复制集只负责存储整个数据集的一部分,这就是Redis集群,其作用是提供在多个Redis节点间共享数据的程序集。
Redis集群是一个 提供在多个Redis节点间共享数据的程序集。可以支持多个Master。
如果set k1 v1 到M1,那么 M2 和 M3 都会存在v1,即S1 S2 S3 也会存在v1。
2. 能干什么
- Redis集群支持多个Master,每个master又可以挂载多个slave。可实现读写分离、支持数据的高可用、支持海量数据的读写存储操作。
- 由于cluster自带了sentinel的故障转移机制,内置了高可用的支持,无需再去使用哨兵功能
- 客户端与Redis的节点连接,不再需要连接集群中的所有节点,只需要任意连接集群中的一个可用节点即可
- 槽位slot负责分配到各个物理服务节点,由对应的集群来负责维护节点、插槽和数据之间的关系。
3. 集群算法-分片-槽位slot
3.1 槽位
Redis cluster specification | Redis
Key distribution model
The cluster's key space is split into 16384 slots, effectively setting an upper limit for the cluster size of 16384 master nodes (however, the suggested max size of nodes is on the order of ~ 1000 nodes).
Each master node in a cluster handles a subset of the 16384 hash slots. The cluster is stable when there is no cluster reconfiguration in progress (i.e. where hash slots are being moved from one node to another). When the cluster is stable, a single hash slot will be served by a single node (however the serving node can have one or more replicas that will replace it in the case of net splits or failures, and that can be used in order to scale read operations where reading stale data is acceptable).
The base algorithm used to map keys to hash slots is the following (read the next paragraph for the hash tag exception to this rule):
HASH_SLOT = CRC16(key) mod 16384
Redis集群没有使用一致性hash,而是引入了哈希槽的概念。Redis集群由16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放到哪个槽。集群的每个节点负责一部分hash槽。
例:如果Redis存入一个数x,会先用这个x通过CRC16计算出一个槽位,然后根据这个槽位所在的集群插入到对应的位置。比如CRC16计算的结果是100,那么就会存储到0-5460这个redis中的第100号槽位。查询也是一样。
3.2 分片
使用Redis集群时我们会将存储的数据分散到多台redis机器上,简言之,集群中的每个Redis实例都被认为是整个数据的一个分片。如上图中3个redis就是3个分片。
如何找到给定key的分片:我们对key进行CRC16(key)算法处理并通过对总分片数据取模。然后,使用确定哈希函数,这意味着给定key将多次始终映射到同一个分片,我们可以推断将来读取特定key的位置。
3.3 使用分片和槽位的优势
方便扩容/缩容和数据分派查找。这种结构很容易添加或者删除节点。比如如果我想新添加个节点D,我需要节点A B C中得部分槽位移到D上。如果我想移除节点A,需要将A中的槽位移到B和C节点上,然后将没有任何槽位的A节点从集群中移除即可。由于从一个节点将哈希槽移动到另一个节点并不会停止服务,所以无论添加/删除或者改变某个节点的哈希槽的数量都不会造成集群不可用的状态。
3.4 槽位映射
3.4.1 哈希取余分区
2亿条记录就是2亿个k v,我们单机不行必须要分布式多机,假设有3台机器构成一个集群,用户每次读写操作都是根据公式:hash(key)%N个机器台数,计算出哈希值,用来决定数据映射到哪一个节点上。
优点:简单粗暴,直接有效,只需要预估好数据规划好节点,例如3台机器,就能保证一段时间的数据支撑,使用Hash算法让固定的一部分请求落到同一台机器上,这样每台服务器固定处理一部分请求(并维护这些请求的信息),起到负载均衡+分而治之的作用。
缺点:原来规划好的节点,进行扩容或者缩容就比较麻烦,每次数据变动导致节点有变动,映射关系需要重新进行计算,在服务器个数固定不变时没有问题,如果需要弹性扩容或者故障宕机的情况下,原来取模公式就会发生变化:Hash(key)/3 会变成Hash(key)/?,此时地址经过取余运算的结果将发生很大变化,根据公式获取的服务器也会变的不可控。某个redis机器宕机了,由于台数量变化,会导致hash取余全部数据重新洗牌。
3.4.2 一致性哈希算法分区
是什么:它是在1997年由麻省理工学院中提出的,设计的目标是为了解决分布式缓存数据变动和映射问题,某个机器宕机了,分母数改变了,自然取余就不ok了。
能干什么:目的是当服务器个数发生变动时,尽量减少影响客户端到服务器的映射关系。
3大步骤:
- 算法结构一致性哈希环:
一致性哈希算法必然有个hash函数并按照算法产生hash值,这个算法的所有可能哈希值会构成一个全量集,这个集合可以成为一个hash空间【0,2^32-1】,这个是一个线性空间,单是在算法中,我们通过适当的逻辑控制将它首位相连(0=2^32),这样让它逻辑上形成了一个环形空间。
它也是按照使用取模的方法,前面笔记介绍的节点取模法是对节点(服务器)的数量进行取模,而一致性Hash算法是对2^32取模,简单来说,一致性Hash算法将整个哈希值空间组成一个虚拟圆环,如假设某哈希函数H的值空间为0-2^32-1(即哈希值是一个32位无符号整形),整个哈希环如下图,整个空间按顺时针方向组织,圆环正上方的点代表0,0点右侧第一个点代表1,依次类推,2、3、4.......直到2^32-1,也就是说0点左侧的第一个代表2^32-1,0和2^32-1在零点钟方向重合,我们把这个由2^32个点组成的圆环称为Hash环。
- 服务器IP节点映射:
节点映射:将集群中各个IP节点映射到环上的某一个位置。
将各个服务器使用Hash进行一个哈希映射,具体可以选择服务器的IP或主机名作为关键字进行哈希,这样每台机器就能确定其在哈希环上的位置。加入4个节点 A B C D,经过IP地址的哈希函数计算(hash(ip)),使用IP地址哈希后在环空间的位置如下:
- key落到服务器的落键规则:
当我们需要存储一个kv键值对时,首先计算key的hash值,hash(key),将这个key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器,并将该键值存储在该节点上。
如果我们有Object A、Object B、Object C、Object D四个数据对象,经过哈希计算后,在环空间的位置如下,根据一致性Hash算法,数据A会被定位到Node A 上,B 被定位到Node B上,C 被定位到Node C上,D被定位扫Node D上。
优点:
- 容错性:假设Node C 宕机,可以看到此时对象A B C D 不会受到影响。一般的,在一致性Hash算法中,如果一台服务器不可用,则受影响的数据仅仅是此服务器到其环空间中前一台服务器(沿着逆时针走遇到的第一台服务器)之间的数据,其它不会受到影响,简单说,就是C挂了,收到影响的只是B、C之间的数据,且这些数据会转移到D进行存储。
- 扩展性:数据量增加了,需要增加一台NodeX。X的位置在A和B之间,那受到影响的就是A到X之间的数据,重新把A到X的数据录入到X上即可。不会导致hash取余全部数据重新洗牌。
缺点:
一致性哈希算法的数据倾斜问题:
一致性Hash算法在服务节点太少时,容易因为节点分布不均匀而造成数据倾斜(被缓存的对象大部分集中缓存在某一台服务器上)问题。例如系统中只有2台服务器。
总结:
将所有的存储节点排列在首尾相接的Hash环上,每个key在计算Hash后会顺时针找到临近的存储节点存放。而当有节点加入或退出时仅影响该节点在Hash环上逆时针相邻的后续节点。
优点:
加入和删除节点只影响哈希环中顺时针方向的相邻节点,对其他节点无影响。
缺点:
数据的分布和节点的位置有关,因为这些节点不是均匀的分布在哈希环上的,所以数据在进行存储时达不到均匀分布的效果。
3.4.3 哈希槽分区
为什么会出现:一致性哈希算法的数据倾斜问题。
哈希槽实质就是一个数组,数组【0,2^14-1】形成的hash slot空间
能干什么:能解决均匀分配的问题,在数据和节点之间又加入了一层,把这层称为哈希槽(slot),用于管理数据和节点之间的关系。现在就相当于节点上放的是槽,槽里放的是数据。
哈希槽计算:Redis 集群中内置了16384个哈希槽,redis会根据节点数量大致均等的将哈希槽映射到不同的节点。当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法,算出一个结果,然后用结果对16384求余数【CRC16(key)%16384】,这样每个key都会对应一个编号在0-16384之间的哈希槽,也就是映射到某个节点上。A和B在节点2上,C在节点3上。
为什么redis集群的最大槽数是16384个?
Redis集群中没有使用一致性hash而是引入了哈希槽的概念。Redis集群中有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽,集群的每个节点负责一部分hash槽,但为什么哈希槽的数量是16384(2^14)个呢?
CRC16算法的hash值有16bit,该算法可以产生2^16=65536个值,有更大的65536不用为什么只用16384就够?
why redis-cluster use 16384 slots? · Issue #2576 · redis/redis · GitHub
(1)如果槽位为65535,发送心跳信息的消息头达8k,发动的心跳包过于庞大。
在消息头中最占空间的是myslts[CLUSTER_SLOTS/8]。当槽位为65536时,块的大小是:65536/8/1024=8kb
在消息头中最占空间的是myslts[CLUSTER_SLOTS/8]。当槽位为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集群可能会丢掉一些被系统收到的写入请求的命令。
4. Redis集群环境搭建
4.1 3主3从redis集群配置
- 分别在6台机器上执行
mkdir -p /myredis/cluster
- 新建6个独立的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 Beijing@123 masterauth Beijing@123 cluster-enabled yes cluster-config-file nodes-6381.conf cluster-node-timeout 5000
1:192.168.217.135:6381
vim /myredis/cluster/redisCluster6381.conf
2:192.168.217.134:6382
vim /myredis/cluster/redisCluster6382.conf
3:192.168.217.140:6383
vim /myredis/cluster/redisCluster6383.conf
4:192.168.217.136:6384
vim /myredis/cluster/redisCluster6384.conf
5:192.168.217.138:6385
vim /myredis/cluster/redisCluster6385.conf
6:192.168.217.137:6386
vim /myredis/cluster/redisCluster6386.conf
分别启动6台机器:redis-server /myredis/cluster/redisCluster6381.conf ps -ef|grep redis
- 通过redis-cli命令为6台机器构建集群关系
redis-cli -a Beijing@123 --cluster create --cluster-replicas 1 192.168.217.135:6381 192.168.217.134:6382 192.168.217.140:6383 192.168.217.136:6384 192.168.217.138:6385 192.168.217.137:6386
--cluster-replicas 1 : 每个master 分配一个slave
- 查看并检验集群状态
info replication
cluster node
实际主从关系:
192.168.217.135:6381 Master -> 192.168.217.138:6385 Slave
192.168.217.134:6382 Master -> 192.168.217.137:6386 Slave
192.168.217.140:6383 Master -> 192.168.217.136:6384 Slave
4.2 3主3从redis集群读写
由于槽位的限制,redis集群不能像单机那样存储。需要一个路由到位
解决办法,在链接客户端时 加参数 -c
查看key的槽位命令
CLUSTER KEYSLOT Key
4.3 主从容错切换迁移案例
当前集群主从关系:
Master 6382 -> Slave 6386
Master 6385 -> Slave 6381
Master 6383 -> Slave 6384
- 手动停止6382(master),观察集群状态
- 手动重启6382,观察集群状态
- 节点从属调整(把6382和6386的主从关系对调)
cluster failover
4.4 主从扩容
1. 虚拟机不够,下面新加入的2台机器都放到6381机器上。
vim /myredis/cluster/redisCluster6387.conf
vim /myredis/cluster/redisCluster6388.conf
2. 启动2台新加入的机器
redis-server /myredis/cluster/redisCluster6387.conf
redis-server /myredis/cluster/redisCluster6388.conf
3.将新加入的节点加入集群
redis-cli -a Beijing@123 --cluster add-node 192.168.217.146:6387 192.168.217.146:6381
6387就是将要做为master加入的新节点,6381就是原来集群节点里面的节点。相当于6387通过6381找到组织加入集群。
4. 第一次检查集群情况
redis-cli -a Beijing@123 --cluster check 192.168.217.146:6381
5.槽位的重新分配
redis-cli reshard 192.168.217.146:6381
6. 在次检查集群情况
redis-cli reshard 192.168.217.146:6381
重新分配的成本太高,所以由之前的每个master平均分配给新的master 6387。
7. 为新主节点6387 分配从节点6388
redis-cli -a Beijing@123 --cluster add-node 192.168.217.146:6388 192.168.217.146:6387 --cluster-slave --cluster-master-id xxxxxxxxx(6387的编号)
![](https://i-blog.csdnimg.cn/blog_migrate/a2028661a60a3833cebb22e99714070d.png)
4.5 主从缩容(6387 和 6388 下线)
1.检查集群状态
2.6388(Slave)从集群中移除
redis-cli -a Beijing@123 --cluster del-node 192.168.217.146:6388 xxxx(6388的节点ID)
3.清空6387(Master)的槽位,重新分配(本例把4096个槽位都给6385)
redis-cli -a Beijing@123 --cluster reshard 192.168.217.146:6381
4.再次检查集群状态
6385的槽位数也从4096变成了8192.。如果要把6387的槽位数平均给所有master要输入多次。
5.移除6387
redis-cli -a Beijing@123 --cluster del-node 192.168.217.146:6387 xxxx(6387的节点ID)
7. 第三次检查集群状态(6387/6388彻底被删除)
不在同一个slot槽位下的键值无法使用mset mget等多建操作。但是通过占位符可以使用mset和mget
CRC16算法在底层c语言源码来支撑的。函数名是 keyHashSlot(char *key,int keylen)
集群是否完整才能对外提供服务:当3主3从由于不可控原因变成2主2从,redis集群是否任然对外提供服务。默认是yes(不会对外提供服务)。
配置文件内容:cluster-require-full-coverage
cluster countkeysinslot xxx(槽位号)
cluster keyslot key