限时领|《AI 百问百答》专栏课+实体书(包邮)! 了解详情
写点什么

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:272505

评论

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

年近而立,Java何去何从?

华为云开发者联盟

Java 开源 程序员 编程语言 Bugayenko Yegor

JavaScript中的正则表达式详解

华为云开发者联盟

Java 正则表达式 程序员 字符串 语法

SpringBoot系列(六):SpringBoot 数据库操作(集成MyBatis)

xcbeyond

Java 微服务 mybatis springboot

免费DDoS攻击测试工具大合集

陈磊@Criss

SpringBoot系列(五):SpringBoot 日志配置(logback)

xcbeyond

Java 微服务 springboot logback

智“营”时代,众盟科技荣膺“2020毕马威中国领先消费科技TOP50企业榜单”

人称T客

MySQL备份脚本,应该这么写

Simon

MySQL

实践总结:在 Java 中调用 Go 代码

写代码的西瓜

守护进程

书旅

php 进程 守护进程

如何从红蓝墨水中分离出红墨水?回声消除算法介绍

拍乐云Pano

音视频 WebRTC 回声消除 3A算法 音频技术

面试官:说下对cookie,session,Token的理解

Java小咖秀

Java 面试

一瓶可乐的自动售货机指令“旅程”

华为云开发者联盟

物联网 嵌入式 华为云 数据传输 无线通信

腾讯人均月薪7.5w,我这是又被平均了?

程序员生活志

腾讯 职场 薪资

分苹果

书旅

算法 LeetCode

分布式链路追踪Skywalking 存储模型设计

胡弦(关注公众号架构随笔录)

分布式 Skywalking 高性能 分布链路追踪

Maven的爱恨情仇

xcbeyond

Java maven

网站改版神秘公式,教你躲避改版陷阱

北柯

创业 网站 网站搭建 网站改版

终于知道Kafka为什么这么快了!

大头星

kafka 消息队列

Phalcon注解学习

Geek_4z9ami

php phalcon

MySQL explain 中的 rows 究竟是如何计算的?

架构精进之路

MySQL

libuv 异步模型之设计概览

Huayra

libuv 异步模型

应用研发平台特惠专场,助力企业加速数智化发展

移动研发平台EMAS

如何正确认识区块链?

CECBC

区块链价值 区块链应用

统一软件开发过程(RUP)的概念和方法

雯雯写代码

Junit执行单元测试用例成功,mvn test却失败的问题和解决方法

陈磊@Criss

面试造火箭,看下这些大厂原题

前端有的玩

Java 面试 大前端

AI能写浙江高考满分作文了!在线满分作文生成器,一键圆你满分梦

程序员生活志

AI

PHP之闭包函数

书旅

面向对象 闭包 函数

MySQL中timestamp和datetime,你用的对么?

xcbeyond

MySQL 数据库 后端

英特尔首席架构师Raja:一个“百亿亿次级计算能力惠及每个人”的时代正在到来

最新动态

如何写出完美的接口:接口规范定义、接口管理工具推荐

xcbeyond

Java 架构 接口规范

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