海量结构化数据存储技术揭秘:Tablestore表设计最佳实践

前言

表格存储Tablestore是阿里云自研的面向海量结构化数据存储的Serverless NoSQL多模型数据库。在处理海量数据时,方案设计非常重要,合理的设计才能够发挥出数据库的性能水平。本文主要介绍Tablestore在表设计方面的一些实践经验,供大家参考。因为是第一篇最佳实践系列的文章,且很多读者对Tablestore还不了解,在介绍表设计之前也花了一些篇幅介绍什么是Tablestore、为什么选择Tablestore等,熟悉Tablestore的用户可以略过一些小节。

什么和为什么

什么是Tablestore

Tablestore是阿里云上的一款Serverless NoSQL多模型数据库(官网页面)。

Tablestore广泛应用在各种业务场景中,这些场景案例有一些实践文章介绍,文章收录在合集《表格存储Tablestore权威指南》。

产品架构可参考下图:

海量结构化数据存储技术揭秘:Tablestore表设计最佳实践

为什么需要最佳实践

作为一个数据库产品和云服务,简单好用是很大的一个竞争力,简单好用往往也意味着使用者不需要关心太多数据库原理或者表设计,能够直接上手。确实,很多用户是看了Tablestore的场景案例文章,直接创建一个Tablestore实例开始测试,熟悉常用接口后,很快实现了一套基于Tablestore的架构来满足业务需求,也没有遇到什么问题。

那么什么时候特别需要根据最佳实践进行设计呢?

  1. 数据规模大,应对海量数据仍需要在数据库功能或表设计上做一些取舍。

    1. 数据库产品的功能丰富度和数据规模存在着矛盾。即针对PB级数据设计的架构和功能,在查询丰富度上就会有取舍,难以满足非常灵活的查询。另一方面,选择了非常灵活的查询方式,可能也意味着数据整体规模存在着瓶颈,或者是成本较高。因此用户还是不可避免的面临数据库选型或者是功能选型。
    2. 分布式数据库需要做sharding,而sharding方式一般跟用户表设计或者用户数据相关。数据规模小时,数据倾斜问题不大,而数据规模大到一定程度,用户就需要关心这一问题。
  2. 数据库的整体性能跟用户的使用方式、业务代码的质量等有很强的关系。有时,业务层感受到的数据库性能取决于业务层的DBA或者架构师对于数据库的了解和自身的代码水平。加深对数据库的了解,可以让大家更好的应用这一产品。

为什么选型Tablestore

上面讲了什么是Tablestore,在讲怎么用Tablestore之前,我们先看下为什么选择Tablestore。为什么用Tablestore其实是一个大话题,Tablestore提供了非常丰富和通用的功能,也有一些独特的功能特性,可以见最上面的架构图,

这里简单提供一些选择Tablestore的理由:

  1. 完全0运维,即开即用、按量付费。Tablestore是阿里云上唯一一个Serverless的数据库,用户不需要预定任何资源来搭建服务,整个平台完全为你所用,只需要为使用量付费。实际上我们有一些免费额度,很多用户常年使用Tablestore,但一直在免费额度内免费使用。许多人没有使用过一些开源领域内针对大数据的数据库产品,原因就在于这些产品依赖很多,也需要很多资源来部署,运维成本也比较高。Tablestore提供了完全0门槛的方式,来满足你的大数据需求。
  2. 适用于多种工作负载,提供水平扩展能力(scalability)的同时提供丰富查询能力。NoSQL阵营有很多产品,各有所长,Tablestore天生为大数据设计,满足PB级数据的存储和查询,同时通过全局二级索引、多元索引等功能扩充查询能力,满足多种工作负载和查询需求。
  3. 一个产品解决多种需求,减少业务层维护多产品的负担和数据同步负担。很多业务为了满足不同查询需求,组合使用MySQL、HBase、Elasticsearch等多款产品,并在这些产品之间进行数据同步。了解到Tablestore具有多种能力后,一些用户开始迁移到Tablestore,通过一个产品满足多种需求,也彻底解决了数据同步的负担。
  4. 提供多模型和丰富场景案例,以及专家在线支持。我们针对特定领域提供了特定的模型,简化业务的设计和开发,比如消息和feed流领域提供Timeline模型,时序数据提供Timestream模型,科学大数据的多维格点数据提供Grid模型等。针对典型的场景,定期推出场景案例文章,并提供代码示例。建立专门的用户支持大群和针对特定客户的支持小群,研发人员轮班来进行技术支持和答疑工作,也帮助用户进行方案设计。
  5. 平台化功能演进,与大数据生态对接,提供数据通道等。Tablestore是阿里云上的自研产品,也在不断的向着多模型和平台化的方向演进,提供更多的功能,与各种生态对接,提供领域化的解决方案。用户使用Tablestore后,即可不断享受最新的技术红利,这也是云计算的优势。

业务的一般接入流程

假设你有一块业务打算用Tablestore来作为数据库。简单想象一下业务接入Tablestore的过程,首先可能是通过《表格存储Tablestore权威指南》中找到类似自己业务场景的解决方案,了解这一方案的实现方式,直接对照这一方案进行实现,然后接入业务数据进行测试和验证,最终上线业务。或者是在了解了一些场景案例后,开始分析自己的业务需求,设计业务表结构,然后编码实现,最终测试验证并上线。

总之,可能会经历这样的几个过程:了解Tablestore(场景案例和文档)、业务需求分析、表结构和查询方案设计、编码实现、测试上线。

这个过程也分两种情况:

  1. 业务场景可以直接套用Tablestore提供某种模型,比如Timeline、Timestream、Grid这些模型。这时候不需要进行特别的表结构设计,直接套用模型即可,开发时也使用特定模型的SDK接口。
  2. 业务场景无法套用模型,此时需要根据业务需求和场景进行表结构和查询方案设计。这个过程因场景而异,有些场景比较简单,不用特别设计,比如简单的KeyValue模式进行存取。有些场景会比较复杂,比如数据规模很大,且查询方式多样,这时候需要一些特别的设计。

Tablestore提供有专门的钉钉技术交流群(群号11789671),在业务接入的任何阶段,有任何问题都欢迎咨询。

表设计

数据散列

怎么看待散列问题

分布式数据系统中,大部分都会提到数据散列这个问题,散列的目的是让数据分布更均匀,避免热点。假设数据分布不均匀,会出现以下问题:

  1. 数据写入和读取能力受限于单个分区的能力,或者是单机能力,存在明显瓶颈。
  2. 在某些数据处理场景下,热点或者数据分布不均会导致明显的长尾效应,拖慢整体速度。
  3. 某个数据系统或者模块往往仅仅是整个业务链路上的一环,热点会拖慢整个上下游的速度

通常来讲,分布式数据库系统中,理想的数据和负载情况是:数据均匀分布,水平方式切分为很多分区,分布在不同机器上,读写压力也水平分散,每个请求的压力仅覆盖局部的一小部分,而不是整体。这种模式下完全水平扩展,业务压力增加,只需要增加机器资源即可。

在业务设计上,应当尽量避免会导致数据热点的设计,在未来负载可支撑的情况下兼顾业务需求。有时业务需求会与数据均匀相矛盾,比如要按照时间全局有序的查询整个表最近写入的数据,那就与数据写入分散的原则有一些冲突。如果要让新写入的数据都集中在一起,系统就存在扩展瓶颈,那么在当前以及未来能否支撑这样的负载,未来系统的最大的写入TPS可能是多少,这些就是业务架构师需要考虑的问题。如果这种模式无法支持未来的负载,就意味着必须更改设计,否则是为当下或者将来埋坑。

另一方面辩证的来看,如果业务需求很低,比如对于Tablestore的tps/qps都在1000以下,整体数据在10GB下,且未来也不会有大的增长,那么热点问题也不是一个需要花太多精力考虑的问题,这样的负载单分区完全可以支撑。实际上,单分区也可以达到几万行/s的写入,但是架构设计上不要依赖单分区能力,尽量控制在很安全的水平内。

一类常见热点

一个常见的热点例子,比如下表。这个表中主键有两列,分别是Timestamp和MachineIp,这是一个简单的监控场景的例子,每次存储某个机器某个时间的一些指标值。

Timestamp(主键列1,分区键) MachineIp(主键列2) Metrics(属性列)
1563617365001 10.10.10.1 {"cpu":10.0, "net_in": 10.0}
1563617365002 10.10.10.2 {"cpu":11.0, "net_in": 20.0}
1563617365003 10.10.10.3 {"cpu":12.0, "net_in": 30.0}

这个表设计有很明显的热点问题,每次数据写入都是在表的末尾追加数据,而数据是按照分区键的范围进行分区的,也就是每次数据写入都会写入最后一个分区,而无法把写入负载平衡到多台机器上。这种情况下,单分区的写入能力就是整个表的写入能力上限,更重要的是,一旦发生热点,无法通过分片分裂来平衡负载,因为写入压力总是在写尾部。

一种较分散的方式

针对上面的问题,一个可以让写入更分散的方式是把MachineIp放到主键列的第一列,Timestamp放到第二列。

这样就把写入压力按照Machine这个级别进行了分散,即写入的是每个Machine的尾部,大部分情况下也足够分散了。

有一种局部热点情况,假设10.10.0.0/16这个网段的机器写入量很大,而Tablestore是按照分区键的一个范围进行分区,刚好这些机器又都分在一个分区内,会不会产生热点呢?如果写入压力超过或接近单分区的上限,确实是一个热点,但是Tablestore具备自动负载均衡的能力,会自动将这个分区进行切分(Split),使得压力平均到两个分区上,如果仍不够会继续进行切分。

类似MachineIp这种分区键是很常见的一种分区方式,即把某个业务上比较分散的Key放到第一列,比如UserId,DeviceId、OrderId等等。这种模式只要这个Key本身比较分散,一般无太大问题。

更分散的方式: 拼接MD5

上面的例子中,也可以通过拼接MD5的方式将ip本身的顺序打散,这种方式也较为常用。比如对ip计算md5,然后在
ip前面拼接md5的前4位,如下图:

MachineIp(主键列1,分区键) Timestamp(主键列2) Metrics(属性列)
7552_10.10.10.2 1563617365000 {"cpu":10.0, "net_in": 10.0}
7552_10.10.10.2 1563617365001 {"cpu":10.0, "net_in": 10.0}
8d9c_10.10.10.3 1563617365003 {"cpu":10.0, "net_in": 10.0}
8d9c_10.10.10.3 1563617365004 {"cpu":10.0, "net_in": 10.0}
e5a3_10.10.10.1 1563617365000 {"cpu":10.0, "net_in": 10.0}

这种方式可以避免某个ip段的热点,同时在结构上具备一定的模式,即以16进制字符串开头,这样有利于系统进行预分区。

解决顺序性的问题

回到最初的例子中,很多用户以Timestamp作为第一个主键列,是希望数据全局按照时间排序,这样可以按照时间范围进行查询,但是这种方式导致了尾部热点,给系统能力扩展带来了瓶颈。

那么怎么解决对顺序性的要求呢?

  1. 方法一:业务设计上避免对全局顺序性的要求,改为局部顺序性,比如上面的例子,在某个MachineIp下,数据仍是按照Timestamp有序的。即先通过一个字段来打散数据,再按照某种顺序查询。
  2. 方法二:业务上采用分桶的方式,分散写入压力,在读取时从每个桶查询再合并。比如采用16个桶,每次写入时把时间对16取模,余数放到第一个主键列(0到15),时间放到第二个主键列。在读取时,从每个桶读取后合并即可。这种方式可以把压力分散到不同的桶上,能有多么分散取决于有多少个桶。在读取时需要对所有桶进行读取,可以采用并行读取的方式进行加速。
  3. 方法三:表上仍然采用均匀的方式写入数据,给表建立一个多元索引,通过多元索引进行Timestamp等字段的有序查询。多元索引在数据分布上本身会进行一次散列,分成多个分片,查询时自动从多个分片合并数据。

其余主键设计问题

  1. 主键的第一个字段为分片键,分片键首先考虑散列问题,上一节已经讲过。
  2. 同一个分区键(分区键为第一列主键列)下数据不宜过多,比如10GB内(无硬性限制),因为相同分区键的行无法再进行分裂。这里相同分片键,指定的是对于第一列主键列的某个确定的值。对应上文的例子,即某个机器下的数据尽量不要超过10GB。
  3. 主键列的长度限制为1KB,尽可能使用较短的主键,有利于加快查询速度。
  4. 主键列是有先后顺序的,根据查询需求设计主键列的顺序。如果要按照不同的几种主键列顺序进行范围查询,考虑使用全局二级索引。如果有各种条件组合查询需求,考虑使用多元索引。

属性列

  1. Tablestore支持宽行,即一行可以非常宽,比如几十万个属性列。但是很宽的行,如果一次性读取,可能会读不出(超时),需要指定列或者分页读取某些列。因此,原则上不太建议非常宽的行(万列以上),可能会使某些功能受限。
  2. 属性列有2MB的限制,如果要保存的数据超过这一限制,需要把数据拆分成多列保存。、

索引选择

Tablestore存储引擎的整体架构类似于Google的BigTable,在开源领域的实现有HBase等。其存储引擎的数据模型为宽行模型,这个模型可以支撑海量的数据,但在数据查询方面依赖主键,对主键做精确查询和范围查询效率很高,但其他查询方式则效率很低,甚至要扫描全表。

为了增加数据查询的能力,Tablestore在存储引擎之外增加了索引引擎,支持全局二级索引和多元索引,其中多元索引支持多条件组合查询、模糊查询、地理空间查询,以及全文索引等。

关于索引引擎的原理和如何选择,我有另外一篇文章专门来进行解读,读者可进行参考:《海量结构化数据存储技术揭秘:Tablestore存储和索引引擎详解》

进一步设计

如果你在为接入Tablestore而进行业务设计,那么看了本文也许会更加清晰,也许会更加迷糊。或者还有些功能需求不是很清晰,比如怎么与大数据分析产品结合,怎么使用数据通道等,或者是希望进行进一步的技术探讨,或者是实现层面想知道如何进行编码。或者是实际使用中遇到什么问题。那么,我们有一个钉钉交流群,群号是11789671,欢迎来一起探讨。
海量结构化数据存储技术揭秘:Tablestore表设计最佳实践

上一篇:hug -- Embrace the APIs of the future


下一篇:核心C#