集群:
广义的集群:只要你有多个机器,构成了分布式系统,都可以称为一个"集群"(前面主从结构,哨兵模式,也可以称为"广义的集群")
狭义的集群:Redis提供的集群,这个集群模式下,主要是要解决,存储空间不足的问题(拓展存储空间)
注:哨兵模式提高了系统的可用性,哨兵模式中,本质上还是Redis主从节点存储数据,其中要求一个主节点/从节点,就得存储整个数据的"全集"
此处的关键问题就是引入多台机器,每台机器上存储一部分数据:
设有1TB数据需要存储
拿两台机器来存储,每台机器上只需要存512G即可;拿三台机器来存,每台机器上只需要300多G即可;拿四台机器来存,每台机器上只需要256GB...随着机器个数的增加,每台机器的数据量就减少了
把数据分成多份的方法(三种主流的分片方法):
一.哈希求余:借鉴了哈希表的基本思想,借助hash函数,把一个key映射到整数,再针对数组的长度求余,就可以得到一个数组下标
比如:有三个分片,编号0,1,2
此时就可以针对要插入数据的key(Redis都是键值对结构的数据)计算hash值(比如,使用MD5)再把这个hash值余上分片个数,就得到一个下标,此时就可以把这个数据放到该下标对应分片中了
hash(key%N=>0)此时这个key就要存储在0号分片中(后续查询key的时候,也是用同样的算法,key是一样的,hash函数是一样的,得到的分片就是一样的)
注:MD5本省是一个计算hash值的算法,针对一个字符串,里面的内容进行一系列的数学变换=>整数
变成一个十六进制的数
MD5是一个非常广泛使用的算法:
特点:
1.MD5计算结果是定长的(无论输入的原字符串多长,最终算出的结果就是固定长度)
2.MD5的结算结果是分散的[哈希函数]:两个原字符串,哪怕大部分相同.只有一个小地方不一样,算出来的MD5值也会影响很大
3.MD5计算结构是不可逆的[加密]:给你原字符串,可以很容易算出MD5 的值,给你MD5的值很难还原出原始的字符串(理论上是不可行的)
问题:一旦服务器集群需要扩容,就需要更高的成本了,分片主要目的就是为了提高存储能力,分片越多,能存的数据越多,成本也更高
一般都是先少搞几个分片(3个),但是随着业务逐渐增长,数据变多了,3个分片已经不足以保存了,就需要"扩容"
引入新的分片,N就能改变了,hash(key)%N=>0(当hash函数和key都不变的情况下,如果N改变了,整体的分片结果仍然会变)
如果发现某个数据,在扩容之后不应该待在当前的分片中了,就直接重新分配(搬运数据)
一共有20个数据,只有3个数据不需要搬运,搬运了17次
如果是20亿个数据,17亿数据就需要搬运,就是一个大活了
上述级别的扩容,开销级别极大的,往往不能直接在生产环境上操作,只能通过"替换"的方式实现扩容,依赖的机器更多了,成本更高,操作步骤非常复杂
二.一次性哈希算法:在hash求余这种昂操作中,当key属于哪个分片时,是交替的;在一致性哈希这样的设定下,把交替出现,改进成连续出现
扩容后:
只需要把0好分片上的这一段数据给搬运到3号分片上即可,2 3号分片都是不变的,这种搬运的成本,也是有的,比刚才哈希求余的方式低了不少(虽然搬运成本低了,但是这几个个分片上的数据量,就可能不均匀了(数据倾斜))
如果是一次扩容高多个分片,确实是一个好的思路,可以避免当前的数据倾斜,总的搬运数据量是比求余的少,但是还是需要更多的机器
三.哈希槽分区算法(Redis真正采用的分片算法(这种算法本质上就是把一致性哈希和哈希求余这两种方式结合一下))
hash_slot=crc16(key)%16384
hash_slot:哈希槽
crc16(key):也是一种计算hash的算法
16384=>16*1024=>2^14(16K)
会进一步把上述的这些哈希槽,分配到不同的分片上
比如:
假设当前有三个分⽚,⼀种可能的分配⽅式:
• 0号分⽚:[0,5461],共5462个槽位
• 1号分⽚:[5462,10923],共5462个槽位
• 2号分⽚:[10924,16383],共5460个槽位
虽然不是严格意义的"均分"差异非常小,此时这三个分片上的数据就是比较均匀的了
这里只是一种可能得分片方式,实际上分片时非常灵活的;每个分片持有的槽位号,可以是连续的,也可以是不连续的
此处,每个分片都会用"位图"这样的数据结构表示当前有多少槽位号(16384个比特位,用每一位0/1来区分自己这个分片当前是否持有该槽位号)
扩容后:
• 0号分⽚:[0,4095],共4096个槽位
• 1号分⽚:[5462,9557],共4096个槽位
• 2号分⽚:[10924,15019],共4096个槽位
• 3号分⽚:[4096,5461]+[9558,10923]+[15019,16383],共4096个槽位
上述过程中,只有被移动的槽位,对应的数据才需要搬运
针对某个分片,上面的槽位号,不一定非得是连续的区间
注:
1.Redis集群是最多有16384个分⽚吗?
每个分片上就只有一个槽位,此时难以保证数据在各个分片上的均衡性(key是先映射到槽位,再映射到分片的;如果每个分片包含的槽位比较多,如果槽位个数想当,就可以认为是包含的key数量想当;如果每个分片包含的槽位非常少,槽位个数不一定能直观的反应到key的数目)
实际上Redis的作者建议集群分片数不应该超过1000(如果真是1.6w个分片,整个数据服务器的集群规模就太可怕了,几万台主机构成的集群,整个集群的可用性是非常堪忧的)
2.为什么是16384个槽位?
心跳包中包含了该节点持有那些slots(需要表示出该节点有那些槽位)
2KB基本上够用了,同时占用的硬件资源(网络带宽有不是很大
搭建Redis集群:
前提:停掉之前启动的Redis容器
在Linux上,一.sh后缀结尾的文件,称为"shell脚本"(在使用Linux的时候,都是通过一些命令来进行操作的,使用命令操作,就非常合适把命令写到一个文件中,批量化执行;同时,还能加入条件,循环,函数等机制)
本次,需要创建11个Redis节点,这些Redis的配置文件内容,大同小异,此时,就可以使用脚本来批量生成(也可以不是用脚本,手动一个一个改)
port就是Redis节点自身绑定的端口(容器内的端口),不同的容器内部可以有相同的端口,后续进行端口映射,再把这些容器内的端口映射到容器外的不同端口即可
业务端口:用来进行业务数据通信的(响应Redis客户端的请求的)
管理端口:为了完成一些管理上的任务来进行通信的(如果某个分片中的Redis主节点挂了,就需要让从节点成为主节点,就需要通过刚才管理端口来完成对应的操作)
创建出Redis容器
把上述Redis节点构成集群:
redis-cli --cluster create 172.30.0.101:6379 172.30.0.102:6379 172.30.0.103:6379 172.30.0.104:6379 172.30.0.105:6379 172.30.0.106:6379 172.30.0.107:6379 172.30.0.108:6379 172.30.0.109:6379 --cluster-replicas 2
列出每个参与构建集群的IP和端口,端口都是写容器内部的端口号
cluster-replicas 2:描述集群的每个节点,应该是有两个从节点;这个配置设置了之后,Redis就知道了3个节点是一伙的(一个分片)一共是9个节点,一共是3个分片
注:Redis在构建集群的时候,谁是主节点,谁是从节点,谁和谁是分片,都是不固定的(本身从集群的角度看,提供的这些节点之间本身就应该是等价的)
步骤:
1)生成每个Redis节点的配置文件
2)使用docker创建出11个Redis节点,并且启动容器
3)使用docker-cli执行构建集群命令
注:集群之后不易定所有的命令就都能够使用,像是控制多个key
从101-109九个节点,现在是一个整体,使用客户端上连接任意一个节点,本质上都是等价的
用cluster nodes查看当前集群信息:
设置集群后,当前数据就分片了:(当前key这个key通过hash计算之后,通过slot 12539,属于103这个节点)
-c选项添加之后,Redis客户端就会根据当前key实际算出来的槽位号,自动找到匹配的分片主机,进一步就可以完成操作了
如果集群中,有节点挂了?
如果挂了的节点是从节点,没事;如果挂了的节点是主节点,此处集群做的工作,就和之前哨兵做的工作类似了,就会自动把该主节点旗下从节点,挑一个出来,提拔成主节点
没有故障的集群
停掉Redis1:docker stop redis1
停掉redis1后的集群:此时redis5就是主节点了
重启redis1节点:docker start redis1
重启redis1节点之后:redis1节点变成从节点
此处就是故障转移
1.故障判定:识别出某个节点是否挂了(心跳包)
①. 节点A给节点B发送ping包,B就会给A返回⼀个pong包.ping和pong除了 message type 属性之外,其他部分都是⼀样的.这⾥包含了集群的配置信息(该节点的id,该节点从属于哪个分⽚, 是主节点还是从节点,从属于谁,持有哪些slots的位图...).
②. 每个节点,每秒钟,都会给⼀些随机的节点发起ping包,⽽不是全发⼀遍.这样设定是为了避免在节 点很多的时候,⼼跳包也⾮常多(⽐如有9个节点,如果全发,就是9*8有72组⼼跳了,⽽且这是按 照N^2这样的级别增⻓的).
③. 当节点A给节点B发起ping包,B不能如期回应的时候,此时A就会尝试重置和B的tcp连接,看能 否连接成功.如果仍然连接失败,A就会把B设为PFAIL状态(相当于主观下线).
④. A判定B为PFAIL之后,会通过redis内置的Gossip协议,和其他节点进⾏沟通,向其他节点确认B 的状态.(每个节点都会维护⼀个⾃⼰的"下线列表",由于视⻆不同,每个节点的下线列表也不⼀定相 同). ⑤. 此时A发现其他很多节点,也认为B为PFAIL,并且数⽬超过总集群个数的⼀半,那么A就会把B标 记成FAIL(相当于客观下线),并且把这个消息同步给其他节点(其他节点收到之后,也会把B标记成 FAIL).
2.故障迁移
①如果B是从节点,那么不需要进⾏故障迁移.
②如果B是主节点,那么就会由B的从节点(⽐如C和D)触发故障迁移了.
所谓故障迁移,就是指把从节点提拔成主节点,继续给整个redis集群提供⽀持.
具体流程如下:
①. 从节点判定⾃⼰是否具有参选资格.如果从节点和主节点已经太久没通信(此时认为从节点的数据和 主节点差异太⼤了),时间超过阈值,就失去竞选资格.
②. 具有资格的节点,⽐如C和D,就会先休眠⼀定时间.休眠时间=500ms基础时间+[0,500ms]随机 时间+排名*1000ms.offset的值越⼤,则排名越靠前(越⼩).
③. ⽐如C的休眠时间到了,C就会给其他所有集群中的节点,进⾏拉票操作.但是只有主节点才有投票 资格.
④. 主节点就会把⾃⼰的票投给C(每个主节点只有1票).当C收到的票数超过主节点数⽬的⼀半,C就 会晋升成主节点.(C⾃⼰负责执⾏slaveofnoone,并且让D执⾏slaveofC).
⑤. 同时,C还会把⾃⼰成为主节点的消息,同步给其他集群的节点.⼤家也都会更新⾃⼰保存的集群结构信息.
某个或者某些节点宕机,有的时候会引起整个集群都宕机(称为fail状态).
以下三种情况会出现集群宕机:
① 某个分⽚,所有的主节点和从节点都挂了.
② 某个分⽚,主节点挂了,但是没有从节点.
③超过半数的master节点都挂了.
前两种情况:该分片就无法提供数据服务了
第三种情况:此时master挂了,但是后面还有slave做补充(如果一系列的master都挂了,此时说明集群遇到了非常严重的情况,此时就的赶紧停下来,检查检查是不是有什么问题)
集群扩容:
101-109 9个主机 构成了3主 6从结构的集群
扩容:以110为master,111为slave=>把数据分片从3->4
1.新的主节点110计入到集群中:redis-cli --cluster add-node 172.30.0.110:6379(新增节点是什么) 172.30.0.101:6379(集群上任意一个节点,表示要把新节点放到那个集群中)
2.重新分配slots(把之前的三组拎一些出来,分配给新的master):redis-cli --cluster reshard 172.30.0.101:6379
①4个分片一共是16384个,除以4得到的就是4096
②会询问那个节点来接收:
③输入从哪些节点来移动slots
1)all,表示从其他每个持有slots的master都拿过来点(输入all之后,会给出搬运计划(还没真正搬运),当输入yes之后才真正搬运,此时不仅是slots重新划分,也会把slots上对应的数据,也搬运到新的主机上)
2)手动指定,从某一个或者某几个节点来移动slots(以done为结尾)
扩容之后:
注:如果在搬运slots/key的过程中,此时客户端能否访问redis集群呢?
搬运key,大部分key是不用搬运的,针对这些未搬运的key,此时可以正常访问的,针对这些正在搬运中的key,是有可能出现访问出错的情况(假设客户端访问key1,集群通过分片算法,得到key1是第一个分片数据,就会重定向到第一个分片的节点,就可能在重定向过去之后,key1就被搬走了,自然就无法访问了)
3.把从节点加到集群中
111添加进来,作为110的从节点
redis-cli --cluster add-node 172.30.0.111:6379 172.30.0.101:6379 --cluster-slave
添加之后: