Codis源码解析——处理slot操作(1)
来源:互联网 发布:nodejs json解析 编辑:程序博客网 时间:2024/06/07 01:25
上一篇我们讲了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
- Codis源码解析——处理slot操作(1)
- Codis源码解析——处理slot操作(2)
- Codis源码解析——slot的分配
- Codis源码解析——dashboard的启动(1)
- Codis源码解析——codis-server添加到集群
- Codis源码解析——dashboard的启动(2)
- Codis源码解析——sharedBackendConn
- Codis源码解析——Jodis
- Codis源码解析——sentinel的重同步(1)
- Codis源码解析——proxy的启动
- Codis源码解析——proxy监听redis请求
- Codis源码解析——fe的启动
- Codis源码解析——proxy添加到集群
- Codis源码解析——sentinel的重同步(2)
- CODIS源码剖析二(codis-proxy功能实现)
- Vue——Slot(插槽)
- Redis源码解析(1)——源码目录介绍
- Redis源码解析(1)——源码目录介绍
- python学习之路-协程-day10
- 神奇的救火现场
- 使用gpTools进行线的等分处理
- HDU1575 Tr A【矩阵快速幂】
- 认识是第一步,你好HTML
- Codis源码解析——处理slot操作(1)
- 算法系列——开篇及目录
- COGS 1473 O(N*logN) 高精乘 FFT
- 数据结构
- 逻辑代数的基本运算
- [第七季]6.实现DIV的显示和隐藏
- 【CUGBACM15级BC第20场 B】hdu 5124 lines
- 【笔记篇】C#笔记1
- 1.14 C# 集合(2)