【Redis】Redis集群主从调整算法

一、问题出发

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













请使用浏览器的分享功能分享到微信等