Codis源碼解析——slot的分配

上一篇我們給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爲true
func (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差距最大的兩個組之間做遷移準備工作
    //from和dest分別是紅黑樹最左和最右的兩個group,換句話說,slot之間的補給傳遞,都是先比較當前groupsize最大的和最小的組
    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

發佈了62 篇原創文章 · 獲贊 237 · 訪問量 58萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章