这时再来看看买家test1在获取订单信息进行页面展现时,应用对于数据库的访问流程就发生了如图的5-11变化。
在有了订单索引表后,应用首先会通过当前买家ID(以图示中test1为例),首先到订单索引表中搜索出test1的所有订单索引表(步骤①),因为步骤②SQL请求中带了以buyer_ID的分库分表键,所以一次是效率最高的单库访问,获取到了买家test1的所有订单索引表列表并由DRDS返回到了前端应用(步骤③和④),应用在拿到返回的索引列表后,获取到订单的ID列表(1,5,8),在发送一次获取真正订单列表的请求(步骤⑤),同样在步骤⑥的SQL语句的条件中带了分库分表键order_ID的列表值,所以DRDS可以精确地将此SQL请求发送到后端包含in列表值中订单ID的数据库,而不会出现全表扫描的情况,最终通过两次访问效率最高的SQL请求代替了之前需要进行全表扫描的问题。
图5-11 基于订单索引表实现买家订单列表查看流程示意
这时你可能会指出,为什么不是将订单的完整数据按照买家ID维度进行一次分库保存,这样就只需要进行一次按买家ID维度进行数据库的访问就获取到订单的信息?这是一个好问题,其实淘宝的订单数据就是在异构索引表中全复制的,即订单按照买家ID维度进行分库分表的订单索引表跟以订单ID维度进行分库分表的订单表中的字段完全一样,这样确实避免了多一次的数据库访问。但一般来说,应用可能会按照多个维度创建多个异构索引表,比如为了避免买家查看自己的订单时频繁进行全表扫描,实际中还会以买家ID的维度进行异构索引表的建立,所以采用这样数据全复制的方法会带来大量的数据冗余,从而增加不少数据库存储成本。
另外,在某些场景中,在获取主业务表的列表时,可能需要依赖此业务表所在数据库的子业务表信息,比如订单示例中的主、子订单,因为是以订单ID的维度进行了分库分表,所以该订单相关的子订单、订单明细表都会保存在同一个数据库中,如果我们仅仅是对主订单信息做了数据全复制的异构保存,还是通过一次对这张异构表的数据进行查询获取包含了子订单信息的订单列表时,就会出现跨库join的问题,其对分布式数据层带来的不良影响其实跟之前所说的全表扫描是一样的。所以我们还是建议采用仅仅做异构索引表,而不是数据全复制,
同时采用两次SQL请求的方式解决出现全表扫描的问题。
实现对数据的异步索引创建有多种实现方式,一种是从数据库层采用数据复制的方式实现;另一种是如图5-12所示在应用层实现,在这一层实现异构索引数据的创建,就必然会带来分布式事务的问题。
图5-12 精卫实现数据同步的流程图这里给大家介绍的是在数据库层实现异构索引的方式,也是阿里巴巴内部目前采用的方式,通过一款名为精卫填海(简称精卫)的产品实现了数据的异构复制。本质上精卫是一个基于MySQL的实时数据复制框架,可以通过图形界面配置的方式就可以实现异构数据复制的需求。除了在同步异构索引数据的场景外,可以认为精卫是一个MySQL的数据触发器+分发管道。
数据从源数据库向目标数据库的过程中,可能需要对数据进行一些过滤和转换,精卫本身的结构分为抽取器(Extractor)、管道(Pipeline)、分发器(Applier),数据从抽取器流入管道,管道中有过滤器可以执行对数据的一些过滤的操作,然后再交由分发器写入到目标,如图5-12所示。
精卫平台通过抽取器(Extractor)获取到订单数据创建在MySQL数据库中产生的binlog日志(binlog日志会记录对数据发生或潜在发生更改的SQL语句,并以二进制的形式保存在磁盘中),并转换为event对象,用户可通过精卫自带的过滤器(Filter)(比如字段过滤、转换等)或基于接口自定义开发的过滤器对event对象中的数据进行处理,最终通过分发器(Applier)将结果转换为发送给DRDS的SQL语句,通过精卫实现异构索引数据的过程如图5-13所示。
虽然精卫平台在系统设计和提供的功能不算复杂,但其实但凡跟数据相关的平台就不会简单。这里不会对精卫核心的组件和机制做更详细的介绍,只是将精卫多年来能力演变后,目前提供的核心功能做一下介绍,为有志在该领域深耕细作的技术同仁多一些思路和借鉴。