上一篇我們給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