『购物车升级』是今年双十一的重要体验提升项目,体现了大淘宝技术人“用技术突破消费者和商家体验天花板”的态度。这是一种敢于不断重新自我审视,然后做出更好选择的存在主义态度。
「体验提升」通常表现在以前需要降级的功能不降级,以前不够实时的数据逐渐实时,以前调用链路的长耗时逐步降低——这通常是庞大的系统工程,需要涉及到的每一个环节(客户端、应用、中间件、数据库、网络、容器、系统内核等组件)提供最强的产品能力来支撑。到数据库这个环节,挑战通常是访问量和连接数暴涨的前提下,仍要保持延时稳定和成本可控。
低延时是这些挑战里面的核心,是内存数据库 Tair 提供的服务本质。在高吞吐、大连接数、热点请求、异常流量、复杂计算逻辑、弹性伸缩这些真实场景下保持稳定的低延时,是 Tair 能够在低延时场景被选择的关键因素。 作为今年支撑购物车升级的核心产品,Tair 使用的内存/SCM 混合存储、水平扩展分区无锁和 SQL 引擎等技术是在支撑十四次双十一的过程中逐渐打磨完善的,在这些技术的基础上 Tair 使用 Fast Path 执行 SQL 、执行器模式及算子适配等技术持续进行服务端优化。本文将围绕 Tair 低延时这一本质特征在构建时所采用的系统手段,藉此提出更多问题来探讨,进一步打造更强大的内存数据库。
Tair 在低延时场景下的服务能力
低延时的基石
存储引擎的性能是数据库低延时的基石。从功能上看,我们会关心存储引擎提供的并发(线程安全、无锁)、事务处理(MVCC、冲突识别、死锁识别、操作原子性)、快照(标记数据集状态、降低延迟、减少容量膨胀)等能力。这里把并发放到后面论述,先看单次请求的延时,主要涉及到存储介质和数据索引。
存储介质
作为内存数据库,Tair 在绝大部分场景使用单次访问延时在 ns 级别的内存 / SCM 作为主要的存储介质。以 Table 存储为例,服务端的常驻数据大概可以分为 Tuple(可以认为是表里面的某一行)、String Pool、Index 三部分,这些数据都是存放在内存 / SCM 中,只有快照和日志会存放在磁盘上。
除了存储介质的延时,通常我们还需要关心的是介质的成本。成本一方面是从硬件上,Tair 是率先采用 SCM 的云产品,相对于 DRAM,SCM 的密度更高能支持持久化,且成本更低。上面提到的三部分数据结构中, Tuple 和 String Pool 是主要占用数据的空间,存放在空间更大的 SCM 上,Index 需要频繁访问且占用空间更低,存放在空间较小延时更低的 DRAM 上。
另外一方面是从数据结构上去降低成本,这里的技术手段包括,设计更友好的数据结构和碎片整理的机制、进行透明的数据压缩。 Tair 中会以 Page 为单位来管理 Tuple,随着数据的删除,每个 Page 会有一些空闲的 Tuple,存储引擎会按照空闲率来对 Page 分组,当整体的空闲率高于一定阈值(默认是 10%)时,就会试图根据空闲率进行页的合并。
索引
Tair 目前在使用的索引主要有 HashTable、SkipList、RBTree、RTree、Number Tree、Inverted index 等,分别应用于不同的场景。索引和需要服务的模型是相关联的,比如如果服务的主要模型是 Key-Value,那么主索引使用 HashTable 来达到 O(1)的时间复杂度,ZSet 涉及到数据排序和排名的获取,所以 Zset 使用了一个可以在查找时同时获取 Rank 的 Skiplist 作为索引。排序场景使用 SkipList 作为索引是内存数据库中比较常见的方案,相较于 BTree 来说,由于没有 Structure Modification,更易于实现并发和无锁,当然,也会增加一些 Footprint。在 Table 存储中,使用 RBTree 作为排序索引,在数据量达到 10k 的场景下,RBTree 能够提供更稳定的访问延时和更低的内存占用。
在数据库系统中,索引能力的增强还可以让执行器对外暴露更强的算子,比如 Tair 中的 RBTree 提供了快速计算两个值之间 Count 的能力,对外提供了 IndexCountOperator,这样类似于 Select count(*) from person where age >= 8 and age <= 25 的查询就可以直接使用 IndexCountOperator 来获取结果,无需朴素地调用 IndexScanOperator -> AggregateOperator对索引进行扫描才得出结果。
低延时的挑战
合适的存储介质和索引只是提供低延时的一个前提,要在真实环境提供低延时的内存数据库服务&#