Codis源码解析——处理slot操作(1)

上一篇我们讲了slot在集群中的分配方式,重点讲了auto-rebalance的原理。之前我们说过,再启动dashboard的时候,有一个goroutine专门用来处理slot的操作。这一篇我们就来看看slot的操作是如何进行的。我们这里举例也是用集群中有两个group和1024个从未分配的slot。

首先复习一下,在slot还处于未分配状态的时候,上下文中的1024个SlotMapping如下所示。后面就是以SlotMapping为单位进行处理,所以这里一定要对其结构掌握清楚

这里写图片描述

接下来,当我们使用auto-rebalance对集群进行处理后,每个slot都被指定了相应的迁移计划

func (s *Topom) ProcessSlotAction() error {
    for s.IsOnline() {
        var (
            marks = make(map[int]bool)
            //分配slot的时候点击弹窗的confirm之后,这个plans才能取出值
            plans = make(map[int]bool)
        )
        var accept = func(m *models.SlotMapping) bool {
            if marks[m.GroupId] || marks[m.Action.TargetId] {
                return false
            }
            if plans[m.Id] {
                return false
            }
            return true
        }
        //对plans和marks进行初始化
        var update = func(m *models.SlotMapping) bool {
            //只有在槽当前的GroupId为0的时候,marks[m.GroupId]才是false
            if m.GroupId != 0 {
                marks[m.GroupId] = true
            }
            marks[m.Action.TargetId] = true
            plans[m.Id] = true
            return true
        }
        //按照默认的配置文件,这个值是100,并行迁移的slot数量,是一个阀值
        var parallel = math2.MaxInt(1, s.config.MigrationParallelSlots)
        //第一次的时候plans为空,所以下面的方法一定会执行一次,这个过程中plans会初始化。后面如果plans的长度大于100,就直接对所有plans做处理;
        //否则如果集群中所有Slotmapping中Action.state最小的那个Slotmapping如果处于pending,preparing或者prepared,也可以跳出循环对plans进行处理
        for parallel > len(plans) {
            //对是否满足plans的处理情况做过滤,后面会讲这个方法
            _, ok, err := s.SlotActionPrepareFilter(accept, update)
            if err != nil {
                return err
            } else if !ok {
                break
            }
        }
        //在指定slot的分配plan之前,这个一直是return nil
        if len(plans) == 0 {
            return nil
        }
        var fut sync2.Future
        //从plans中取出具体的每个slot的迁移计划,前面我们已经说过,plans的键是每一个slot的id,值是要迁移到的groupId
        for sid, _ := range plans {
            fut.Add()
            go func(sid int) {
                log.Warnf("slot-[%d] process action", sid)
                //针对每个slot做处理
                var err = s.processSlotAction(sid)
                if err != nil {
                    status := fmt.Sprintf("[ERROR] Slot[%04d]: %s", sid, err)
                    s.action.progress.status.Store(status)
                } else {
                    s.action.progress.status.Store("")
                }
                //在Future的vmap中存储slotId和对应的error,并调用WaitGroup.Done
                fut.Done(strconv.Itoa(sid), err)
            }(sid)
        }
        //当所有slot操作结束之后,遍历Future的vmap,取出有error的并返回
        for _, v := range fut.Wait() {
            if v != nil {
                return v.(error)
            }
        }
        time.Sleep(time.Millisecond * 10)
    }
    return nil
}

一个slot共有七种状态,分别是:
nothing(用空字符串表示)、pending、preparing、prepared、migrating、finished、syncing

在看每个slot具体的操作之前,可以先看一下SlotActionPrepareFilter这个方法。


func (s *Topom) SlotActionPrepareFilter(accept, update func(m *models.SlotMapping) bool) (int, bool, error) {
    s.mu.Lock()
    defer s.mu.Unlock()
    //加载上下文
    ctx, err := s.newContext()
    if err != nil {
        return 0, false, err
    }

    //找到所有Action.State既不为空也不是pending的SlotMapping中Action.Index最小的SlotMapping
    var minActionIndex = func(filter func(m *models.SlotMapping) bool) (picked *models.SlotMapping) {
        for _, m := range ctx.slots {
            if m.Action.State == models.ActionNothing {
                continue
            }
            if filter(m) {
                if picked != nil && picked.Action.Index < m.Action.Index {
                    continue
                }
                //只有一个slot没有执行过update方法,accept才会返回true;也就是说,一个slot只会被处理一次
                if accept == nil || accept(m) {
                    picked = m
                }
            }
        }
        return picked
    }

    var m = func() *models.SlotMapping {
        var picked = minActionIndex(func(m *models.SlotMapping) bool {
            return m.Action.State != models.ActionPending
        })
        if picked != nil {
            return picked
        }
        if s.action.disabled.IsTrue() {
            return nil
        }
        //如果前面找不到Action.State既不为空也不是pending的SlotMapping中Action.Index最小的SlotMapping
        //就去找Action.State为pending的SlotMapping中Action.Index最小的SlotMapping
        return minActionIndex(func(m *models.SlotMapping) bool {
            return m.Action.State == models.ActionPending
        })
    }()

    if m == nil {
        return 0, false, nil
    }

    if update != nil && !update(m) {
        return 0, false, nil
    }

    log.Warnf("slot-[%d] action prepare:\n%s", m.Id, m.Encode())

    //变更每个SlotMapping的action.state,并与zk交互
    //另外,Action.state符合preparing或者prepared的时候,要根据SlotMapping的参数同步到Slot
    switch m.Action.State {

    case models.ActionPending:

        defer s.dirtySlotsCache(m.Id)

        //Action.State指向下一阶段
        m.Action.State = models.ActionPreparing
        //只是更新zk
        if err := s.storeUpdateSlotMapping(m); err != nil {
            return 0, false, err
        }

        fallthrough

    case models.ActionPreparing:

        defer s.dirtySlotsCache(m.Id)

        log.Warnf("slot-[%d] resync to prepared", m.Id)

        m.Action.State = models.ActionPrepared
        //同步SlotMapping操作,后面会有介绍
        if err := s.resyncSlotMappings(ctx, m); err != nil {
            log.Warnf("slot-[%d] resync-rollback to preparing", m.Id)
            m.Action.State = models.ActionPreparing
            s.resyncSlotMappings(ctx, m)
            log.Warnf("slot-[%d] resync-rollback to preparing, done", m.Id)
            return 0, false, err
        }
        if err := s.storeUpdateSlotMapping(m); err != nil {
            return 0, false, err
        }

        fallthrough

    case models.ActionPrepared:

        defer s.dirtySlotsCache(m.Id)

        log.Warnf("slot-[%d] resync to migrating", m.Id)

        m.Action.State = models.ActionMigrating
        if err := s.resyncSlotMappings(ctx, m); err != nil {
            log.Warnf("slot-[%d] resync to migrating failed", m.Id)
            return 0, false, err
        }
        if err := s.storeUpdateSlotMapping(m); err != nil {
            return 0, false, err
        }

        fallthrough

    case models.ActionMigrating:

        return m.Id, true, nil

    case models.ActionFinished:

        return m.Id, true, nil

    //如果不属于以上任何一种情况,直接返回invalid
    default:

        return 0, false, errors.Errorf("slot-[%d] action state is invalid", m.Id)

    }
}

很显然,上面的方法取出的最小的Action.State的Slotmapping是

这里写图片描述

当一个SlotMapping处于preparing和prepared转台的时候,会将其状态推进到下一阶段,并同步SlotMapping,根据[]*models.SlotMapping创建1024个models.Slot,再填充1024个pkg/proxy/slots.go中的Slot,此过程中Router为每个Slot都分配了对应的backendConn。下面就来看看这个同步方法。

func (s *Topom) resyncSlotMappings(ctx *context, slots ...*models.SlotMapping) error {
    if len(slots) == 0 {
        return nil
    }
    var fut sync2.Future
    for _, p := range ctx.proxy {
        fut.Add()
        go func(p *models.Proxy) {
            //ApiClient中存储了proxy的address以及xauth信息。其中xauth是根据ProductName,ProductAuth以及proxy的token生成的
            err := s.newProxyClient(p).FillSlots(ctx.toSlotSlice(slots, p)...)
            if err != nil {
                log.ErrorErrorf(err, "proxy-[%s] resync slots failed", p.Token)
            }
            fut.Done(p.Token, err)
        }(p)
    }
    for t, v := range fut.Wait() {
        switch err := v.(type) {
        case error:
            if err != nil {
                return errors.Errorf("proxy-[%s] resync slots failed", t)
            }
        }
    }
    return nil
}

同步的过程中有两个方法比较复杂,分别是FillSlots和toSlotSlice。这一节我们先来看toSlotSlice。这个方法实际上就是将SlotMapping切片转化为Slot切片,在Slot结构体重记录了这个Slot在迁移的不同阶段,接到的请求由哪个BackendAddr进行处理。

type Slot struct {
    Id     int  `json:"id"`
    Locked bool `json:"locked,omitempty"`

    BackendAddr        string `json:"backend_addr,omitempty"`
    BackendAddrGroupId int    `json:"backend_addr_group_id,omitempty"`
    MigrateFrom        string `json:"migrate_from,omitempty"`
    MigrateFromGroupId int    `json:"migrate_from_group_id,omitempty"`

    ForwardMethod int `json:"forward_method,omitempty"`

    ReplicaGroups [][]string `json:"replica_groups,omitempty"`
}
func (ctx *context) toSlotSlice(slots []*models.SlotMapping, p *models.Proxy) []*models.Slot {
    var slice = make([]*models.Slot, len(slots))
    for i, m := range slots {
        slice[i] = ctx.toSlot(m, p)
    }
    return slice
}
func (ctx *context) toSlot(m *models.SlotMapping, p *models.Proxy) *models.Slot {
    slot := &models.Slot{
        Id:     m.Id,
        Locked: ctx.isSlotLocked(m),

        ForwardMethod: ctx.method,
    }
    switch m.Action.State {
    case models.ActionNothing, models.ActionPending:
        //这个getGroupMaster实际上就是从每个Group中取出第一台,因为codis中认定group中添加的第一台是主服务器
        slot.BackendAddr = ctx.getGroupMaster(m.GroupId)
        slot.BackendAddrGroupId = m.GroupId
        slot.ReplicaGroups = ctx.toReplicaGroups(m.GroupId, p)
    case models.ActionPreparing:
        slot.BackendAddr = ctx.getGroupMaster(m.GroupId)
        slot.BackendAddrGroupId = m.GroupId
    case models.ActionPrepared:
        fallthrough
    case models.ActionMigrating:
        slot.BackendAddr = ctx.getGroupMaster(m.Action.TargetId)
        slot.BackendAddrGroupId = m.Action.TargetId
        slot.MigrateFrom = ctx.getGroupMaster(m.GroupId)
        slot.MigrateFromGroupId = m.GroupId
    case models.ActionFinished:
        slot.BackendAddr = ctx.getGroupMaster(m.Action.TargetId)
        slot.BackendAddrGroupId = m.Action.TargetId
    default:
        log.Panicf("slot-[%d] action state is invalid:\n%s", m.Id, m.Encode())
    }
    return slot
}

其中如果slot处于migrating状态,migrate.bc就不为空,如果恰好有请求发到这个slot,proxy就会执行一次SLOTSMGRTTAGONE让这个slot迁移完成,再由其backend.bc来执行请求

下面的ReplicaGroups是专门为了主从读写分离设置的。从优先级我们可以看到,读请求会优先转发到和proxy在一台服务器上的codis-server,也就是优先级最高的0

func (ctx *context) toReplicaGroups(gid int, p *models.Proxy) [][]string {
    g := ctx.group[gid]
    switch {
    case g == nil:
        return nil
    case g.Promoting.State != models.ActionNothing:
        return nil
    case len(g.Servers) <= 1:
        return nil
    }
    var dc string
    var ip net.IP
    if p != nil {
        dc = p.DataCenter
        ip = ctx.lookupIPAddr(p.AdminAddr)
    }
    //replica的访问优先级
    getPriority := func(s *models.GroupServer) int {
        if ip == nil || dc != s.DataCenter {
            return 2
        }
        if ip.Equal(ctx.lookupIPAddr(s.Addr)) {
            return 0
        } else {
            return 1
        }
    }
    var groups [3][]string
    for _, s := range g.Servers {
        if s.ReplicaGroup {
            p := getPriority(s)
            groups[p] = append(groups[p], s.Addr)
        }
    }
    var replicas [][]string
    for _, l := range groups {
        if len(l) != 0 {
            replicas = append(replicas, l)
        }
    }
    return replicas
}

有关于FillSlot和每个槽的处理方法processSlotAction,我们会在下一篇讲

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值