在2006年阿里巴巴B2B团队以开源方式研发了Cobar这一关系型数据的分布式处理系统。该系统在很大程度上解决了最初使用Oracle数据库因为存储数据变得越来越大带来的扩展性问题,并且为开发人员提供了一个使用相对简单的用户体验,在当时Cobar平均每天处理近50亿次的SQL操作。
阿里巴巴分布式数据层平台发展和演变
业务数据从原来的单库单表模式变成了数据被拆分到多个数据库,甚至多个表中,如果在数据访问层做一下功能的封装和管控,所有分库分表的逻辑和数据的跨库操作都交给应用的开发人员来实现,则对开发人员的要求变得相对高一点,稍有不慎,可能会对平台的业务包括数据带来较大的影响。
在2006年阿里巴巴B2B团队以开源方式研发了Cobar这一关系型数据的分布式处理系统。该系统在很大程度上解决了最初使用Oracle数据库因为存储数据变得越来越大带来的扩展性问题,并且为开发人员提供了一个使用相对简单的用户体验,在当时Cobar平均每天处理近50亿次的SQL操作。但随着阿里巴巴业务场景越来越复杂,Cobar平台功能上的约束对满足某些业务场景显得力不从心,例如:
1)不支持跨库情况下的连接、分页、排序、子查询操作。
2)SET语句执行会被忽略,处理事务和字符集设置除外。
3)分库情况下,insert语句必须包含拆分字段列名。
4)分库情况下,update语句不能更新拆分字段的值。
5)不支持SAVEPOINT操作。
6)使用JDBC时,不支持rewriteBatchedStatements=true参数设置(默认为false)。
7)使用JDBC时,不支持useServerPrepStmts=true参数设置(默认为false)。
8)使用JDBC时,BLOB、BINARY、VARBINARY字段不能使用setBlob()或setBinaryStream()方法设置参数。
2008年阿里巴巴内部基于淘宝业务发展的需要,在Cobar的基础上重新研发了分布式数据层框架TDDL(Taobao Distributed Data Layer,外号:头都大了),针对分库分表场景,提供了对各种业务场景的支持更加完善,开发人员体验更加友好,管控能力大幅提升。
目前TDDL已经成为阿里巴巴集团内部业务默认使用的分布式数据层中间件,支撑着今天阿里巴巴上千个应用,平均每天SQL调用超千亿次。从架构角度(如图5-3所示),TDDL沿袭了Cobar之前在应用和后端数据库之间的定位,通过增加对SQL的解析实现了更为精准的路由控制,以及对跨库join、统计等计算的支持,弥补了之前Cobar在功能上的约束和限制,成为一个完整支持SQL语法兼容的平台。
图5-3TDDL架构示意图
三层数据源每层都按JDBC规范实现,使得对前端应用没有任何代码侵入。
Matrix层(TDataSource)实现分库分表逻辑,底下持有多个GroupDs实例。
Group层(TGroupDataSource)实现数据库的主备/读写分离逻辑,底下持有多个AtomDs实例。
Atom层(TAtomDataSource)实现数据库连接(ip、port、password、connec-
tionProperties)等信息的动态推送,持有原子的数据源。
通过TDDL实现一次来自应用的SQL请求,完整的交互流程(如图5-4所示)中体现了各个服务组件所起到的作用。
图5-4TDDL针对一次SQL请求完整处理流程
正是有了这样的架构和设计,特别是增加了对SQL语义的解析,使得TDDL相比之前的Cobar在功能上提升了一个新的层级,对于Cobar不支持的跨库数据聚合、子查询、group by、order by等特性都有了很好的支持,从而成为在分库分表技术业界被很多技术同仁认可的一套分布式数据层框架,总结来说,TDDL提供了以下优点:
数据库主备和动态切换。带权重的读写分离。单线程读重试。集中式数据源信息管理和动态变更。支持MySQL和Oracle数据库。基于JDBC规范,很容易扩展支持实现JDBC规范的数据源。无Server、client-jar形式存在,应用直连数据库。读写次数,并发度流程控制,动态变更。可分析的日志打印,日志流控,动态变更。随着阿里巴巴集团业务的多元化,特别是对于除电商领域以外业务的不断扩展和并购,TDDL这种无Server的模式对于故障的定位和对运行环境的要求(必须是阿里巴巴内部服务环境),支持这些新兴业务有了不少困难,所以在2014年,阿里巴巴已经研发出新一代分布式数据库产品DRDS(Distributed Relational Database Service),该产品相比TDDL在业务场景的支持、故障的定位、运维管控等方面又有了一个全面的提升,今天DRDS已经成为阿里云上用于解决关系型数据库线性扩展问题的标准云产品,服务了几百家阿里巴巴集团外部的客户。
数据尽可能平均拆分
不管是采用何种分库分表框架或平台,其核心的思路都是将原本保存在单表中太大的数据进行拆分,将这些数据分散保存到多个数据库的多个表中,避免因为单表数据太大给数据的访问带来读写性能的问题。所以在分库分表场景下,最重要的一个原则就是被拆分的数据尽可能的平均拆分到后端的数据库中,如果拆分得不均匀,还会产生数据访问热点,同样存在热点数据因为增长过快而又面临数据单表数据过大的问题。
而对于数据以什么样的维度进行拆分,大家看到很多场景中都是对业务数据的ID(大部分场景此ID是以自增的方式)进行哈希取模的方式将数据进行平均拆分,这个简单的方式确实在很多场景下都是非常合适的拆分方法,但并不是在所有的场景中这样拆分的方式都是最优选择。也就是说数据如何拆分并没有所谓的金科玉律,更多的是需要结合业务数据的结构和业务场景来决定。
下面以大家最熟悉的电商订单数据拆分为例,订单是任何一个电商平台中都会有的业务数据,每个淘宝或天猫用户在平台上提交订单后都会在平台后端生成订单相关的数据,一般记录一条订单数据的数据库表结构如图5-5所示。
订单数据主要由三张数据库表组成,主订单表对应的就是用户的一个订单,每提交一次都会生成一个主订单表的数据。在有些情况下,用户可能在一个订单中选择不同卖家的商品,而每个卖家又会按照该订单中是自己提供的商品计算相关的商品优惠(比如满88元免快递费)以及安排相关的物流配送,所以会出现子订单的概念,即一个主订单会由多个子订单组成,而真正对应到具体每个商品的订单信息,则是保存在订单详情表中。
图5-5 订单相关数据表结构示意
如果一个电商平台的业务发展健康的话,订单数据是比较容易出现因为单个数据库表中的数据太大而造成性能的瓶颈,所以需要对它进行数据库的拆分。此时从理论上对订单拆分是可以由两个维度进行的,一个维度是通过订单ID(一般为自增ID)取模的方式,即以订单ID为分库分表键;一个是通过买家用户ID的维度进行哈希取模,即以买家用户ID为分库分表键。
两种方案做一下对比:
如果是按照订单ID取模的方式,比如按64取模,则可以保证主订单数据以及相关的子订单、订单详情数据平均落入到后端的64个数据库中,原则上很好地满足了数据尽可能平均拆分的原则。
通过采用买家用户ID哈希取模的方式,比如也是按64取模,技术上则也能保证订单数据拆分到后端的64个数据库中,但这里就会出现一个业务场景中带来的一个问题,就是如果有些卖家是交易量非常大的(这样的群体不在少数),那这些卖家产生的订单数据量(特别是订单详情表的数据量)会比其他卖家要多出不少,也就是会出现数据不平均的现象,最终导致这些卖家的订单数据所在的数据库会相对其他数据库提早进入到数据归档(为了避免在线交易数据库的数据的增大带来数据库性能问题,淘宝将3个月内的订单数据保存进在线交易数据库中,超过3个月的订单会归档到后端专门的归档数据库)。
所以从对“数据尽可能平均拆分”这条原则来看,按照订单ID取模的方式看起来是更能保证订单数据进行平均拆分,但我们暂且不要这么快下结论,让我们继续从下面几条原则和最佳实践角度多思考不同的拆分维度带来的优缺点。
尽量减少事务边界
不管是TDDL平台还是DRDS,采用分库分表的方式将业务数据拆分后,如果每一条SQL语句中都能带有分库分表键,如图5-6所示是以自增的订单ID以8取模,将订单平均分布到8个数据库的订单表中,通过分布式服务层在对于SQL解析后都能精准地将这条SQL语句推送到该数据所在的数据库上执行,数据库将执行的结果再返回给分布式服务层,分布式服务层再将结果返回给应用,整个数据库访问的过程跟之前的单机数据库操作没有任何差别。这个是在数据进行了分库分表拆分后,SQL语句执行效率最高的方式。
图5-6DRDS对带分库分表键的SQL请求处理
但不是所有的业务场景在进行数据库访问时每次都能带分库分表键的。比如在买家中心的界面中,要显示买家test1过去三个月的订单列表信息,因为该买家test1的订单按订单ID取模的方式分布到了不同的数据库中,此时SQL语句中就没有了分库分表键值,则出现了如图5-7所示的情况,分布式数据层会将获取test1订单的SQL语句推送到后端所有数据库中执行,然后将后端数据库返回的结果在分布式数据层进行聚合后再返回给前端应用。
图5-7DRDS对不带分库分表键的SQL请求进行全表扫描处理
此时就出现了我们所说的全表扫描。此时我们来解释一下这里“事务边界”的定义,所谓的事务边界即是指单个SQL语句在后端数据库上同时执行的数量,上面示例中就是事务边界大的典型示例,即一条SQL语句同时被推送到后端所有数据库中运行。事务边界的数量越大,会给系统带来以下弊端:
系统的锁冲突概率越高。如果事务边界大的SQL请求比较多,在一次SQL请求处理过程中自然对于后端的数据库操作的数据库记录覆盖比较广,当有多个类似的SQL请求并行执行时,则出现数据锁造成的资源访问互斥的概率会大大增加。
系统越难以扩展。如果有大量的SQL请求都是这样全表扫描,或者从极端角度说明这个问题,如果每一次的SQL请求都需要全表扫描执行,你会发现整个平台的数据库连接数量是取决于后端单个数据库的连接能力,也就意味着整个数据库的能力是无法通过增加后端数据库实例来扩展的。所以如果有大量的全表扫描的SQL请求对于系统的扩展能力会带来不小的影响。
整体性能越低。对于性能,这里想强调的是对系统整体性能的影响,而不是单次SQL的性能。应用发送获取买家test1订单列表SQL的请求(如图5-8步骤①)时,分布式数据层会并行的将这条SQL语句推送(如图5-8步骤②)到后端8台数据库上运行,因为订单数据进行了平均的拆分,单个数据库订单表的数据量大小都使得数据库处于最佳性能表现的状态,所以意味着每一个数据库返回的计算结果都是在一个可期望的时间内(比如100毫秒),将结果返回到分布式数据层(如图5-8步骤③),分布式数据层将从各个数据库返回来的结果在内存中进行聚合或排序等操作(如图5-8步骤④),最后返回订单列表给应用(如图5-8步骤⑤)。
图5-8DRDS对需全表扫描操作的SQL请求处理流程
整个SQL执行的过程包含了5个步骤,仔细看看,你会发现一次带分库分表键执行的SQL过程也会经历这5个步骤,区别只是在②③步骤是并行的方式同时跟多个后端数据库进行交互,但在时间上带来的影响几乎是毫秒级的;而第④个步骤是可能造成差异的一个点,如果像示例中一个用户的订单信息可能最多几千条,对于几千条数据的内存聚合操作,处理时间也是毫秒级的,所以这样一次全表扫描的操作,用户的体验是完全无感知的,跟访问单机数据库的体验是没有差异的。但如果在第④个步骤中确实遇到对大数据量(比如几十万、几百万条数据)的聚合、排序、分组等计算时,则会占用较大的内存和CPU计算资源,如果这样类型的SQL请求比较频繁的话,就会给分布式数据层带来较大的资源占用,从而导致整体分布式服务的处理性能受到影响。
很多人对于全表扫描会有一些误解,甚至认为出现全表扫描对于系统来说是完全不能接受的。其实全表扫描在真实的业务场景中很难完全避免(也可以做到完全避免,但会带来其他方面的问题,后面会有说明),对于在分布式数据层的内存中进行数据量不大的聚合这类的SQL请求,如果不是高并发同时请求的情况下,比如对订单进行复杂的条件检索,如图5-9所示,就一定需要采用全表扫描的方式,将查询语句同时推送到后端的数据库中才能实现该场景的要求,但因为调用不会特别频繁,而且计算的数据量不会太大,所以整体不会给数据库整体性能带来太大的影响。
图5-9 订单搜索是典型的多条件查询场景
如果是高并发情况下同时请求的话,为了数据库整体的扩展能力,则要考虑下面描述的异构索引手段来避免这样的情况发生。对于在内存中要进行大数据量聚合操作和计算的SQL请求,如果这类SQL的不是大量并发或频繁调用的话,平台本身的性能影响也不会太大,如果这类SQL请求有并发或频繁访问的要求,则要考虑采用其他的平台来满足这一类场景的要求,比如Hadoop这类做大数据量离线分析的产品,如果应用对请求的实时性要求比较高,则可采用如内存数据库或HBase这类平台,这一部分的内容不在本书中讨论。
异构索引表尽量降低全表扫描频率
还是基于订单数据的分库分表场景,按照订单ID取模虽然很好地满足了订单数据均匀地保存在后端数据库中,但在买家查看自己订单的业务场景中,就出现了全表扫描的情况,而且买家查看自己订单的请求是非常频繁的,必然给数据库带来扩展或性能的问题,有违“尽量减少事务边界”这一原则。其实这类场景还有很多,比如卖家要查看与自己店铺相关的订单信息,同样也会出现上述所说的大量进行全表扫描的SQL请求。
针对这类场景问题,最常用的是采用“异构索引表”的方式解决,即采用异步机制将原表内的每一次创建或更新,都换另一个维度保存一份完整的数据表或索引表。本质上这是互联网公司很多时候都采用的一个解决思路:“拿空间换时间”。
也就是应用在创建或更新一条按照订单ID为分库分表键的订单数据时,也会再保存一份按照买家ID为分库分表键的订单索引数据,如图5-10所示,其结果就是同一买家的所有订单索引表都保存在同一数据库中,这就是给订单创建了异构索引表。
图5-10 订单异构索引表
这时再来看看买家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所示。
虽然精卫平台在系统设计和提供的功能不算复杂,但其实但凡跟数据相关的平台就不会简单。这里不会对精卫核心的组件和机制做更详细的介绍,只是将精卫多年来能力演变后,目前提供的核心功能做一下介绍,为有志在该领域深耕细作的技术同仁多一些思路和借鉴。
图5-13 采用精卫平台实现异构索引表流程示意
(1)多线程管道实现
在精卫平台应用的早期,数据的同步均是采用单线程管道任务模式,即如
图5-12中对binlog进行单线程的处理。随着业务的发展,需要同步的数据量越来越大,单纯的单线程管道任务已经成为系统的瓶颈,后来开发了对多线程管道任务的支持(如图5-14所示)。
图5-14 精卫支持多线程管道数据同步
但多线程管道就会带来数据同步的顺序问题。在对binlog数据进行多线程并行处理后,就不能保证在源数据库中执行的SQL语句在目标数据库的顺序一致,这样在某些业务场景中一定会出现数据不一致性的问题。对于这个问题,目前精卫中提供的解决思路是保证同一条记录或针对同一分库表发生的数据同步按照顺序执行。
如果最后发送到分布式数据层的SQL语句中没有分库键,则通过对“库名+表名+主键值”哈希后对线程数取模,这样就能让同一条记录的数据同步事件处理都会在同一线程中顺序执行,保证了该记录多次变更的顺序性,但是不保证不同记录间的顺序。如果SQL语句中有分库键,则通过“库名+分库键值”哈希后对线程数取模,效果是保证不同逻辑表针对相同分库逻辑的记录变化顺序。
(2)数据的安全
凡是牵涉数据的操作,数据的安全一定是最重要的。如何保证在分布式环境下同步任务效率最大化,同时保证服务的稳定和数据的安全,是很多此类平台精益求精、力求突破的方向。
平台稳定性保障。为了保证同步任务执行的效率最大化,同时互相不会因为资源会抢占或某些同步任务的异常对其他任务造成影响,在精卫的系统设计中,支持多个服务节点作为任务执行的集群,通过统一的任务调度系统(Zookeeper集群),将任务分配到集群中的各节点并行执行。
为了保证任务间不会因为同步任务性能或异常造成互相的干扰,采用了每个同步任务都是独立Java进程的方式运行,出现异常该任务自动终止。任务调度系统会定期轮询任务列表,发现任务缺少立即抢占式启动该任务。
心跳+报警。运行集群与ZooKeeper采用定时心跳的方式,将集群节点的运行状态以及任务完成的位点(即目前同步任务处理binlog的进度信息)信息同步到Zookeeper上,如果心跳信息异常或位点时间落后过大则立即报警。在抽取器和分发器发生任何错误复制任务立即转变成STANDBY状态,集群中其他机器上的服务在感知后会立即将自己启动,继续执行前一复制任务。
MySQL主备切换。利用比对主备数据库的状态信息,通过以下顺序,采用手工的方式处理MySQL出现主备切换时进行同步任务的恢复:
1)查看新主库的当前位点Show master status,获取到PA状态。
2)查看老主库拉去新主库的位置Show slave status,获取到PR状态。
3)如果PR>PA,直接用新主库的位点PA切换到新主库上读取。
如果希望通过自动化的方式,实现的思路则可利用binlog里的serverId和时间戳,发现dump的binlog中的serverId发生变化记录变化时间戳,然后在给定的MySQL服务器中查找到有同样变化的数据库,根据探测到的serverId发生变化的时间戳进行回溯,在新的机器符合条件的位点进行dump。
MySQL异常挂掉。利用数据库上binlog文件修改时间,按照以下顺序采取手工的方式进行整个文件回溯:
1)在数据库所在的服务器上找到服务挂掉的时间点。
2)到新的主机上查看找到服务挂掉时间点之前最近的binlog文件。
3)从这个文件的位点开始进行回溯。
如果希望通过自动化的方式自动进行恢复,可同样借鉴MySQL主备切换中提到的自动化实现思路。
(3)友好的用户自服务接入体验
精卫平台是整个电商业务实现数据实时同步复制的统一平台,负责来自上千个不同应用的需求,如果每一个应用的接入都需要平台的技术人员给予入门的培训和支持都是非常大的工作量,也会影响到前端应用的用户体验。所以提供一个用户体验友好,自带常用功能的平台,能针对大部分的业务需求可以让应用方在界面上通过配置的方式就能实现,大大降低接入开发成本。
如图5-15所示,精卫平台给应用方客户提供了Web的配置界面,可让用户针对需要同步的数据源进行设置,并对数据同步的事件类型(增、删、改)和是否进行分表以及分库分表键列等进行设置。
精卫平台的数据库分发器支持一些高级功能,如字段过滤、字段映射、action转换等,如果自带功能不满足需求,可以上传包含自己的业务逻辑的过滤代码。这些功能的使用也提供了界面的方式,让用户对源数据库表中的字段如何映射到目标数据库表进行设置(如图5-16所示)。
图5-15 精卫支持界面配置不同数据源间的数据同步
图5-16 精卫提供的自服务体验提升数据同步服务接入效率
正是有了这样简单易用的用户体验,使得精卫平台在应用的接入效率和用户满意度上都有非常不错的表现。
(4)平台管控和统计
在精卫的平台中,每天都运行着上千亿次的数据同步和复制任务,必然需要对这些任务的执行有一个清晰的管控,甚至可以从中找出对业务数据变化的趋势。实现的方法是定时轮询Zookeeper集群中对应任务的节点进行监控,如图5-17所示。目前提供以下三个方面监控:
心跳监控。
延迟堆积监控。
任务状态、数据监控(TPS、异常)等。
图5-17 精卫平台提供的数据同步监控
采用类似精卫这样的平台实现数据异构索引的好处是,不需要在各个前端应用层的代码中去实现,只需统一通过精卫平台实现。有了这样专业的平台来实现数据同步的效率、服务高可用性、任务管控、统计等,能提供更好的服务。但设计这样的平台确实需要掌握数据库相关知识,以及任务调度、平台管控等技术,甚至需要在各种复杂场景中逐步打磨和完善技术。所以如果有些企业还没有这样数据同步的专业平台,通常会建议采用通过在应用层实现数据的异构索引,具体实现方式在6.3节中重点阐述。
将多条件频繁查询引入搜索引擎平台
采用数据异构索引的方式在实战中基本能解决和避免90%以上的跨join或全表扫描的情况,是在分布式数据场景下,提升数据库服务性能和处理吞吐能力的最有效技术手段。但在某些场景下,比如淘宝商品的搜索(如图5-18)和高级搜索(如图5-19),因为商品搜索几乎是访问淘宝用户都会进行的操作,所以调用非常频繁,如果采用SQL语句的方式在商品数据库进行全表扫描的操作,则必然对数据库的整体性能和数据库连接资源带来巨大的压力。
图5-18 淘宝网商品全文搜索
图5-19 淘宝网商品高级搜索
所以面对此类场景,我们不建议采用数据库的方式提供这样的搜索服务,而是采用专业的搜索引擎平台来行使这样的职能,实现的架构如图5-20所示。
图5-20 全文搜索实现示意图
阿里巴巴有自身的主搜索平台,该平台承载了淘宝、天猫、一淘、1688、神马搜索等搜索业务,其核心功能跟业界开源工具,如Iucene、Solr、ElasticSearch等搜索引擎类似,但在数据同步(从数据库到搜索引擎)、索引创建算法、查询执行计划、排序算法等方面针对商品搜索这样的场景做了相应的调整和功能增强。该搜索平台目前已经以阿里云上OpenSearch产品的形态,给有此类搜索需求的客户提供强大的搜索服务,更多关于该平台详细的资料可访问开放搜索服务的官方网站。
简单就是美
在真实的世界中,选择的困难往往是因为充满着各种诱惑,选择A方案,有这些好处;而选择B方案,也会有另外一些好处。
如果在“尽量减小事务边界”与“数据尽可能平均拆分”两个原则间发生了冲突,那么请选择“数据尽可能平均拆分”作为优先考虑原则,因为事务边界的问题相对来说更好解决,无论是做全表扫描或做异构索引复制都是可以解决的。而写入或单机容量如果出现不均衡,那么处理起来难度就比较大。
尽管复杂的切分规则或数据的异构索引能够给系统的性能和扩展性带来显著的收益,但其后面所带来的系统运维复杂度上升也是不能忽视的一个结果。
如果为每一个存在跨join或全表扫描的场景都采用数据异构索引的方式,整个数据库出现大量数据冗余,数据一致性的保障也会带来挑战,同时数据库间的业务逻辑关系也变得非常复杂,给数据库运维带来困难和风险,从而对数据库运维人员的要求和依赖会非常高,所以从系统风险的角度考虑,以82法则,在实际中,我们仅针对那些在80%情况下访问的那20%的场景进行如数据异构索引这样的处理,达到这类场景的性能最优化,而对其他80%偶尔出现跨库join、全表扫描的场景,采用最为简单直接的方式往往是就最有效的方式。