写点什么

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

  • 2019-10-23
  • 本文字数:3657 字

    阅读完需:约 12 分钟

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 版本。


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 的状态会发生变化,过程为:



也可以触发 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)生成迁移操作;



上面的代码的逻辑是:


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


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



生成迁移计划:


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


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



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

02 StartDaemonRoutines


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

03 ProcessSlotAction 实现分析

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




从上面代码可以看出:


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


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


下边再分析 processSlotAction 的实现:






可以看出:


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 事件的处理。



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



02 Multikey 迁移

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



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


2019-10-23 10:272417

评论

发布
暂无评论
发现更多内容

建木持续集成平台v2.1.1发布

Jianmu

DevOps CI/CD 开源软件

Kyligence 韩卿:开源改变了人类生产软件的方式 | 大话开源Vol.10

OpenTEKr

大话开源

SphereEx 潘娟:玩开源,我们就要秀出别样 My Way 来构建活力生态 | 大话开源Vol.11

OpenTEKr

大话开源

最好的 6 个免费天气 API 接口对比测评

蒋川

API 天气api

一个cpp协程库的前世今生(三)cocpp的核心框架结构

SkyFire

c++ cocpp

LabVIEW图像分割算法(基础篇—6)

不脱发的程序猿

机器视觉 图像处理 LabVIEW 图像分割算法

向未来飞驰:武汉推开了AI产业化和产业AI化的三重门

脑极体

2021阅读总结

俞凡

阅读

『征文精选』ShardingSphere-Proxy:Base 事务基于 Seata 验证

SphereEx

数据库 架构 开源社区 ShardingSphere SphereEx

一个cpp协程库的前世今生(四)协程上下文ctx

SkyFire

c++ cocpp

Java 数据持久化系列之 HikariCP (一)

程序员历小冰

持久化 HikariCP 28天写作 12月日更

一个cpp协程库的前世今生(五)协程执行环境env

SkyFire

c++ cocpp

如何将List<Integer>转换为int[]数组

liuzhen007

Java 28天写作 12月日更

KubeEdge 王泽锋:只有代码没有生命力,凝聚开发者的社区才能活力无限Vol.8

OpenTEKr

大话开源

回顾 2021,拥抱 2022~

阿策小和尚

盘点2021

2022 让我们登上更大的舞台

坚果

28天写作 12月日更 2021年终总结 盘点 2021

Hoo虎符研究院 | 币海寻珠最新一期的DAO生态

区块链前沿News

DAO Hoo 虎符交易所 虎符研究院

祝大家元旦快乐,分享一些知识演讲

石云升

28天写作 12月日更

61 K8S之日志系统部署

穿过生命散发芬芳

k8s 28天写作 12月日更

盘点 2021|一个 SAP 成都研究院开发工程师的2021年度总结:既没有厚积,也未能薄发

汪子熙

程序员 28天写作 12月日更 盘点2021 盘点 2021

我选择了VSCode

xcbeyond

vscode 28天写作 12月日更

TAOS Data 陶建辉:一个开源项目要成功,最关键的是定位 | 大话开源Vol.12

OpenTEKr

大话开源

Postman 使用教程 - 手把手教你 API 接口测试

蒋川

Postman 接口测试

.NET6新东西--Logging Source Generator

喵叔

28天写作 12月日更

关于内核堆溢出漏洞的分析

网络安全学海

黑客 网络安全 信息安全 安全漏洞 渗透测试·

元宇宙很好,但VR开发者不准备停留在这里

脑极体

VMware 任道远:中国的开源生态还处在萌芽发展的青春期,需要多元力量和全球化协作 I OpenTEKr 大话开源 Vol.9

OpenTEKr

大话开源

瞰见|从电厂螺蛳里看 Elastic 与亚马逊云的恩怨情仇

OpenTEKr

狄安瞰源

如何打造一个云原生背景下的可观测平台?

淡泊明志、宁静致远

瞰见|即将上市的云明星 HashiCorp 走过的开源之路

OpenTEKr

开源 狄安瞰源

瞰见 | 美股新贵Confluent背后的卡夫卡,不是那个魔幻小说家

OpenTEKr

狄安瞰源

Redis实践系列丨Codis数据迁移原理与优化_文化 & 方法_夏光_InfoQ精选文章