写点什么

节约 60%-80% 成本,携程 kvrocks (Redis On SSD) 实践

2020 年 6 月 17 日

节约60%-80%成本,携程 kvrocks (Redis On SSD) 实践

一、背景

2019 年,随着携程 G2 战略和国际化的推进,有一些大容量的 Redis 集群需要出海对海外客户提供服务,相比私有云的单 GB 成本,公有云上的 Redis 要贵 10 倍左右,这迫切需要我们寻找一种能替代 Redis 的廉价方案部署在海外,我们开始着手调研 Redis On SSD 的可行性。


二、调研和选型

携程大部分 Redis 数据是通过 xpipe 同步到海外(图 1),而 xpipe 是实现了 Redis 复制协议的伪 slave, 为了让海外基于 SSD 存储的替代 Redis 的方案能够顺利落地,需要兼容 Redis 的协议,Redis 的协议基本分为两大块:


1)面向客户端的协议,如 ping/set/get 等客户端的命令。


2)复制协议,slave 同步 master 时需要用到。



图 1


我们调研了目前绝大部分 Redis 的替代方案,如 Redislabs 的 Redis On Flash(https://redislabs.com/lp/redis-enterprise-flash/), 360 的 pika(https://github.com/Qihoo360/pika)和美图的 kvrocks(https://github.com/bitleak/kvrocks),其中 Redis On Flash 是商业化的产品,无开源代码,pika 市面上使用的公司比较多,但缺点也很明显:


1)面向客户端的协议是 Redis 的二进制协议,而面向复制的却是基于 google 的 protobuf 格式,语义层面有割裂感。


2)复制是基于 Rsync 的多进程模式,这种复制模式比较重,出现问题也不好定位。


3)代码风格比较乱,此外网络类库也用的是 360 自家的 pink,二次开发比较困难。


而 kvrocks 很好的避免了 pika 的这些问题,语义和复制上与 Redis 原生的更加接近,缺点是刚刚开源,几乎无任何公司来使用 kvrocks,经过权衡,我们发现 kvrocks 的整体框架和代码比较好把握,我们还是决定基于 kvrocks 做二次开发。


为什么要二次开发呢?因为面向客户端的协议 pika/kvrocks 可以达到 90%以上的 Redis 兼容,但复制协议都不兼容,究其原因在于,无论 pika 还是 kvrocks,其底层的存储引擎都是 rocksdb,而 rocksdb 是基于磁盘的 KV 存储方案,数据已经落盘成文件的无需再像 Redis 那样复制时将 Master 内存的数据保存到文件中再发送到 slave 端,直接传输文件更高效。为了让业务和中间件少改动,我们基于 kvrocks 进行二次开发,用来支持 Redis 的 SYNC/PSYNC 协议,也就等于支持了 xpipe,最终的同步模式也就如图 2 所示。



图 2


三、二次开发

二次开发前,必须厘清,一个 kvrocks 实例要成为一个 Redis 的 slave,有以下几个步骤:


1)执行 slaveof 后的 Redis slave 状态机模拟,如下图 3 所示。


2)对于全量同步的逻辑,也就是图三中的 REPL_STATE_TRANSFER 状态,会接受来自 master 的 RDB 文件,接受完成后,需要解析 RDB。


3)完成 RDB 文件解析后,模仿正常客户端命令写入 Rocksdb 中。


4) 进入 CommandPropogate 阶段后,死循环接受 Master 传来的增量命令,每一秒 ACK 一次当前的 offset。



图 3


此外,还需要区分 kvrocks 复制 kvrocks 和 kvrocks 复制 Redis,得益于 kvrocks 良好的代码风格,其 Replication 模块已经实现了一个 kvrocks 复制 kvrocks 的状态机,其中 psync_steps_和 fullsync_steps_表示 kvrocks 复制 kvrocks 的增量同步和全量同步,这样我们的思路就很清晰了:


1)将目前的 slaveof 的语义改为复制 Redis,并将 kvrocks 自身的复制重命名为 kslaveof,这样在输入端,我们就知道客户端需要执行的是复制 kvrocks 还是复制 Redis,当然为了对哨兵透明,也可以统一 slaveof,而在 slaveof 后续步骤出错时根据返回值来判断是否回退到起始状态,执行另外一种复制逻辑。


2)kvrocks 的 server 类添加 Redis 复制的一些特有标识,比如 repl_offset,repl_id 等,并添加一个字段 slave_mode 来区分当前是作为 Redis/kvrocks 的 slave。


3)Replication 复制类添加一个类似的状态机 redis_steps_,在此状态机中完成上面图 3 状态切换的函数封装。最终简化成以下的逻辑:



图 4


完成了上面的流程,我们就获得了一个同步 Redis 的 kvrocks slave,解析 master 传播过来的 RDB 文件,对于每个 key,遍历其是否过期,然后根据类型 (string,hash,set,zset) 选择对应的插入命令,将其导入到 rocksdb 中。RDB 解析完成后,会进入 Command Propogate 阶段,而对于 PSYNC 的支持,只需要保存 master 的 repli_id 和 offset,在传送 RDB 之前根据 master 返回是否是+CONTINUE 来区分是增量同步还是全量同步。


如果是增量则直接进入 Command Propogate 阶段,此时只需要循环接受 master 传过来的命令,累加 repl_offset,并每一秒 ack 一次当前的 repl_offset,kvrocks 就可以一直 online 并且对外提供服务,而对于 master/客户端/中间件来说,它跟真正的 Redis 无任何差别。


除了上面的这些步骤外,为了监控需要,我们完善了一些 Redis 上支持的,但 kvrocks 暂时还没支持或无法支持的命令或统计信息,如 role,instantaneous_ops_per_sec 等,这里就不再一一赘述。


3.1 数据

我们经过将近 100 个版本和线上 2 个月的生产测试,总结的数据主要分为以下几个方面(除了从 master 同步的命令外,面向客户端的基本都是读操作,大部分操作为 hget/get, value<1024byte,单个实例 QPS<20K):


1)kvrocks 和普通 Redis 的区别;


2)线程数和响应时间的关系;


3)kvrocks 跑在傲腾 SSD 和普通 SSD 上的区别;


4)kvrocks 适用场景;


5)成本节约多少?


kvrocks VS Redis



图 5


从图 5 上我们可以看到,基于 SSD 的 kvrocks 和基于内存的 Redis 性能没有明显差别,而且这是基于 rocksdb 的配置比较低的情况(4 线程处理 client 命令,1 线程复制,metadata/subkey 的 block_cache_size 为 128M,write_buffer_size 64M,wal_size 2G)。


线程数和响应时间



图 6


我们固定其他参数,只开放处理 client 命令的线程,图 6 中是 4 线程和 1 线程的对比,从图上来看,这个差距还是比较明显的,但是否线程数越多越好?也不是,如图 7 所示,4 线程和 8 线程的平均响应时间无任何差别,因此实际上线上版本我们固定为 4 线程处理 client 命令。



图 7


傲腾 SSD VS 普通 SSD


我们除了在普通 SSD 上测试,还测试了傲腾 SSD 的场景,这种情况下,傲腾 SSD 是用来当硬盘而不是当内存用。从结果来看,傲腾 SSD 相比普通 SSD 的优势是全方位的领先,首先用 redis-benchmark 来测试 SET 的性能,傲腾的 100%响应时间约为普通 SSD 的 1/3(图 8,9),而 QPS 却是 3 倍(图 10,11)。



图 8



图 9



图 10



图 11


kvrock 的实际场景也证实了压测的数据(图 12),延迟和抖动方面傲腾 SSD 有明显的优势。



图 12


四线程的 kvrocks 跑在傲腾上甚至比 Redis 的性能更要好,这点也比较出乎我们的意料,如图 13 所示:



图 13


随着下半年 PCI-4.0 的傲腾量产和 kvrocks 自身固有的落盘优势,重启实例不会丢失数据和全量同步,完全可以畅想下 kvrocks 未来在傲腾上的应用场景。


kvrocks 适用场景


上面这些数据说明了 kvrocks 代替 Redis 的可行性,但并不是所有场景都合适,主要原因在于 rocksdb 自身的一些限制,这里可以认为是将 Redis 的内存密集型转换成了 CPU/IO 密集型,尤其是 CPU(图 14,15),在写入量大的情况下相比 Redis 有 7-8 倍的提升。


这主要是由于 rocksdb 为了防止空间放大和读放大,定时会 compaction,而写入的越频繁,compaction 也就越频繁并且单次 compaction 的 CPU 就越高,所以就形成了图 15 这种脉冲式的波峰。



图 14



图 15


从我们测试的经验来看,单个实例 QPS<1 万情况下,用 kvrocks 替换 Redis 是比较合适的,如果 QPS 过高,会导致 CPU 过高,我们甚至无法选择到合适的宿主机来存放这种类型的实例,因为这时候 CPU 内存的配比是 2:1 或者更高的关系。


成本能节约多少


这实际上需要在 CPU/内存/磁盘中做各种 tradeoff,我们需要在保证响应延迟的情况下尽可能地降低 CPU/内存的使用率。以我们线上某实际的集群为例,经过 rocksdb 各种参数调整后,该集群单个 Redis 实例所用内存为 6G,而这些数据全部跑在 kvrocks 中,大概 CPU 为 100%,内存为 1G 左右如图 16,17 所示。按这样的关系换算我们之前选用的 Redis 宿主机机型和计划选用的 kvrocks 宿主机机型,用 kvrocks 大概能将成本节约 63%,并且实例越大,节省越多,整体能节约 60-80%的成本。



图 16



图 17


四、一些坑

二次开发过程中,遇到各种奇怪的坑,有些是为了支持 Redis 复制协议或者跑在容器上才出现的,有些是 kvrocks 固有的。


1)编译时 jemalloc 必须指定–with-jemalloc-prefix=je_,否则无法在容器中运行 ,具体可见https://github.com/bitleak/kvrocks/issues/54


2)在新的 CPU 机器上编译后无法在老的机器上运行,会报非法指令错误,这个现象在 pika 上同样存在,考虑都使用了 Rocksdb,启用 snappy 压缩,高度怀疑 snappy 压缩在高级 CPU 上采用某些指令集有关。


3)rocksdb 在某些虚拟机虚拟出来的文件系统上无法工作,这个现象在 pika 上同样存在,猜测是跟 linux 的底层系统调用没有实现有关系(根据 pika 开发者反馈是 access 系统调用),具体可见https://github.com/bitleak/kvrocks/issues/56 ,切换到 xfs 文件系统解决。


4)对于 setbit 操作,Redis 认为 value 是个 string,但 kvrocks 认为是个 bitmap,所以如果一个 setbit 操作的 string 在全量同步阶段被同步到 kvrocks 中,再有命令传播的 setbit/getbit 操作的话,kvrocks 会报类型不匹配的错误,该问题已经提交给官方,官方会试图将这两种类型统一。而作为暂时的解决方案,setbit 操作的 key 加上指定的前缀比如"bit_" ,这样程序就认识到此 string 为 bitmap 类型,而选择对应的数据导入方式,而如果 kvrocks 复制 kvrocks 的话则不会有这种问题。


5)兼容 Redis 的时发现有个断错误,多抓取 core 文件发现是二次开发的代码导致,多线程访问了 libevent 的同一个 evbuffer(图 18),同时读写操作同一个 evbuffer 会导致无法预期的错误,解决方法是 evbuffer 加锁。



图 18


6)kvrocks pub/sub 方面的一个死锁,堆栈如图 19,提给官方后很快修复,具体可见 https://github.com/bitleak/kvrocks/issues/68



图 19


五、未来展望

目前我们已经将公有云上 50%+的实例都替换成为了 kvrocks,未来我们计划将公有云上所有可以替换的 Redis 都替换成 kvrocks 来降低成本,除此之外,支持 Redis slaveof kvrocks,之后再考虑开源。


作者介绍


布莱德,携程技术专家,负责 Redis 和 Mongodb 的容器化和服务化工作,喜欢深入分析系统疑难杂症。


向晨,携程资深数据库工程师,专注于数据库和缓存智能运维工作。


本文转载自公众号携程技术(ID:ctriptech)。


原文链接


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


2020 年 6 月 17 日 10:001810

评论

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

多功能工具箱Quicker+笔记软件flomo,竟然还能擦出这样的火花?

彭宏豪95

效率 工具软件 笔记 工具分享 4月日更

区块链数据共享平台—追踪、溯源、可信

电微13828808271

区块链+

正点原子:STM32F103(战舰)、STM32F407(探索者)、STM32F103(MINI)原理图和PCB

不脱发的程序猿

开发板 stm32 硬件设计 四月日更 正点原子

Github限时开放!阿里内部强推微服务容器化参考指南我粉了

Java王路飞

Java 程序员 面试 微服务 运维

团队协作中,如何写出让同事赞不绝口的代码

有道技术团队

代码规范

SumSwap节点预售关注度飙升而Uniswap V3版本却备受争议

币圈资讯

重磅功能!博睿数据APM助企业从容应对云原生架构演进

博睿数据

应用性能监控产品 Bonree Server 博睿数据 bonree

区块链农产品质量安全溯源,保证农产品品质

13530558032

kubectl top node报错及解决

箭上有毒

华云大咖说 | 华云数据与海量数据携手共建国产云生态

华云数据

智慧平安小区系统开发,智慧社区综合信息服务平台建设方案

WX13823153201

GitHub爆火!银四巨作:拼多多/蚂蚁/百度面经分享

比伯

Java 架构 面试 程序人生 技术宅

Golang 字符串分组

一代咩神

golang

区块链电子合同--助推合同数字化管理

13530558032

火山引擎 Redis 云原生实践

火山引擎开发者社区

云原生 redis cluster

技术分享第二讲报名!

神策技术社区

大数据 活动 报名 神策

面试笔记(一)事务连环炮

U+2647

分布式事务 事务隔离级别 事务 四月日更

Javascript执行机制-任务队列

Sakura

Python实现植物大战僵尸

不脱发的程序猿

Python GitHub 游戏开发 开源项目 四月日更

基于Vue和Quasar的前端SPA项目crudapi后台管理系统实战之布局菜单嵌套路由(三)

crudapi

Vue crud crudapi quasar 路由

EFT【阿凡提】等级规则、收益、排线方法与EFTalk十大关键点

币圈那点事

【LeetCode】寻找旋转排序数组中的最小值Java题解

HQ数字卡

算法 LeetCode 4月日更

量化交易系统开发app,量化马丁策略交易平台搭建

WX13823153201

阿里最强 Python 自动化工具开源了!

星安果

Python 开源 自动化 阿里

关于机器学习的十大常见问题

澳鹏Appen

人工智能 机器学习 深度学习 数据 人工智能大数据

语音聊天室 anyHouse 使用手册

anyRTC开发者

ios android 音视频 WebRTC RTC

远离风险源!户外投放没烦恼

󠀛Ferry

四月日更

以太坊杀手?NA公链(Nirvana)Chain忠于挑战自己NAC公链

区块链第一资讯

智慧平安社区建设,创建“三零平安社区”

13530558032

Linux cat 命令

一个大红包

4月日更

“区块链+电子处方”,医疗跟更健康

电微13828808271

节约60%-80%成本,携程 kvrocks (Redis On SSD) 实践-InfoQ