阿里巴巴中台战略--数据库分库分表

阿里巴巴中台战略

数据尽可能平分

    不管是采用何种分库分表框架或平台,其核心的思路都是将原来保存在单边中太大的数据进行拆分,将这些数据分散保存在多个数据库的多个表中,避免因为单表数据太大给数据的访问带来读写性能的问题。所以在分库分表的场景下,最重要的一个原则就是被拆分的数据尽可能的平均拆分到后端的数据库中,如果拆分的不均匀。可能会产生数据访问热点,同样存在热点数据因为增长过快而又面临数据单表数据过大的问题。

    而对于数据按照什么样的维度进行拆分,大家看到很多场景中都是对业务数据的ID进行哈希取模的方式将数据平均拆分,这个方式确实在很多场景下都是非常合适的拆分方法,但并不是所有的场景中这种拆分都是最优选择。所以需要结合实际业务数据的结构和场景进行决定。

下面以大家最为熟悉的电商订单数据拆分为例。一般记录一条订单数据结构如下:
在这里插入图片描述
     订单数据主要有订单表和订单明细表。有的会有拆单的情况,就会有主订单和子订单的情况。主订单就是用户的一个订单,每购买成功提交一次就会生成一个主订单的数据,在有些情况下,用户可能在一个订单中选择不同卖家的商品,而每个卖家又会按照该订单中自己提供的商品计算相关的优惠(比如使用优惠券),所以会出现子订单的概念。即一个主订单会包含多个子订单,真正对应到具体每个商品的订单信息,则是保存在订单详情表中。

     如果一个电商平台稳健发展的话,订单数据是比较容易出现数据量大的问题。所以需要对它进行拆分。此时从理论上对订单拆分有两个维度,一个是订单的id取模的方式,即以订单id为分库分表键。另一个是通过买家用户id进行取模,即以买家 用户id为分库分表键。

  1. 如果是按照订单id取模,比如按照64取模,则可以保证主订单以及相应的子订单,和详情数据平均落入64个数据库中。
  2. 如果按照买家用户id取模的方式,比如也是按照64取模,也能够保证订单相关数据拆分到64个数据库中。但这里会出现一个业务场景中带来的问题,就是有些卖家的交易量很大,会比其他卖家要多出不少,就会出现数据不平均的现象,最终导致这些卖家的订单数据所在的数据库会相对其他数据库提前进入到数据归档(为了避免在线交易数据增大带来数据库性能问题,淘宝将3个月内的订单数据保存进在线交易数据库中,超过3个月的订单会归档到归档数据库)。

     所以从对“数据尽可能平均拆分”这条原则来看,按照订单id取模的当时看起来更能保证订单数据平均拆分。我们先不下结论,让我们继续从下面几条原则和最佳实践角度多思考不同的拆分维度带来的优缺点。

尽量减少事务边界

采用分库分表的方式将业务数据拆分后,如果每一条SQL语句中都能带有分库分表键,如图所示是以订单id按照8取模平均分布到8个数据库的订单表中,通过分布式服务层对于SQL进行解析后都能精确地将这条SQL语句推送到该数据所在的数据库上执行,数据库将执行的结果再返回给分布式服务层,然后分布式服务层再将结果返回给应用,整个过程和之前的单机操作没有差别。
在这里插入图片描述
     但有些场景进行查询时并不会带上分库分表键。比如在买家中心的要显示买家test1过去三个月的订单列表,此时SQL语句中就没有了分库分表键,则会出现把sql语句在所有数据库中进行查询,然后把查询的结果在分布式数据层进行聚合后再返回给前端应用。

在这里插入图片描述

     此时就是全表扫描。我们再看一下“事务边界”的定义,所谓的事务边界即是指单个SQL在数据库上同时执行的数量,上面示例中就是事务边界大的典型示例,即一条SQL语句同时被推到所有数据库中执行。事务边界的数量越大,会给系统带来以下弊端:

  1. 系统的锁冲突概率越高。当多个类似的SQL请求并行执行时,则出现数据锁造成资源访问互斥的概率大大增加。
  2. 系统越难以扩展。如果大量sql请求都是这样全表扫描,那么整个平台的数据库连接数量取决于单个数据库的连接能力,也就意味着整个数据库的能力是无法通过增加数据库实例来扩展。所以大量的全表扫描的sql请求对于系统扩展能力会带来不小的影响。
  3. 整体性能越低。对于性能,这里想强调的是对系统整体性能的影响,而不是单次SQL的性能。应用发送买家test1订单列表SQL的请求时(步骤1),分布式数据层会并行的将这条SQL语句推送(步骤2)到8台数据库上运行,因为订单数据进行了平均的拆分,单个数据库订单量都使得处于数据库最佳性能表现的状态,所以意味着每一个数据库返回的计算结果都是在一个可期望的时间内(比如100毫秒),将结果返回到分布式数据层(步骤3)分布式数据层将从各个数据库返回的结果在内存中进行聚合或排序等操作(步骤4),最后返回订单表给应用(步骤5)。

在这里插入图片描述
     整个SQL执行的过程包含了5个步骤,全表扫描和非全表扫描的区别是2和3步骤是并行的方式同时跟多个数据库进行交互,但在时间上带来的影响几乎是毫秒级的;而在第4个步骤是可能造成差异的一个点,如果只有几千条数据返回,对于几千条数据的内存聚合操作,处理时间也是毫秒级的。但是如果返回的数据是比较大的(比如几十万、几百万条数据)的聚合、排序、分组计算时,则会占用较大的内存和CPU资源。如果这样的请求比较频繁,就会给分布式数据层带来较大的资源占用,从而导致整体分布式服务的处理性能受到影响。

     不过很多人对全表扫描会有一些误解,甚至认为全表扫描对于系统是完全不能接受的。其实全表扫描在真实的业务场景中很难完全避免(也可以避免,但是会带来其他方面的问题),对于在分布式服务层的内存中进行数量不大的聚合这类的sql请求,如果不是高并发同时请求的情况下,对整体性能不会带来太大的影响。

     对于在内存中要进行大数量量聚合操作和计算sql的请求,如果请求比较频繁,则要考虑采用其他的平台来满足这一类场景的要求,比如Hadoop这类做大数据量离线分析的产品,如果应用对请求的实时性要求比较高,则可采用加内存数据库或Hbase这类平台。
如果是高并发同时请求的情况,为了扩展数据库的整体扩展能力,则要考虑“异构索引”手段来避免这样的情况发生。

==>数据库分库分表之异构索引表