图片空间是淘宝智能图片中心面向商家提供的免费图片存储管理服务,由于淘宝、天猫主站上累积的用户图片数据量非常大(想想淘宝/天猫的商家和消费者每天要上传多少图片!),并且增长量惊人,图片空间业务面临着非常巨大的存储空间和写入性能压力。尤其每年双11之前,商家大量更新商品库存保有单位SKU(Stock keeping Unit),此时数据会急剧增长。
淘宝/天猫每日新增大量商品、评论图片某年双十一前夕,当时阿里大部分数据库系统还使用的是InnoDB存储引擎,图片空间的研发同学梳理双十一线上风险时,咨询到DB磁盘及水位的容量是否足够,我们曾信誓旦旦地说:“没有问题,四个月前我们刚扩了一倍机器”。可是没过多久就被现实打脸了:不到5个月的时间,业务数据累积了过去6-7年的量,每日增量急剧上升,扩容的磁盘很快也将不够了。
最简单粗暴的方法当然是扩容,这样做风险最小,但却只能解决眼前的问题。以现在数据的膨胀速度,未来难免多次扩容。仅仅因为空间不足的问题,导致成本翻好几倍,这是难以接受的。另外一个方法是换引擎,当时阿里主打高性能低成本的自研存储引擎X-Engine(X-Engine: MySQL RDS的新存储引擎)刚刚成熟,相较于基于B+-Tree的存储引擎(例如InnoDB)数据页存在较多空间浪费,基于LSM-Tree的X-Engine数据完全紧凑排列,空间利用率更高。而紧凑排列的数据施以前缀压缩技术,空间使用进一步减少。
X-Engine的Data Block无需原地更新,可以方便使用通用压缩算法(zlib,zstd,snapy等)压缩。所有位于LSM-tree低层次的数据都会默认压缩。经过大量对比测试,X-Engine默认选用了ZSTD压缩算法,但同时也保留了对其他算法的支持。此外后台compaction会持续删除无效记录(LSM-Tree更新和删除都是写入新记录,旧版本记录不再被需要时,视为无效),持续释放冗余的空间。因为上述技术特点,X-Engine对存储空间的节省几乎到达了“变态”的程度,以至于当图片空间库的数据全部从InnoDB转移到X-Engine后,空间节省了7倍,如下图所示
为什么数据从InnoDB迁移至X-Engine后,取得了如此巨大的成本收益?
此外,由于图片空间是一个高频使用的应用,如果X-Engine的性能不满足要求,也无法落地。得益于LSM轻量化写机制,X-Engine写入操作本就是优势,何况还引入了group commit和事务处理流水线机制,大大增加了写入处理的并发度。读请求本是LSM的弱项,分层的结构和追加写产生的多版本数据,会增加读请求查询路径的长度,X-Engine为此做了大量的优化,诸如:多粒度Cache(memtable,Block Cache和Row Cache)、bloomfilter和range scan filter(Surf, SIGMOD'18)有效减少点查询和范围扫描的次数、异步I/O预取等,尽力把它打造成读写性能均衡,成本优势突出的存储引擎。关于X-Engine读写优化,可以参考这篇文章:X-Engine SIGMOD论文详解。
经过DBA和业务开发同学的验证,X-Engine的读写性能及延时完全满足业务需求。很快,淘宝图片空间库全部切换为X-Engine引擎,节省了大量的存储成本。
X-Engine分层存储的架构,特别适合具有如下业务负载特征的业务:
数据访问具有鲜明的时间特征。例如大部分读取及修改操作集中在最近写入的数据上,而历史数据较少被访问(例如淘宝交易库淘宝万亿级海量交易订单都存储在哪呢?)。X-Engine新写入的数据通过高效的内存索引缓存,访问性能极高,而较少访问的历史数据保存在磁盘,提供稍逊的读写性能。例如:X-Engine在淘宝交易库的应用。
实时历史库需求背景
在当今的数字化时代,随着业务的迅速发展,每天产生的数据量会是一个惊人的数量,数据库存储的成本将会越来越大,通常的做法是对历史数据做归档,即将长期不使用的数据迁移至以文件形式存储的廉价存储设备上,比如阿里云OSS或者阿里云数据库DBS服务。
然而在部分核心业务的应用场景下,针对几个月甚至几年前的“旧”数据依旧存在实时的,低频的查询甚至更新需求,比如淘宝/天猫的历史订单查询,企业级办公软件钉钉几年前的聊天信息查询,菜鸟海量物流的历史物流订单详情等。
• 如果这时从历史备份中还原后查询,那么查询时间将会是以天为单位,可接受度为0
• 如果将这些低频但实时的查询需求的历史数据与近期活跃存储在同一套分布式数据库集群下,那么又会带来以下两大挑战
实时历史库场景需求分析
通过上面的分析,不管是冷备份还是在线历史数据混合存储在同一张物理表上的方法都是不可取的,一般实时查询历史数据库的场景,一般需要有以下几个关键特性
X-Engine引擎介绍
X-Engine简介
X-Engine是阿里云数据库产品事业部自研的联机事务处理OLTP(On-Line
Transaction
Processing)数据库存储引擎。作为自研数据库POLARDB的存储引擎之一,已经广泛应用在阿里集团内部诸多业务系统中,包括交易历史库、钉钉历史库等核心应用,大幅缩减了业务成本,同时也作为双十一大促的关键数据库技术,挺过了数百倍平时流量的冲击。
与传统的InnoDB引擎不同,X-Engine使用分层存储架构(LSM-Tree)。分层存储有两个比较显著的优点:
相比InnoDB引擎,依据数据特征,使用X-Engine存储空间可降低至10%~50%,我们在著名的Link-Bench和阿里巴巴内部交易业务两个数据集上测试了X-Engine的存储空间效率。在测试中,对比开压缩的InnoDB引擎,X-Engine有着2倍空间优势,而对比未开压缩的InnoDB,X-Engine则有着3~5倍的优势。
实时历史库方案,为何不是其他高压缩引擎
• 通常我们默认MySQL是当今最流行的开源数据库,大概率是在线核心数据库集群的首选。相比其他高压缩的存储引擎,引入X-Engine完全无需做任何SQL代码改造,并且支持事务,接入成本最低,学习成本几乎为0
• 写入性能更强,X-Engine相比同为LSM-tree架构的Rocksdb,有超过10倍的性能提升。
• 在存储层引入数据复用技术等,优化Compaction的性能,降低传统LSM-tree架构中Compaction动作对系统资源的冲击,保持系统性能平稳
• 引入多个层级Cache,同时结合Cach回填和预取机制,利用精细化访问机制和缓存技术,弥补传统LSM-tree引擎的读性能短板,X-Engine的点查询能力几乎与Innodb持平
下图是X-Engine与主流历史数据存储方案对比
历史数据存储选型备份至OSS开源HBaseX-Engine压缩率高高高是否支持查询支持解析历史备份文件查询高高实时性N/A较高非常高应用代码改造代价N/A很高几乎不用修改事务支持N/A仅支持单行事务强主要场景冷备份大数据生态OLTP实时历史数据库架构设计和实现
总体架构思路
基于上文对实时历史库和X-Engine的介绍,阿里云数据库团队推出以X-Engine引擎为历史数据存储核心,同时生态工具DTS作为在线/历史数据流转通道,DMS作为历史数据无风险删除的完整“实时在线-历史库”方案,针对不同的业务场景和客户需求,在具体实现上可能会有所不同,我们提供了多种实时历史库方案的具体实现。主体架构图如下,核心思路为:
在线库/历史库拆分方案
一般来说,需要使用到实时历史库的场景,数据量都足够大到单台宿主机存放不了。在线数据库可能是根据业务水平或垂直拆分的多个RDS,也可能是一个规模较大的DRDS集群。为了尽可能地保证在线库的性能,推荐将在线库/历史库完全拆分解耦
• 历史库集群存储全量数据
• 通过DTS链路打通在线库和历史库,实时同步
• DTS链路过滤Delete操作
• 可直接使用新版DMS配置历史数据定期删除
源端为DRDS集群
数据同步链路走RDS
• 多条DTS链路打通底层RDS节点,同步性能强
• RDS数量较多可支持API批量创建和配置
• 链路稳定性更好
• 需要保证源端目标端库表数量一致,数据路由规则一致
数据同步链路走DRDS
• 只需要配置一条DTS链路,方便省钱
• 数据同步性能较差
• 源端DRDS扩容会影响到DTS同步链路
• 源端目标端的实例数量和数据路由规则可自由配置
源端为多个RDS
目标端为多个RDS
• 业务代码无需任何改造
• 运行后期历史库节点磁盘容量存在风险
目标端为DRDS集群
数据同步链路走RDS
同实例混用存储引擎方案
在线库/历史库拆分方案相对较为复杂,RDS支持同一实例混用存储引擎。针对总数据量不是特别大的场景,可以考虑同一实例下Innodb&X-Engine引擎混合使用
使用DMS-->数据工厂-->数据编排功能可以轻松地实现同一实例内的数据流动和过期数据删除,架构示意图如下。
DTS赋能在线/历史数据流转
DTS不仅支持全量&增量同步,支持不同数据库产品之间的数据同步,在在线/历史库解决方案中,DTS强大的"条件过滤"功能是非常重要的一环,通过配置DTS任务可以非常便捷地实现过滤Delete操作,动动鼠标点两下即可实现自定义的数据同步策略。
DMS赋能在线库过期数据删除
在线库的过期数据删除既要保障删除效率,也要保证删除过程中对在线库不会造成性能上的抖动,新版DMS支持创建“历史数据清理”的数据变更任务,通过该任务可以非常方便地完成以下工作
• 历史数据定期删除,指定调度时间和一次调度时长
• 大事务拆分,减少事务执行过程中锁表时间过长,避免主备延迟
• 清理遭遇异常中断可重试
• 支持查看任务运行状态和失败原因分析
• 配置方面简洁
过期数据清理思路
如果没有使用DMS生态工具,也自行实现过期数据删除,但实现较为复杂。一般较为通用的设计思路为将表的主键按照大小做拆分,保证一次删除"恰当数量"的数据,既保证删除效率又不影响线上服务
• 在线库的历史数据删除策略(假设主键为id,数据保存180天,时间属性列为date_col)
• 在线库历史数据清理注意点
• 代码上注意不要出现高并发删除的情况,即步骤b还没跑完,新的步骤b又进来了
• 程序sleep的具体秒数,要通过测试,取一个最合适的数值,主要看主备是否存在较大延迟,3只是估值
• 100000也是一个估值,实际取值最好也通过测试,取一个效率最高,对业务影响最小的数值。因为drds的序列不是单点递增1的,所以这里的10w不代表10w条记录。
• 假设删除程序中途崩溃了,或者执行很多天后发现部分数据没有删除。那么可以手工先删除一小部分残留的数据,比如预估下id<100w的记录还有多少条,不多的话直接执行DELETE FROMlogs_trans
WHERE reqdate
< SUBDATE(CURDATE(),INTERVAL 30 DAY) and id<100w 然后初始化整个程序,这样保证重新初始化以后不会做很多无用功,即反复执行删除条目很少的sql
极端场景分析
在临界时间处理上,实时历史库方案可能遭遇极端场景导致业务可能存在历史库的脏读问题,假设在线库数据保存180天
解决方法
• 配置链路异常告警,及时发现及时处理
• 预计影响的数据范围为DTS链路恢复前的临界时间点附近数据,建议从业务逻辑上订正数据
• 建议过期数据删除设置保守一点,比如临界时间为180天,过期数据只删除190天以后的数据,方便在极端场景下对比源端目标端的数据情况进行数据订正
本文系作者在时代Java发表,未经许可,不得转载。
如有侵权,请联系nowjava@qq.com删除。