一、问题出发
1)Redis 集群为什么要调整主从?
在大集群中,一个集群上百个redis 实例,一个 IP 可能有上十个实例,如果这十几个实例都是主实例,那么这么大的流量会导致集群的抖动
当承载10
多个主实例的节点宕机,集群的通信,数据的复制,可想而知,有多恐怖
2)Redis 大集群调整主从的窘境
100 多实例,人工调整,得耗费多少时间?
实例关系错综复杂,有些IP
之前实例有直接主从关系,直接
cluster failover
就能调整,但是如果节点之间没直接主从关系呢?甚至没有间接主从关系?
二、深入环境的提炼与抽象
1)有直接主从关系;比如A 节点与 C 节点
图(一)
2) 有间接主从关系;例如:A->B->C
图(2)
3) 节点之间无关系
比如:A 节点与 C 节点; D 节点与 C 节点;这里我们继续说明一个概念,一个集群中,如果IP团体之间没有关系,我们叫孤岛,如果整个集群都有关系,那么集群也是一个孤岛;图中AD是一个孤岛,BC是一个孤岛,即这个集群有两个孤岛
图(3)
三、提炼需求
(1)将孤岛中从实例最多的节点跟从实例最少的节点进行交换(可能要进过多次切换,比如图(2)要切换两次),直到平衡( 为什么说是要孤岛,而不是集群呢,因为一个孤岛与另一个孤岛之间没有直接和间接主从关系,无法 cluster failover 达到平衡 )
(2)分割孤岛( 每个孤岛可以按照第一步来达到平衡 )
四、核心难点解决
1)将每个IP 的按需要移动从实例的个数排列,然后首尾切换,得到平衡
例如:这里slave_most:3 表示有三个实例要变为主库; slave_most:0 表示没有实例变为主库, slave_most:-2 表示有 2 个实例要从库,这里需要两个数据结构,单个 IP 的 redis 抽象( RedisNode ),以及整个集群 IP 的 Redis 的抽象( RedisNodes )
图(4)
2) 求切换路径
在《深入环境的提炼与抽象》这里,我们讲了一个孤岛中,存在直接或者间接的主从关系,那么理想的切换是有直接关系的,然后再找有间接关系,这样我们可以 抽象成金字塔 , 自顶向下,从左向右扫描, 且扫描数层数不能超过节点总数 -1 ; 例如图 2 ,我们从 A 节点开始扫描,目标是 C 节点 (A 是金字塔的顶层 ) ,发现 A 节点上的从实例对应的主实例有节点 D( 即 D 是金字塔的第二层) , D 上的从实例对用的主实例有 C,A(C , A 即金字塔的第底层 ); 那么图 2 也可表示成 :
图(5)
这里还可以优化,就是底层的A 是可以去掉,因为形成了回环,用剪枝的方法给他去掉
3) 求孤岛
孤岛,一堆实例有直接和间接主从关系的节点的集合; 找出每个节点的直接关系图,然后有交集的就合并,没交集或者合并后与其他的集合没交集,就是孤岛 , 以图 (2) 为例:
图(6)
孤岛这里描述不太清楚,直接上代码吧
def getAreaNodes(self) : #单个ip的关系集合 tmp_sets=[] #有主从关系的IP的集合,包括级联关系 area_sets=[] for r_node in self.m_nodes.redis_nodes : ip_list=set() ip_list.add(r_node.node_name) for entry in r_node.entrys : if entry.is_master() : continue ip_list.add(entry.master_ip) tmp_sets.append(ip_list) #我只需要遍历单个ip关系集合-1次,就能找出区域数 num_t = len(tmp_sets)-1 #遍历优化,如果一个孤岛的节点数就是集群数,那可以立即返回 finish_flag=False for out_index in range(0,num_t) : #找到有关联关系的集合,则此值为True find_flag=False #一旦tmp_sets[0] 与后面的tmp_sets[1],tmp_sets[2],...任何一个下标为in_index有交集,则合并集合,开始下一轮循环 for in_index in range(1,len(tmp_sets)) : #有交集,则合并 if len([elem for elem in tmp_sets[0] if elem in tmp_sets[in_index]]) > 0 : tmp_sets[0]=tmp_sets[0] | tmp_sets[in_index] if len(tmp_sets[0]) == len(self.m_nodes.redis_nodes) : area_sets.append(tmp_sets[0]) finish_flag=True break del tmp_sets[in_index] find_flag=True break if finish_flag==True : break #与其他集合无交集,那肯定是孤岛,这这就是我们所求的孤岛 if find_flag==False or out_index==num_t-1 : area_sets.append(tmp_sets[0]) del tmp_sets[0] for area_index in range(0,len(area_sets)): self.node_areas.area_append(RedisNodes()) for r_node in self.m_nodes.redis_nodes : for area_index in range(0,len(area_sets)): if r_node.node_name in area_sets[area_index] : self.node_areas.areas[area_index].node_append(r_node) return self.node_areas