集群原理简述
Redis3.0版本之前没有提供集群功能,一般用一致性Hash和Hash环在客户端做key的分片。3.0版本开始Redis使用Hash槽实现分片,Redis共准备了16384个槽(slot),这些slot分布在集群节点上。
为什么是16384?
Redis需要维护节点和槽之间的映射关系,每个节点需要知道自己有哪些槽,并且需要在结点之间传递这个消息。
为了节省存储空间,每个节点用一个Bitmap来存放其对应的槽:
2k = 2*1024*8 = 16384,也就是说,每个结点用2k的内存空间,总共16384个比特位,就可以存储该结点对应了哪些槽。然后这2k的信息,通过Gossip协议,在结点之间传递。
客户端使用公式(HASH_SLOT = CRC16(key) mod 16384)计算出Key对应的slot位置,根据从服务端获取slot和节点映射信息,找到slot对应的节点。
如果服务端slot对应的节点发生了变更,客户端向旧节点发送操作时会收到MOVED指令:(error) MOVED 866 127.0.0.1:7000,意思是866这个slot已经迁移到127.0.0.1:7000节点,客户端向新节点发送操作并更新本地缓存的slot和节点映射信息。
集群容灾
当Redis部分slot所在的节点不可用时,整个集群都变的不可用,所以集群容灾最重要的就是要避免这种情况出现。
方案一:为每个master节点配置一个slave节点
优点:当master宕机时,slave自动提升为master节点恢复服务。
缺点:如果slave节点也宕机,由于没有其他从节点可以提升为主节点(因为master节点仍未恢复正常),集群没法继续进行正常操作。如果为每个master节点都配置多个slave节点,则会造成浪费。
方案二:不对称集群——备份迁移
不对称性集群是让集群布局时不时地自动变化。
例如,假设集群有三个主节点 A,B,C。节点 A 和 B 都各有一个从节点,A1 和 B1。节点 C 有两个从节点:C1 和 C2。
主节点 A 失效。A1 被提升为主节点。
节点 C2 迁移成为节点 A1 的从节点,要不然 A1 就没有任何从节点。
三个小时后节点 A1 也失效了。
节点 C2 被提升为取代 A1 的新主节点。
集群仍然能继续正常工作。
备份迁移算法
迁移算法不用任何形式的协议,因为 Redis 集群中的从节点布局不是集群配置信息(配置信息要求前后一致并且/或者用 config epochs 来标记版本号)的一部分。 它使用的是一个避免在主节点没有备份时从节点大批迁移的算法。这个算法保证,一旦集群配置信息稳定下来,最终每个主节点都至少会有一个从节点作为备份。
接下来讲这个算法是如何工作的。在开始之前我们需要定义清楚在这个上下文中什么才算是一个好的从节点:一个好的从节点是指从给定节点的角度看,该从节点不处于 FAIL 状态。
每个从节点若检测出存在至少一个没有好的从节点的单一主节点,那么就会触发这个算法的执行。然而在所有检测出这种情况的从节点中,只有一部分从节点会采取行动。 通常这“一部分从节点”都只有一个,除非有不同的从节点在给定时间间隔里对其他节点的失效状态有稍微不同的视角。
采取行动的从节点是属于那些拥有最多从节点的主节点,并且不处于 FAIL 状态及拥有最小的节点 ID。
例如,如果有 10 个主节点,它们各有 1 个从节点,另外还有 2 个主节点,它们各有 5 个从节点。会尝试迁移的从节点是在那 2 个拥有 5 个从节点的主节点中的所有从节点里,节点 ID 最小的那个。已知不需要用到任何协议,在集群配置信息不稳定的情况下,有可能发生一种竞争情况:多个从节点都认为自己是不处于 FAIL 状态并且拥有较小节点 ID(实际上这是一种比较难出现的状况)。如果这种情况发生的话,结果是多个从节点都会迁移到同个主节点下,不过这种结局是无害的。这种竞争发生的话,有时候会使得割让出从节点的主节点变成没有任何备份节点,当集群再次达到稳定状态的时候,本算法会再次执行,然后把从节点迁移回它原来的主节点。
最终每个主节点都会至少有一个从节点作为备份节点。通常表现出来的行为是,一个从节点从一个拥有多个从节点的主节点迁移到一个孤立的主节点。
这个算法能通过一个用户可配置的参数 cluster-migration-barrier 进行控制。这个参数表示的是,一个主节点在拥有多少个好的从节点的时候就要割让一个从节点出来。例如这个参数若被设为 2,那么只有当一个主节点拥有 2 个可工作的从节点时,它的一个从节点会尝试迁移。
备份迁移测试
用于测试的集群:主节点7000/7001分别有从节点7005/7003,主节点7002有从节点7004和7006。
127.0.0.1:7000> cluster nodes 9b022d79cf860c87dc2190cdffc55b282dd60e42 127.0.0.1:7002@17002 master - 0 1542960331000 3 connected 10923-16383 3bcdfbed858bbdd92dd760632b9cb4c649947fed 127.0.0.1:7000@17000 myself,master - 0 1542960330000 1 connected 0-5460 ea4e0dcf8dbf6d4611659b5abbd6563926224f0f 127.0.0.1:7004@17004 slave 9b022d79cf860c87dc2190cdffc55b282dd60e42 0 1542960329000 5 connected 6e41c9b986e863943c1fd41de3411241e0fcb65b 127.0.0.1:7006@17006 slave 9b022d79cf860c87dc2190cdffc55b282dd60e42 0 1542960331690 3 connected e852e07181f20dd960407e5b08f7122870f67c89 127.0.0.1:7003@17003 slave 2a8f29e22ec38f56e062f588e5941da24a2bafa0 0 1542960331000 2 connected 2a8f29e22ec38f56e062f588e5941da24a2bafa0 127.0.0.1:7001@17001 master - 0 1542960328000 2 connected 5461-10922 583a6b71dcde83ab2685fcfd4ea94ecf9e353a1e 127.0.0.1:7005@17005 slave 3bcdfbed858bbdd92dd760632b9cb4c649947fed 0 1542960329000 7 connected
停掉7000后,7005升级为master。
127.0.0.1:7001> cluster nodes ea4e0dcf8dbf6d4611659b5abbd6563926224f0f 127.0.0.1:7004@17004 slave 9b022d79cf860c87dc2190cdffc55b282dd60e42 0 1542961385025 3 connected 2a8f29e22ec38f56e062f588e5941da24a2bafa0 127.0.0.1:7001@17001 myself,master - 0 1542961380000 2 connected 5461-10922 e852e07181f20dd960407e5b08f7122870f67c89 127.0.0.1:7003@17003 slave 2a8f29e22ec38f56e062f588e5941da24a2bafa0 0 1542961383000 6 connected 583a6b71dcde83ab2685fcfd4ea94ecf9e353a1e 127.0.0.1:7005@17005 master - 0 1542961383995 8 connected 0-5460 9b022d79cf860c87dc2190cdffc55b282dd60e42 127.0.0.1:7002@17002 master - 0 1542961382000 3 connected 10923-16383 6e41c9b986e863943c1fd41de3411241e0fcb65b 127.0.0.1:7006@17006 slave 9b022d79cf860c87dc2190cdffc55b282dd60e42 0 1542961381936 3 connected 3bcdfbed858bbdd92dd760632b9cb4c649947fed 127.0.0.1:7000@17000 master,fail - 1542961362723 1542961362313 1 disconnected
停掉7005后,原属于7002的从节点7006被提升为master。
127.0.0.1:7001> cluster nodes ea4e0dcf8dbf6d4611659b5abbd6563926224f0f 127.0.0.1:7004@17004 slave 9b022d79cf860c87dc2190cdffc55b282dd60e42 0 1542961749007 3 connected 2a8f29e22ec38f56e062f588e5941da24a2bafa0 127.0.0.1:7001@17001 myself,master - 0 1542961747000 2 connected 5461-10922 e852e07181f20dd960407e5b08f7122870f67c89 127.0.0.1:7003@17003 slave 2a8f29e22ec38f56e062f588e5941da24a2bafa0 0 1542961750033 6 connected 583a6b71dcde83ab2685fcfd4ea94ecf9e353a1e 127.0.0.1:7005@17005 master,fail - 1542961727108 1542961726285 8 disconnected 9b022d79cf860c87dc2190cdffc55b282dd60e42 127.0.0.1:7002@17002 master - 0 1542961746000 3 connected 10923-16383 6e41c9b986e863943c1fd41de3411241e0fcb65b 127.0.0.1:7006@17006 master - 0 1542961749000 9 connected 0-5460 3bcdfbed858bbdd92dd760632b9cb4c649947fed 127.0.0.1:7000@17000 master,fail - 1542961362723 1542961362313 1 disconnected
停掉7006后,没有其他从节点被提升,集群瘫痪。将7002的cluster-migration-barrier设置为0,7002的从节点也没有被提升。可能Redis要求从节点少于2个不给提升?
127.0.0.1:7001> cluster nodes ea4e0dcf8dbf6d4611659b5abbd6563926224f0f 127.0.0.1:7004@17004 slave 9b022d79cf860c87dc2190cdffc55b282dd60e42 0 1542963389063 3 connected 2a8f29e22ec38f56e062f588e5941da24a2bafa0 127.0.0.1:7001@17001 myself,master - 0 1542963389000 2 connected 5461-10922 e852e07181f20dd960407e5b08f7122870f67c89 127.0.0.1:7003@17003 slave 2a8f29e22ec38f56e062f588e5941da24a2bafa0 0 1542963387000 6 connected 583a6b71dcde83ab2685fcfd4ea94ecf9e353a1e 127.0.0.1:7005@17005 master,fail - 1542961727108 1542961726285 8 disconnected 9b022d79cf860c87dc2190cdffc55b282dd60e42 127.0.0.1:7002@17002 master - 0 1542963388024 3 connected 10923-16383 6e41c9b986e863943c1fd41de3411241e0fcb65b 127.0.0.1:7006@17006 master,fail - 1542963208985 1542963206916 9 disconnected 0-5460 3bcdfbed858bbdd92dd760632b9cb4c649947fed 127.0.0.1:7000@17000 master,fail - 1542961362723 1542961362313 1 disconnected
灾备恢复
假如我们某个主节点宕机后,由于某种原力死活启动不起来或者无法接入集群,需要我们在新的机器上恢复该节点和数据。
情况一:宕机服务器能访问,只是Redis无法使用
假设7006主节点挂了,复制7006整个目录到新机器,编辑根目录下的nodes-7006.conf,将里面7006的IP改为新机器的IP,直接启动即可恢复集群和数据。
如果是在同一台机器测试,将nodes-7006.conf改成nodes-7007.conf,修改redis.conf中的7006为7007。
注意:用7007代替7006后,重新启动7006时,需要在7006执行flushall和cluster reset,清除之前的集群信息,然后重新手动将7006加入集群,否则集群会报错。
情况二:宕机服务器无法访问,文件也复制不出来
这种情况数据肯定是没了,除非之前有做过备份。我们先想办法保证恢复集群可用性。
将当前集群中任意一台可用服务的nodes-port.conf文件复制到新机器的Redis目录下,这里假设用7008来恢复7007,修改文件名为nodes-7008.conf,修改redis.conf中相关端口为7008。
打开nodes-7008.conf文件
删除原节点所在行的"myself,",
在7007所在行的"master"前添加"myself,",
删除master后面的"fail",如果"fail"后面有其他异常标志,一并删除。
修改当前行的ip:port为新节点的ip:port
启动7008。