AICon日程100%就绪,9折倒计时最后一周 了解详情
写点什么

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

  • 2020-06-17
  • 本文字数:4252 字

    阅读完需:约 14 分钟

节约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-06-17 10:005645

评论

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

(28DW-S8-Day22) 《流程型组织》学习笔记:金字塔

mtfelix

28天写作

数据采集之Flume采集及点击流模型详解

五分钟学大数据

大数据 28天写作 3月日更 flume 点击流模型

Vue3源码 | 深入理解响应式系统下篇-effect

梁龙先森

源码分析 大前端 Vue3

【笔记】第七周 第 2 课

Geek_娴子

如何使用标准稳压器输出几百毫伏极低直流电压?

不脱发的程序猿

28天写作 电路设计 3月日更 电源电路 标准稳压器

《精通比特币》学习笔记(第九章)

棉花糖

区块链 学习 3月日更

Zookeeper.02 - API

insight

zookeeper 3月日更

大作业(一)

Binary

架构师是什么?

ES_her0

28天写作 3月日更

《精通比特币》学习笔记(第十章)

棉花糖

区块链 学习 3月日更

爱赢才会拼——目标梯度效应

Justin

心理学 28天写作 游戏设计

滚雪球学 Python 之作用域下的 global 和 nonlocal 关键字

梦想橡皮擦

28天写作 3月日更

通过使用终端(iTerm2&Oh my ZSH)来提高您的生产率 John 易筋 ARTS 打卡 Week 41

John(易筋)

ARTS 打卡计划 iterm2 myzsh

我的Java转Go之路

roseduan

Java 转行 Go web Go 语言

Elasticsearch 写入流程 Making Changes Persistent

escray

elastic 28天写作 死磕Elasticsearch 60天通过Elastic认证考试 3月日更

简述软件不可用性及解决办法

跳蚤

Wireshark数据包分析学习笔记Day11

穿过生命散发芬芳

Wireshark 数据包分析 3月日更

打造技术人创作利器:Typora+PicGo+Github+Jsdelivr 组合拳

Viktor

创作 GitHub Pages 工具软件

大作业(二)

Binary

区块链+版权:NFT出圈,善用技术能否“破心中贼”?

CECBC

数字技术

多应用集中落地,四川区块链产业爆发增长

CECBC

区块链

雄岸科技区块链布局价值待考

CECBC

区块链 科技

区块链+金融落地应用详解

CECBC

金融

基于 SparkMLlib 智能课堂教学评价系统-系统设计(三)

大数据技术指南

大数据 spark 智能时代 28天写作 3月日更

场景化面试:Kafka 为何有如此高的吞吐量和性能

面试官问

kafka 零拷贝 PageCache

ARTS - week 2

steve_lee

HR14问

我是程序员小贱

面试 3月日更

软件工程任务排期方法

steve_lee

「架构师训练营 4 期」 第十周 - 001&2

凯迪

架构师训练营 4 期

GO GC知识点整理

非晓为骁

垃圾回收 GC算法 Go 语言

又一款Nginx 管理可视化神器!通过界面完成配置监控,一条龙!

Java小咖秀

nginx 运维 后端

节约60%-80%成本,携程 kvrocks (Redis On SSD) 实践_开源_布莱德_InfoQ精选文章