Redis 实践系列丨 Codis 数据迁移原理与优化

阅读数:65 2019 年 10 月 23 日 10:27

Redis实践系列丨Codis数据迁移原理与优化

Codis 介绍

Codis 是一种 Redis 集群的实现方案,与 Redis 社区的 Redis cluster 类似,基于 slot 的分片机制构建一个更大的 Redis 节点集群,对于连接到 codis 的 Redis 客户端来说, 除了部分不支持的命令外,与连接开源的 Redis Server 没有明显的区别, 客户端代码基本需要进行修改,Codis-proxy 会根据访问的 key 进行 slot 的计算,然后转发请求到对应的 Redis-server,对于客户端来说,中间的 codis-proxy 是不可见的,因此根据客户业务的需要,可以使用 codis 构建大规模的 Redis 服务,或者仅仅是用于把请求分担多个 Redis-server 提高系统的吞吐量。

与业界著名的 twproxy 相比,除了支持 Redis 的转发,coids 还支持不停机的数据迁移,使用户可以在容量或者吞吐量要求有变化时,轻松进行节点的增减,本文主要对 codis 的迁移原理进行分析,并提出一个可行的优化点。

本文是基于 codis3.0 版本。

Redis实践系列丨Codis数据迁移原理与优化

Codis 迁移实现原理

Codis-dashboard 在启动时,运行了 4 个后台线程 (goroutine),包括后台 redis 状态同步、proxy 状态同步、slot 事件处理、sync 事件处理,并提供了 slot 相关的 RestFUL API 进行 slot 与 Redis-group 归属关系的定义、迁移的定义和触发。

如下结构定义一个 slot 与 Redis-group 的归属关系和迁移关系,GroupId 表示索引为 Id 的 slot 所属的 redis-group,而 Action 用于表示一次迁移,Action.TargetId 表示该 slot 要迁移的目标 redis-group 的 Id,Action.State 表示迁移的状态,主要有 Pending、Preparing、Prepared、Migrating、Finished 几种状态。

复制代码
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"`
UpdatedAt int64 `json:"updated_at,omitempty"`
} `json:"action"`
}

手动进行一次迁移过程,可以用如下命令来触发:

codis-admin --dashboard=ADDR -slot-action --create --sid=ID --gid=ID,比如把 slot 10 迁移到 group 5,则可以执行” codis-admin --dashboard=ADDR -slot-action --create --sid=10 --gid=5”

如果是把多个 slot 迁移到同一个 server,则可以使用如下命令,一次性来定义若干个迁移操作,codis-admin --slot-action --create-range --beg=ID --end=ID --gid=ID,比如把 slot 10~15 迁移到 group 5,则可以执行” codis-admin --dashboard=ADDR -slot-action –create–range --beg=10 –end=15 --gid=5”。

一次迁移的执行过程中,slot 的 Action 的状态会发生变化,过程为:

Redis实践系列丨Codis数据迁移原理与优化

也可以触发 codis 进行 rebalance,命令为:codis-admin --dashboard=ADDR –rebalance --confirm,codis 会自动把 slot 往一些新加入的节点进行迁移,使各个节点负责的 slot 均衡。

Codis 迁移的测试

经测试,对于一个 64G 规模的集群(由 8 个节点组成,每个节点 8G),使用 redis-benchmark 写满数据,每个 key 的 value 长度为 32 字节,总共写入 341446298(3.4 亿)条数据,扩容到 128G,即对其中的 512 个 slot 进行迁移。

测试结果如下:

1 第一个 slot,从 06:49:31 开始到 07:47:23 结束,从总耗时 3472 seconds,也即 57 分 52 秒

2 后续的 7 个 slot,分别花费时间为 3463、3468、3472、3468、3472、3474、3470 seconds, 基本在 57~58 分之间,由于耗时非常长,没有记录后续的时间数据

从测试结果来看,迁移速度非常慢,每迁移一个 slot 需要花费基本 1 个小时,因此使用 codis 时,需要监控数据量,当数据不够时,需要进行及时的扩容,否则当空间不够时的故障处理和恢复时间可能影响线上业务。

Codis 迁移代码分析及瓶颈分析

从测试结果来看,迁移速度确实非常慢,极端情况下可能会影响线上业务,因此对迁移过程进行分析和优化就很有必要,下边对关键的实现代码 handleSlotRebalance 、StartDaemonRoutines、ProcessSlotAction 进行解读,并分析优化改进的地方。

01 handleSlotRebalance 实现分析

这个函数的主要逻辑分为三部分:

1)找到需要迁移的 slot;

2)为每个新节点分配 slot;

3)生成迁移操作;

Redis实践系列丨Codis数据迁移原理与优化

上面的代码的逻辑是:

1)根据节点个数和 slot 槽数 (固定的 1024),计算每个节点上应该负责的 slot 槽数,表示为 bound;

2)对每个 redis-group,找到需要迁移出去的 slot,表示为 pending;

Redis实践系列丨Codis数据迁移原理与优化

生成迁移计划:

1)遍历所有的 redis-group,对于已有的 slot 小于应该负责的 slot 槽数的,就要迁移一些槽进来;

2)所有的 redis-group,决定需要迁移进来的 slot 列表,表示为 plans;

Redis实践系列丨Codis数据迁移原理与优化

遍历迁移计划,使用 create actionRange 生成一系列的 slot action,并保存到 etcd,下一步就需要由后台线程去 etcd 中取出 slot 操作进行分别处理。

02 StartDaemonRoutines

Redis实践系列丨Codis数据迁移原理与优化

这个代码是在 dashboard 启动时就启动的后台任务,每隔 5 秒钟触发一次 slot 操作,且只会运行一个 slot 操作任务。

03 ProcessSlotAction 实现分析

分为两步 Topom.SlotActionPrepare 和 Topom.processSlotAction。

Redis实践系列丨Codis数据迁移原理与优化
Redis实践系列丨Codis数据迁移原理与优化

从上面代码可以看出:

1 从 0 开始从所有的 slot 中,选择一个 Action.State 不为 ActionNothing 的 slot 进行处理,比如,如果为迁移某个 slot,那么该 slot 的 Action.State 就为 ActionPending,就可以被处理到

2 如果是多个 slot 需要处理,那么是从最小的 slot 开始,一个一个接一个的顺序处理,直到没有需要处理的 slot,然后继续每过 10 秒就处理一遍

下边再分析 processSlotAction 的实现:

Redis实践系列丨Codis数据迁移原理与优化
Redis实践系列丨Codis数据迁移原理与优化
Redis实践系列丨Codis数据迁移原理与优化
Redis实践系列丨Codis数据迁移原理与优化

可以看出:

1 循环调用底层的 redisp.MigrateSlot 函数,从源 slot 向目的 slot 做一次迁移操作

2 每次调用 redis 的 slotsmgrttagslot 命令进行一次迁移,只迁移一个 key;迁移完成后暂停一个时间,再继续进行后续迁移操作;应该是为了避免大量的迁移操作,影响了业务对 redis 的读写

3 直到迁移返回个位为 0,即表明迁移完成

04 瓶颈分析

从上面的分析可以得出:

1 多个 slot 的迁移,是一个一个 slot 串行处理的

2 对一个 slot 的迁移,是逐个 key 进行串行处理的,每迁移一次需要从 codis-dashboard 向 redis-server 发一次迁移命令,每发出一个迁移命令只迁移一个 key

这个设计的好处是,迁移过程对客户业务的影响很小,但是也有一些明显的缺点:

1 迁移效率较低,如果数据量很大,那么迁移时间会很长

2 如果扩容较晚,有些节点已经较为满了,那么很可能因为迁移较慢而还没有处理到这些 slot,从而导致对这些节点的写入失败

由于扩容一般会有一定的提前量,且会选在业务低峰期进行,因此可以对该迁移方案进行优化,可以在不对业务访问造成太大的影响的前提下提高迁移效率。

Codis 代码优化

根据上面对迁移实现的分析,优化的思路为:

1 对多个 slot 进行迁移,尽量进行并行化处理

2 调用 redis-server 进行迁移时,可以一次迁移多个 key

01 Slot 迁移并行化

从代码实现的分析,有 2 个点可以选择:

1 Topom.ProcessSlotAction 中,封装 SlotActionPrepare 和 processSlotAction,然后启动多个线程 (goroutine),实现多个 slot 并行操作

2 Topom.StartDaemonRoutines 中,修改 ProcessSlotAction 的调用,启动多个线程 (goroutine)

最终处理代码简单化的考虑,选择了方案 2,同时考虑到如下几点:

1 节点资源的限制,为避免耗费太多资源,限制最多 10 个线程并发

2 由于一般多个 slot 会映射到同一个 redis-server,而这样的多个 slot 进行并行化处理不会有提升效果,反而会影响到客户业务的访问,因此在并行时选择 slot 时,要选择不在同一个 redis-server 的 slot,使迁移真正的并行化

如下优化代码,启动至多 10 个线程进行 slot 事件的处理。

Redis实践系列丨Codis数据迁移原理与优化

同时修改 SlotActionPrepare,选择一个状态为 Pending 且没有归属于同一个 redis-server 的 slot,进行处理。

Redis实践系列丨Codis数据迁移原理与优化
Redis实践系列丨Codis数据迁移原理与优化

02 Multikey 迁移

修改 redis-server 的迁移指令,支持一次迁移多个 key,为了灵活性,把迁移的个数从外部传入,代码比较显而易见,参考如下:

Redis实践系列丨Codis数据迁移原理与优化Redis实践系列丨Codis数据迁移原理与优化

Codis 迁移优化测试结果

经过验证,对于一个 64G 规模的集群,使用 redis-benchmark 写满数据,每个 key 的 value 长度为 32 字节,总共写入 341446298(3.4 亿)条数据,扩容到 128G,即对其中的 512 个 slot 进行迁移。最终测试结果为:

1 从 10:05:41 开始,到 10:34:50 结束,最终花费时间为 29 分钟

2 每个 slot 的迁移时间平均为 25 秒。比如 slot-64,从 10:05:41,到 10:06:07,花费 26 秒;slot-195, 从 10:05:42,到 10:06:08,花费 26 秒,slot-576, 从 10:05:48,到 10:06:13,花费 25 秒

因此,经过优化后迁移性能有极大的提升。当然当前的配置也是考虑到了尽量不影响客户的业务访问,一次迁移的数据量并不是最大化的,在某些情况下,可以修改配置,一次迁移更多的 key,可以更加快速的完成迁移。

本文转载自公众号中间件小哥(ID:huawei_kevin)。

原文链接:

https://mp.weixin.qq.com/s/kfHx8GKN0ZHYXO456boCnQ

评论

发布