写点什么

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

评论

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

super与this在成员变量,成员方法,构造方法方面的作用

Java 程序员 后端

VirtualBox虚拟机网络设置(四种方式),java高级程序员简历

Java 程序员 后端

Spring入门后半部分----JDBCTemplate和事务控制

Java 程序员 后端

Spring动态代理时是如何解决循环依赖的?为啥用三级缓存?

Java 程序员 后端

Spring源码解析(一)IOC,终于找到一个看得懂的JVM内存模型了

Java 程序员 后端

SpringCloud Alibaba 入坑(六),java开发程序员面试题

Java 程序员 后端

Spring中注解,java电话面试技巧

Java 程序员 后端

T-SQL——数据透视和逆透视,java架构师面试答案

Java 程序员 后端

Threadtear:一款多功能Java代码反混淆工具套件

Java 程序员 后端

VIVO一面竟然翻车,含泪整理了这些Java面经,看完我悟了

Java 程序员 后端

SpringCloudRPC调用核心原理:RxJava响应式编程框架,其他操作符

Java 程序员 后端

Spring源码学习~循环依赖(面试必问系列,学java基础的心得体会

Java 程序员 后端

windows下快速安装nginx 并配置 开机自启动,基础+底层+算法+数据库

Java 程序员 后端

Springboot过滤器和拦截器详解及使用场景,nginx原理和架构

Java 程序员 后端

Spring中的循环依赖及解决,springboot入门与实践PDF

Java 程序员 后端

SpringBoot基于微服务架构的前后端分离博客系统源码免费赠送(1)

Java 程序员 后端

Vue 脚手架结合 SpringBoot 构建前后端分离入门项目(实现增删改查)

Java 程序员 后端

Web开发基础:HTML,java面试没问技术问题

Java 程序员 后端

zookeeper之java API使用,zookeeper学习

Java 程序员 后端

Spring总结以及在面试中的一些问题,java面试笔试题代码

Java 程序员 后端

Spring经典面试题,帮你整理好了!,阿里P8大牛亲自教你

Java 程序员 后端

SpringBoot源码学习系列之嵌入式Servlet容器

Java 程序员 后端

Spring经典面试题,帮你整理好了!(1),一个星期4轮面试终拿下offer

Java 程序员 后端

YYDS,瞬间秒杀全网,这套Java面试笔记可以解决90

Java 程序员 后端

springboot入门教程,4000多页合集的计算机、网络、算法知识总结

Java 程序员 后端

SpringBoot基于微服务架构的前后端分离博客系统源码免费赠送

Java 程序员 后端

SpringMVC--请求参数绑定,spring菜鸟教程

Java 程序员 后端

Tomcat是如何运行的?整体架构又是怎样的?,java面试经验技巧

Java 程序员 后端

Tomcat服务器搭建及测试教程,java技术路线规划

Java 程序员 后端

SpringBoot整合SpringSecurity超详细入门教程

Java 程序员 后端

SpringBoot配置文件,springboot项目案例百度云

Java 程序员 后端

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