codis迁移方案分析

codis在迁移方案上下了不少功夫,我认为codis和twemproxy最大的区别之一是:

codis是面向slot的管理,而twemproxy只是面向redis后端的管理

codis迁移是由Topom结构体的SlotCreateAction方法开始的,这个方法也许叫做SlotMoveToGroupCreateAction更加容易让人理解

这个方法需要两个参数,slot id以及group id,目的是吧这个slot(以及该slot的所有key)迁移到目标group中

SlotCreateAction检测了一些边界条件,例如slot正在被迁移,或者该slot已经存在与目标group

而后就进入了ActionPending状态,由后台ProcessSlotAction来推进状态机进行演化

slot迁移状态转换

如图,蓝色箭头表示命令触发,黄色箭头表示后台ProcessSlotAction推进演化

ActionPreparing进入ActionPrepared没有任何条件

进入ActionPrepared后第一个动作是向每个proxy执行fillslot来往slot里填充内容:

后端地址(BackendAddr)为slot迁移目标组的master地址

迁移来源(MigrateFrom)为slot原来所在组的master地址

以及该slot写锁,设置为true,不让客户端进行读写

如果fillslot失败将回滚到ActionPreparing阶段,而后后台ProcessSlotAction将继续推进到ActionPrepared阶段,来重试这个fillslot操作

ActionPrepared进入ActionMigrating后做的第一个事情仍然是fillslot。

BackendAddr和MigrateFrom不变,区别是锁将由slot迁移目标组和slot原来所在组的Promoting状态决定

只要有一个组的Promoting处于ActiongPrepared状态,那么就会锁死这个slot

而后会等待两个组的Promoting结束才能继续(Promoting结束也会把这个slot解锁)

ActionMigrating阶段的最后一件事情也是最重要的事情,是向slot所在的redis发出MigrateSlot(slot id, from, dst)指令,由redis来完成最终的迁移过程

迁移结束后,ActionMigrating演化为ActionFinished阶段

该阶段重新fillslot,包括以下内容:

后端地址(BackendAddr)为slot迁移目标组的master地址

锁将由slot迁移目标组的Promoting状态是否为ActionPrepared决定

分析:

1 状态机+后台进程来实现的原因

在之前一篇博客的codis高可用中分析的sync操作同样使用了状态机+后台进程来实现,原因是相似的

多个redis同时执行migrate slot操作会减小整个redis集群的可用性,所以需要一个后台队列来一个slot一个slot的完成

2 三次fillslot的作用

第一次fillslot将BackendAddr由OriginGroupMaster改写为TargetGroupMaster,这一步操作相当于读写临界区资源BackendAddr,所以必须带写锁,而MigrateFrom只是顺便改了而已

第二次fillslot相当于取消了第一次的写锁,但是如果Promoting在执行的话,不应该取消Promoting设置的锁

第三次fillslot取消了MigrateFrom

那么问题来了,MigrateFrom到底是做什么用的

这部分在proxy的代码里面,在proxy收到请求后,会使用Router结构体的Dispatch方法来分发请求

分发请求时存在一个prepare方法,这一步会获取到该key对应的slot是否有MigrateFrom

如果有的话,会使用SLOTSMGRTTAGONE将这个key从MigrateFrom代表的redis强制迁移到Backend代表的redis里去,迁移完成以后再去访问Backend获得这个key

这样就能解决,如果被迁移的slot中的key,刚好被访问时,产生的一致性问题了

3 slot lock对slot migrate的作用

migrate基本上不依赖lock,当发生数据冲突时,由强制迁移这个key来解决一致性问题,和lock基本上没太大关系,lock主要是针对promote设计的

4 promote和migrate的关系

如果有任意一个slot migrate正在执行的时候,执行promote指令会报错

如果promote正在执行的时候,执行slot migrate不会报错,如果slot migrate涉及到的两个组被promote,就会阻塞在ActionMigrating阶段,直到完成

5 是否有优化空间

codis的迁移方案把slot属性做在redis里,然后新加一个slotmigrate指令,这种方式让人眼前一亮

以前我也做过一个方案,是使用第三方程序来对redis进行迁移,如果第三方程序挂了,导致的迁移失败势必牵涉到严重的一致性问题

由redis来迁移就不存在这个问题了。最差的情况也就是redis挂了,但是slot迁移到一半(这种情况还没有验证,后续会验证补充到博客里)

唯一的缺点是由于每个key新加了一个slot属性,使得如果key和value都比较小的时候,占用的内存大大增加了

这个缺点比起稳定的迁移过程来说,也并不算什么