Codis源码解析——slot的分配

来源:互联网 发布:java shiro 权限控制 编辑:程序博客网 时间:2024/06/03 14:33

上一篇我们给codis集群中添加了codis-server,接下来就是把1024个slot分配给每个codis-server。Codis给我们提供了多种方式,可以将指定序号的slot移到某个group,也可以将一个group中的多少个slot移动到另一个group。不过最方便的方式就是通过自动rebalance。

首先看一下Slot的结构,可以看到,每个Slot都分配了其所属的BackendAddr。了解结构之后,我们就大概猜到Slot分配过程中需要做什么了

type Slot struct {     id int     lock struct {      hold bool      sync.RWMutex     }     refs sync.WaitGroup     switched bool     //migrate表示从何处迁移     backend, migrate struct {      id int      bc *sharedBackendConn     }     replicaGroups [][]*sharedBackendConn     method forwardMethod}

1 自动rebalance

这里写图片描述

那么,这个自动rebalance的过程是怎么样的呢?我们本地有两个group,就以这两个group为例进行说明

这里写图片描述

//第一次进入的时候传入的confirm是false,因为只是指定rebalance的plan。当页面上弹窗问是否迁移的时候,点了OK,又会进入这个方法,传入confirm为truefunc (s *Topom) SlotsRebalance(confirm bool) (map[int]int, error) {    s.mu.Lock()    defer s.mu.Unlock()    //获取上下文,这个步骤前面几篇都有了,就不再赘述了    ctx, err := s.newContext()    if err != nil {        return nil, err    }    var groupIds []int    for _, g := range ctx.group {        if len(g.Servers) != 0 {            groupIds = append(groupIds, g.Id)        }    }    //升序排序,结果是[1,2]    sort.Ints(groupIds)    if len(groupIds) == 0 {        return nil, errors.Errorf("no valid group could be found")    }    var (        //已分配好,不需要再迁移的,键是groupId,值是当前group不需迁移的slot的数量        assigned = make(map[int]int)        //等待分配的,键是groupId,值是当前group等待分配的slot的id组成的切片        pendings = make(map[int][]int)        //可以迁出的,键是groupId,值是当前group可以迁出的slot的数量。如果是负数,就表明当前group需要迁入多少个slot        moveout  = make(map[int]int)        //确定要迁移的slot,int切片,每个元素就是确定要迁移的slot的id        docking  []int    )    //计算某个group中槽的数量的方法,固定属于该组的,加上待分配的,再减去要迁移出去的    var groupSize = func(gid int) int {        return assigned[gid] + len(pendings[gid]) - moveout[gid]    }    //如果槽的Action.State等于空字符串,才可以迁移。否则不能迁移    for _, m := range ctx.slots {        if m.Action.State != models.ActionNothing {            //如果这个槽处于迁移的过程中,就直接归属于targetId的组            assigned[m.Action.TargetId]++        }    }    //这里是1024/2=512    var lowerBound = MaxSlotNum / len(groupIds)    //遍历槽,如果槽所属的group的size小于512,这个槽也不需要迁移    for _, m := range ctx.slots {        if m.Action.State != models.ActionNothing {            continue        }        if m.GroupId != 0 {            if groupSize(m.GroupId) < lowerBound {                assigned[m.GroupId]++            } else {                //表示当前槽可以等待分配                pendings[m.GroupId] = append(pendings[m.GroupId], m.Id)            }        }    }    //传入一个自定义的比较器,新建红黑树。这个比较器的结果是,红黑树中左边的节点的group size小于右边的    var tree = rbtree.NewWith(func(x, y interface{}) int {        var gid1 = x.(int)        var gid2 = y.(int)        if gid1 != gid2 {            if d := groupSize(gid1) - groupSize(gid2); d != 0 {                return d            }            return gid1 - gid2        }        return 0    })    for _, gid := range groupIds {        tree.Put(gid, nil)    }    //将不属于任何group和Action.State为""的slot(被称为offline的slot,初始阶段所有slot都是offline的),分配给目前size最小的group    for _, m := range ctx.slots {        if m.Action.State != models.ActionNothing {            continue        }        if m.GroupId != 0 {            continue        }        //得到整个树最左边节点的键,也就是size最小的group的id        dest := tree.Left().Key.(int)        tree.Remove(dest)        //当前节点要进行迁移        docking = append(docking, m.Id)        moveout[dest]--        tree.Put(dest, nil)    }    //在我们这个例子里面也是512。如果是上限,9999个group,这个值就是1    var upperBound = (MaxSlotNum + len(groupIds) - 1) / len(groupIds)    // 当集群中group的数量大于2(上限是9999),红黑树的rebalance。在group size差距最大的两个组之间做迁移准备工作    for tree.Size() >= 2 {        //group size最大的groupId        from := tree.Right().Key.(int)        tree.Remove(from)        if len(pendings[from]) == moveout[from] {            continue        }        dest := tree.Left().Key.(int)        tree.Remove(dest)        var (            fromSize = groupSize(from)            destSize = groupSize(dest)        )        if fromSize <= lowerBound {            break        }        if destSize >= upperBound {            break        }        if d := fromSize - destSize; d <= 1 {            break        }        moveout[from]++        moveout[dest]--        tree.Put(from, nil)        tree.Put(dest, nil)    }    //moveout的键值对分别是1和2,值都是-512。表明两个group都需要迁入512个slot    for gid, n := range moveout {        if n < 0 {            continue        }        if n > 0 {            sids := pendings[gid]            sort.Sort(sort.Reverse(sort.IntSlice(sids)))            docking = append(docking, sids[0:n]...)            pendings[gid] = sids[n:]        }        delete(moveout, gid)    }    //docking升序排列,结果是0到1023    sort.Ints(docking)    //键是slot的id,值是这个slot要迁移到的group的id    var plans = make(map[int]int)    //找到需要迁入slot的group,也就是moveout[gid]为负数的group,从docking中的第一个元素开始迁移到这个group    for _, gid := range groupIds {        var in = -moveout[gid]        for i := 0; i < in && len(docking) != 0; i++ {            plans[docking[0]] = gid            //docking去除刚刚分配了的首元素            docking = docking[1:]        }    }    if !confirm {        return plans, nil    }    //只有弹窗点击OK,方法才会走到这里。现在开始执行plan中的规划    var slotIds []int    for sid, _ := range plans {        slotIds = append(slotIds, sid)    }    sort.Ints(slotIds)    for _, sid := range slotIds {        m, err := ctx.getSlotMapping(sid)        if err != nil {            return nil, err        }        defer s.dirtySlotsCache(m.Id)        m.Action.State = models.ActionPending        //每一个Slot的Action.Index都是其slotId+1        m.Action.Index = ctx.maxSlotActionIndex() + 1        m.Action.TargetId = plans[sid]        //这里就是在zk中更新路径        if err := s.storeUpdateSlotMapping(m); err != nil {            return nil, err        }    }    return plans, nil}func (ctx *context) getSlotMapping(sid int) (*models.SlotMapping, error) {    if len(ctx.slots) != MaxSlotNum {        return nil, errors.Errorf("invalid number of slots = %d/%d", len(ctx.slots), MaxSlotNum)    }    if sid >= 0 && sid < MaxSlotNum {        return ctx.slots[sid], nil    }    return nil, errors.Errorf("slot-[%d] doesn't exist", sid)}type SlotMapping struct {    Id      int `json:"id"`    GroupId int `json:"group_id"`    Action struct {        Index    int    `json:"index,omitempty"`        State    string `json:"state,omitempty"`        TargetId int    `json:"target_id,omitempty"`    } `json:"action"`}const (    ActionNothing   = ""    ActionPending   = "pending"    ActionPreparing = "preparing"    ActionPrepared  = "prepared"    ActionMigrating = "migrating"    ActionFinished  = "finished"    ActionSyncing   = "syncing")

最后一步zk更新路径之后,我们就可以在zk中看到slot被挂到了相应的group下

这里写图片描述

这里写图片描述

2 手动迁移

另外两种迁移槽的方式,即我们在开头说过的,一是指定序号的slot移到某个group,二是将一个group中的多少个slot移动到另一个group,主要流程类似,先取出当前集群的上下文,然后根据请求参数做校验,将符合迁移条件的slot放到一个pending切片里面,接下去更新zk,就不专门做介绍了。

说明
如有转载,请注明出处
http://blog.csdn.net/antony9118/article/details/77016335

原创粉丝点击