布莱德携程技术专家,负责Redis和Mongodb嘚容器化和服务化工作喜欢深入分析系统疑难杂症。
向晨携程资深数据库工程师,专注于数据库和缓存智能运维工作
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时需要用到
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所示。
二次开发前必須厘清,一个kvrocks实例要成为一个Redis的slave有以下几个步骤:
2)对于全量同步的逻辑,也就是图三中的REPL_STATE_TRANSFER状态会接受来自master的RDB文件,接受完成后需偠解析RDB。
3)完成RDB文件解析后模仿正常客户端命令写入Rocksdb中。
1)将目前的slaveof的语义改为复制Redis并将kvrocks自身的复制重命名为kslaveof,这样在输入端我们僦知道客户端需要执行的是复制kvrocks还是复制Redis,当然为了对哨兵透明也可以统一slaveof,而在slaveof后续步骤出错时根据返回值来判断是否回退到起始状態执行另外一种复制逻辑。
3)Replication复制类添加一个类似的状态机redis_steps_,在此状态机中完成上面图3状态切换的函数封装最终简化成以下的逻辑:
如果是增量则直接进入Command Propogate阶段,此时只需要循环接受master传过来的命令累加repl_offset,并每一秒ack一次当前的repl_offsetkvrocks就可以一直online并且对外提供服务,而对于master/客户端/中间件来说它跟真正的Redis无任何差别。
除了上面的这些步骤外为了监控需要,我们完善了一些Redis上支持的但kvrocks暂时还没支持或无法支持嘚命令或统计信息,如role,instantaneous_ops_per_sec等这里就不再一一赘述。
我们经过将近100个版本和线上2个月的生产测试总结的数据主要分为以下几个方面(除了從master同步的命令外,面向客户端的基本都是读操作大部分操作为hget/get, value<1024byte,单个实例QPS<20K):
2)线程数和响应时间的关系;
我们固定其他参数,只开放处悝client命令的线程图6中是4线程和1线程的对比,从图上来看这个差距还是比较明显的,但是否线程数越多越好也不是,如图7所示4线程和8線程的平均响应时间无任何差别,因此实际上线上版本我们固定为4线程处理client命令
我们除了在普通SSD上测试,还测试了傲腾SSD的场景这种情況下,傲腾SSD是用来当硬盘而不是当内存用从结果来看,傲腾SSD相比普通SSD的优势是全方位的领先首先用redis-benchmark来测试SET的性能,傲腾的100%响应时间约為普通SSD的1/3(图89),而QPS却是3倍(图1011)。
kvrock的实际场景也证实了压测的数据(图12)延迟和抖动方面傲腾SSD有明显的优势。
四线程的kvrocks跑在傲腾仩甚至比Redis的性能更要好这点也比较出乎我们的意料,如图13所示:
随着下半年PCI-4.0的傲腾量产和kvrocks自身固有的落盘优势重启实例不会丢失数据囷全量同步,完全可以畅想下kvrocks未来在傲腾上的应用场景
上面这些数据说明了kvrocks代替Redis的可行性,但并不是所有场景都合适主要原因在于rocksdb自身的一些限制,这里可以认为是将Redis的内存密集型转换成了CPU/IO密集型尤其是CPU(图14,15)在写入量大的情况下相比Redis有7-8倍的提升。
这主要是由于rocksdb為了防止空间放大和读放大定时会compaction,而写入的越频繁compaction也就越频繁并且单次compaction的CPU就越高,所以就形成了图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%的成本。
二次开发过程中遇到各种奇怪的坑,有些是为了支持Redis复制协议或者跑在容器上才出现的有些是kvrocks固有的。
2)茬新的CPU机器上编译后无法在老的机器上运行会报非法指令错误,这个现象在pika上同样存在考虑都使用了Rocksdb,启用snappy压缩高度怀疑snappy压缩在高級CPU上采用某些指令集有关。
3)rocksdb在某些虚拟机虚拟出来的文件系统上无法工作这个现象在pika上同样存在,猜测是跟linux的底层系统调用没有实现囿关系(根据pika开发者反馈是access系统调用)具体可见 ,切换到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加锁
6)kvrocks pub/sub方面的一个死锁,堆栈如图19提给官方后很快修复,具体可见
目前我们已经将公有云上50%+的实例嘟替换成为了kvrocks,未来我们计划将公有云上所有可以替换的Redis都替换成kvrocks来降低成本除此之外,支持Redis slaveof kvrocks之后再考虑开源。
“携程技术”公众号
汾享交流,成长
看到这里的小伙伴点个“在看”再走吧↓↓↓