MyRocks是完全兼容MySQL的一个关系型数据库系统,由Facebook于2016年下半年开源,网易杭研数据库内核团队在2018年初引入,在网易云音乐某些业务场景落地使用,取得了较好的效果,在这之后落地了更多地业务场景。
MyRocks是Facebook数据库工程团队将MySQL的默认存储引擎InnoDB(基于B+树)替换为自家的RocksDB(基于LSM树)后发展而来的MySQL分支版本,其继承了MySQL的SQL语法,数据高可用框架和配套工具。
MyRocks具有较高的普适性,对于MySQL用户来说,将MySQL替换为MyRocks是透明的,在使用方面并没有差别。最大的不同是DML操作(数据操作语句,如增、删、改)性能相比MySQL有明显优势;同样的数据量,所需的存储空间明显变少。
目前MyRocks在云音乐内使用,业务场景包括云音乐实时推荐、离线推荐、历史听歌记录、P0核心库延迟从库和云计算NOS系统内部数据管理等等,累计节省了百万级的成本开销。
MyRocks是MySQL和RocksDB的结合,MySQL是目前最流行的开源关系型数据库系统,RocksDB是Facebook在google的LevelDB基础上改进的KV存储引擎。
RocksDB基于Log Structured Merge Tree(LSM树)存储数据,下面简单介绍下RocksDB。
如上图所示。在RocksDB中,LSM可有多层,图中为4层(L0~L3),每一层都会独立进行Compaction操作;可以为每个用户数据表/索引创建一个列族(CF),每个CF都是一颗LSM树;每颗LSM树都会有数个Write Buffer(MemTable),用于缓存已经提交的事务数据;事务提交时会将WAL(Write Ahead Log)持久化到全局的日志文件中。
每个事务都有一个writebatch对象,用于缓存该事务在提交前修改的所有数据。在事务提交时,其修改的数据先写入WAL日志buffer,根据配置参数选择是否持久化。然后将修改的数据有序写入到Active MemTable中。当Active MemTable达到阈值后会变为Immutable,再新产生一个Active MemTable。
数据写入MemTable后就意味着事务已经提交。数据的持久化和Compaction都是异步进行的。当Immutable MemTable个数达到所设置的参数阈值后,会被回刷成L0 SST文件。在L0文件个数达到阈值后,合并到L1上并依次往下刷。RocksDB中可以配置多个线程用于对每层数据文件进行Compaction。
由于其优秀的特点,目前已经被集成到MySQL(即MyRocks)、MongoDB(即MongoRocks)、TiDB/TiKV和Nebula等关系型数据库、文档数据库、分布式KV、NewSQL和图数据库中,成为实际上的KV存储引擎标准。
MyRocks继承了MySQL优秀的基因,包括完全兼容的MySQL SQL语法、行级锁粒度、读已提交(RC)和可重复读(RR)两种事务隔离级别、多版本并发控制(MVCC,Multi-Version ConCurrent Control)、成熟的主从复制框架和完善的配套工具等功能和特性。
核心竞争力
由于引入RocksDB,MyRocks具备比MySQL(相比基于B+树的InnoDB)更多优点,如更快的写入速度,更小的存储空间,更高的压缩效率等;这是MyRocks的核心竞争力,下面简单对比说明。
InnoDB是典型的B/B+树存取方式,即使是顺序插入场景,也会在一个page还未完全填满时触发分裂,变为2个半空的page,也就是说InnoDB的page一定存在页内碎片。
而RocksDB由于是追加(Append)方式,所以都是文件顺序写行为;MemTable从内存 Flush到磁盘后成为独立的sst文件,不同的sst文件间的Merge操作也是顺序读写,整个过程均不会产生内部碎片。
即使在非压缩模式下,RocksDB记录也进行了前缀压缩/编码,默认每16条记录才有一条是完整的,节省了后续15条记录的共同前缀所需的存储空间。
此外,RocksDB每个索引占用7+1 bytes(seq id + flag)的元数据开销,而InnoDB每记录6+7 bytes (trx_id + undo ptr)空间开销,似乎在多个索引的场景下RocksDB更加费空间,但Ln SST文件seq id可置0,经过压缩等操作后绝大部分元数据开销是可节省的,只需占用1byte,因此RocksDB的元数据开销也比InnoDB更省。
在压缩效率方面,也是RocksDB更占据优势。InnoDB压缩以page/block为单位来独立保持压缩后的数据,而文件系统是需要块对齐的,一般都是4KB,这就意味着一个16KB的页,即使压缩为5KB,也需要使用2个文件block,即8K保存。其中的3KB空间填空。
RocksDB很好得解决了这个问题。每个block压缩后,先组合成一个SST文件,保存时只需要SST文件对齐到4KB即可。
不足
在性能上,MyRocks相比InnoDB不足之处在于查询性能(主要是范围查询),在这方面,MyRocks通过使用布隆过滤器(Bloom Filter)进行了查询优化。
相对来说,MyRocks作为一个年轻的开源项目,相比MySQL/InnoDB在成熟度上和功能完整度上有所欠缺,包括XA事务支持度不够、在线DDL性能不足且内存消耗过大、支持的索引类型偏少(无地理位置索引、全文索引等)等。此外,Bug数也更多。
由于MyRocks完全兼容MySQL,因此,对于一般的,比如读写压力都不大、只需关系型数据库基本功能的业务场景下,MyRocks是完全可以替代MySQL的,但这不是MyRocks项目的存在意义,因为带来不了多少价值。
MyRocks应该用在相比现有方案能够明显产生更多价值的场景,下面列举MyRocks典型的应用场景,并介绍在部分场景上的使用效果。
这类场景典型的特点是数据量大,此外可能数据增长也很快,也可能有过期删除的需求等,这类场景包括用户行为,动态等。我们曾与DBA对云音乐和考拉等业务的MySQL实例进行应用场景分析,发现这类场景的MySQL实例占有不小的比率。
MyRocks由于具备更高地压缩比,可以极大缩小数据的存储空间。降低这部分业务的存储开销。而且MyRocks支持数据TTL(Time To Live)特性,可以设定数据过期时间,更加方便进行数据生命周期管理。
目前云音乐有多个这类业务场景使用了MyRocks,这里拿云音乐的用户历史听歌记录为例。
在没替换为MyRocks前,历史听歌记录为一个下挂有16个MySQL高可用实例的DDB集群。数据采用InnoDB压缩表形式存储,每个节点数据约1TB,整个集群共32TB,如上图所示。下图为替换为MyRocks后的情况,设置RocksDB的压缩算法为Snappy,单个节点的数据量降为322GB,节省了700GB。整个集群可省下20TB SSD盘,节省了超过2/3的存储成本;由于计算资源利用率低,存储缩小后,可提高物理服务器的MyRocks实例部署密度,进一步节省计算资源;同时数据增长速度也得到控制,集群扩容周期变长。
这类场景的特点是数据量不小,对写性能要求超过了MySQL/InnoDB的能力,且对读的延迟和平稳性有较高要求。如果仅需高写入能力,那么可选择分布式KV系统,比如HBase,其提供了不弱于MyRocks的写入能力,且扩展性更好。但若对读性能有较高要求,那么选择MyRocks会更好,因为基于C++实现的MyRocks没有Java的GC等问题,性能更加平稳。
如果是数据量较小,且对读写均有较高要求的场景,可采用redis等缓存方案。但若业务场景的数据量较大,使用缓存的方案成本就会过高。
这类场景有推荐类、业务流水和日志类等等。在网易公司内部各部门均广泛存在类似的场景,目前云音乐的实时推荐、离线推荐等,NOS内部数据反查服务等均已使用MyRocks,这里举云音乐实时推荐为例。
在本案例中主要还是关心成本。因为数据量比较大,需要大量的内存,而且随着推荐的实时化改进和推荐场景的增多,内存使用成本急剧增加,甚至可能出现内存采购来不及的情况。
显然,单个MyRocks肯定扛不住全部的压力,于是仍采用DDB+MyRocks的方式将压力拆分到多个实例上。但落到MyRocks的写入压力仍较大,虽MyRocks主库能够扛住,但由于MySQL主从复制跟不上,所以MyRocks从库延迟不断增加。由于推荐类业务对数据一致性有一定的容忍度,所以,最终落地的方案是业务层双写2个MyRocks而不是MyRocks层做主从复制。下图为当前的部署架构。
图中分左右两部分,左边为8个MyRocks节点组成的DDB1集群,右边也是8个MyRocks组成的DDB2集群,推荐算法逻辑相关的单元用Flink来表示。算法单元从DDB1读取上一周期的推荐数据,跟当前实时变化的新数据相结合计算出本周期的推荐数据,然后将其分别写入DDB1和DDB2。推荐业务使用方从DDB2上读取所需的推荐数据。
上图为经过参数调优后的MyRocks节点性能表现,DDB1上的节点读写QPS/TPS达到1.5w/s,DDB2上的业务读QPS达到0.5w/s,读延迟大部分时间均在2ms以下,平稳度高,波动小。
总的来说,实时推荐通过将Redis替换为MyRocks集群,在性能上扛住了业务负载的情况下有效降低了所需投入的硬件成本。
依托高写入性能和低存储开销的特点,MyRocks还可作为MySQL高可用实例的低成本从库,比如防止数据误删的延迟从库(比线上数据延迟n个小时),用于数据导入的专用从库等。目前云音乐的歌单、用户和曲库等所有P0级的MySQL核心库,均建立了延迟从库,这些节点都部署在同一个物理机服务器上,每个节点仅需5G内存,原数据量1/3以下的存储空间,如下图所示。基于MyRocks的延迟从库以非常低的成本换来了核心业务数据更高的安全保障。
除了上面所述的场景外,还有更多其他场景可以使用MyRocks,在此不一一展开说明。
当然,MyRocks在落地使用过程并不是一帆风顺的。在新的场景类型落地时遇到了不少问题,杭研的数据库内核团队和杭研DBA、业务开发一道为最终的推广使用做了至关重要的贡献,包括对MyRocks进行功能增强、BugFix、参数调优和使用经验输出等。下面举例介绍。
在使用过程中,我们发现、定位和解决了数十个大大小小的问题,对MyRocks进行优化增强来满足业务需求。先后发布了3个MyRocks内核版本,其中第一个版本将开源的MyRocks代码合入杭研MySQL分支InnoSQL中,后两个版本(v4a和v4b)均是对MyRocks的优化增强和BugFix。
这里要展开说的是我们为MyRocks增加了完整的XA事务处理能力,这是MyRocks想要在网易内部大规模使用所必备的能力。因为网易众多业务均使用DDB作为MySQL分库分表的解决方案,而DDB可能会大量产生XA事务。
开源版MyRocks仅支持执行XA事务,但不支持回放XA事务,因为XA事务PREPARE后对应Session不能执行除XA COMMIT或XA ROLLBACK外其他操作,这在主库是没有问题的,可以通过创建新的Session执行其他事务操作,但在Slave端,用于回放事务的worker线程(等同于主库的Session)是有限的。这种情况下,也就是说如果存在XA事务,那么就无法使用MyRocks高可用实例,其原因是MySQL的XA事务复制框架与存储引擎强相关。
MyRocks并没有适配这套框架,如在XA START和PREPARE时必须的detach/reattach操作等,杭研数据库内核团队参考InnoDB实现了MyRocks的XA事务相关API接口。
除了实现API接口外,由于目前MySQL官方仅有InnoDB一个支持事务的存储引擎,所以MySQL对多存储引擎混合的XA事务支持上有较大问题,最典型的问题是多种不同原因导致的内存泄露;此外,我们发现MyRocks的XA PREPARE并未进行WAL持久化,会导致mysqld crash后数据丢失。这些问题均通过源码级分析完成问题定位并得到了有效解决。
RocksDB有数百个配置参数,其中暴露到MySQL上的有100+个。这些参数影响了MyRocks内存使用、MyRocks性能和数据持久性等方方面面,需要根据不同的业务场景进行最优化配置。
MyRocks与内存相关的参数较多,如block cache、MemTable和CF相关配置等。设置过小可能影响写性能,设置过大可能导致OOM。
在云音乐历史听歌记录场景下,我们本来采用每个用户数据表一个CF的配置方案。由于每个CF均有独立的MemTable,会占据几百MB甚至数GB的内存空间,而该场景下每个MyRocks实例有较多数据表,出现因为MemTable占据内存过多而OOM的情况。所以需要合理规划CF个数,每个CF的MemTable总大小由于max_write_buffer_size,max_write_buffer_number*等4个参数共同决定,其大小会影响该CF的写性能,应根据业务场景进行合理配置。
除了参数调优,在内存使用方面,我们还进行了源码级优化,包括暴露更具体的MemTable内存使用现状,替换更高效的系统内存分配器等。
Compaction相关参数较多,对于写密集型业务,若配置不当会导致业务停写问题(write stall)。在云音乐实时推荐场景,我们通过合理调优Compaction参数来实现MyRocks读写性能平稳输出。
上图右上侧为未调优时的性能曲线,波动非常剧烈,最高1w+,最低仅2k+。这是由于写入压力过大频繁触发停写问题。
显然,进行参数调优时,如果参数能够在线调整,可以避免数据库服务重启导致业务受影响,所以我们通过代码优化尽可能增加了在线可调的参数个数。
一、 MyRocks拓展了MySQL应用领域,使其可以在要求更高性能的业务场景上使用;
二、 MyRocks减少了MySQL所需的存储空间,节省了业务的硬件投入成本;
三、 有大量MyRocks能够发挥优势的使用场景。
来源:网易工程师--温正湖
有任何问题欢迎留言交流~
整理总结不易,如果觉得这篇文章有意思的话,欢迎转发、收藏,给我一些鼓励~
有想看的内容或者建议,敬请留言!