订单中心,是互联网业务中典型的“多键”业务,即:用户ID、商户ID、订单ID等key都有业务查询需求。随着数据量和并发量的逐渐增加,如何设计订单中心“多键”业务的架构,需要考虑哪些因素,是本文将要系统探讨的问题。什么是“多键”业务?所谓“多键”,是指一个元数据中存在多个属性,需要前台在线查询。什么是订单中心业务,典型的业务需求是什么?订单中心是一个很常见的“多键”业务,主要提供订单查询和修改服务。它的核心元数据是:Order(oid,buyer_uid,seller_uid,time,money,detail…);其中:oid为订单ID,主键;buyer_uid为买家uid;seller_uid为卖家uid;time,money,detail,...等是订单属性;在数据库设计方面,一般来说,在业务的初始阶段,单一的数据库,加上查询字段上的索引,就可以满足元数据的存储和查询需求。order-center:订单中心服务,为调用者提供友好的RPC接口;order-db:存储订单数据,并在order、buyer、seller等字段建立索引;随着订单量的增加,需要对数据库进行横向切分。既然有多个key的查询需求,应该用哪个字段来切分呢?如果使用oid进行切分,对buyer_uid和seller_uid的查询需要遍历多个数据库;如果使用buyer_uid或seller_uid进行切分,查询其他属性需要遍历多个数据库;总之,很难有一个完美的解决方案。在推出技术方案之前,我们必须一起梳理查询需求。任何背离业务需求的架构设计都是耍流氓。在订单中心,典型的业务查询需求有哪些?第一类是前端接入,最典型的需求有三类:订单实体查询:通过oid查询订单实体,90%的流量属于此类需求;用户订单列表查询:通过buyer_uid分页查询用户的历史订单列表,9%的流量属于此类需求;商户订单列表查询:通过seller_uid页面查询商户的历史订单列表,1%的流量属于此类需求;前端访问有什么特点?吞吐量大,服务要求高可用性,用户对订单访问一致性要求高,商家对订单访问一致性要求相对较低,可以接受一定的时延。第二种,后台访问,根据产品和运营需求有不同的访问方式:按时间、价格、产品、详情查询;后台访问有什么特点?运营端的查询基本都是批量查询,因为是内部系统,访问量很低,对可用性的要求不高,对一致性的要求也没有那么严格,秒级或秒级的查询延迟甚至十秒钟也是允许的。应该采用什么样的架构方案来解决这两种不同的业务需求呢?重点一:前后台分离的架构设计。如果前端业务和后端业务共享一批服务和一个数据库,可能会造成后台“请求少”的“批量查询”访问“低效”,造成cpu数据库偶尔会瞬间100%,影响正常用户访问前端(比如订单查询超时)。前台和后台访问的查询需求不同,对系统的要求也不同。因此,两者应该解耦,实行“前后台分离”的架构设计。前台业务架构不变,站点接入,服务分层,数据库横向切分。后端业务需求由独立的web/service/db支持,去除系统之间的耦合。对于“业务复杂”、“低并发”、“不需要高可用”、“延迟可以接受”的后端业务:可以去掉service层直接在运营后台的web层通过dao访问数据层;它不需要反向代理或集群冗余;可以通过MQ同步数据,也可以异步离线,牺牲部分数据的实时性;可以采用更适合大量数据允许的“外部索引”或延迟较高的“HIVE”的设计方案;解决了后台业务的访问需求后,前台的oid、buyer_uid、seller_uid如何进行数据库的横向切分?重点2:多维度查询比较复杂,对于复杂的系统设计,应该逐一分解。假设没有seller_uid,怎么破oid和buyer_uid的查询需求呢?订单中心,假设只有oid和buyer_uid的查询需求,会退化为“一对多”的业务场景。对于“一对多”的业务,层次切分应该使用“遗传法”。第3点:遗传法是解决“1对多”业务和数据库横向切分的常用方案。什么是分库基因?通过buyer_uid分库,假设分为16个库,使用buyer_uid%16的方法进行数据库路由。所谓取模16,其实质就是buyer_uid的后4位决定了哪一行数据落在库上,这4位就是分库基因。什么是基因方法子库?生成订单数据oid时,在oid末尾添加分库基因,这样所有同一个buyer_uid下的订单都包含相同的基因,落在同一个分库上。如上图,buyer_uid=666的用户下了一个订单:使用buyer_uid%16分库来决定将这行数据插入到哪个数据库;分库基因为buyer_uid的后4位,即1010;在识别oid时,首先使用分布式ID生成算法生成前60位(上图中绿色部分);将分库基因添加到oid的后4位(上图粉色部分),组装成最终的64位顺序oid(上图蓝色部分);通过这种方式,保证了同一个用户下的所有订单oid都落在同一个库上,并且oid后4位相同,所以:可以通过buyer_uid%16定位到库中;也可以通过oid%16;定位库;如果没有oid,应该怎么断buyer_uid和seller_uid的查询需求呢?订单中心,假设只有buyer_uid和seller_uid的查询需求,会退化为“多对多”的业务场景,对于“多对多”的业务,“数据冗余方式”应该是用于水平分割。如上图所示:订单生成时,通过buyer_uid分库,将分库基因整合到oid中,写入DB-buyer数据库;通过离线异步方式,将数据通过binlog+canal冗余发送到DB-seller库中;买家库通过buyer_uid分库,卖家库通过seller_uid分库。前者满足oid和buyer_uid的查询需求,后者满足seller_uid的查询需求;数据冗余有多种方式:服务同步双写;服务异步双写;离线异步双写(如上图为离线异步双写);重点4:数据冗余是解决“多对多”业务和水平数据库切分的常用方案。不管采用哪种方案,由于两步操作不能保证原子性,总是存在数据不一致的可能。高吞吐量的分布式事务是业界尚未解决的问题。此时的架构方向是最终一致性,并不能完全保证数据。一致性,但要及早发现不一致并加以解决。Point5:终极一致性是高通量互联网业务一致性的普遍做法。保证冗余数据最终一致性的常见方案有3种:冗余数据全调度扫描;冗余数据的增量日志扫描;在线消息冗余数据实时检测;oid/buyer_uid/seller_uid同时存在怎么办?解决办法是:如果没有seller_uid,“多键”业务就会退化为“一对多”业务。这时候就要用到“基因法”分库:使用buyer_uid分库,将分库基因添加到oid中;如果没有oid,“多键”业务就会退化为“多对多”业务。这时就要用到“数据冗余法”分库:分别用buyer_uid和seller_uid分库,冗余数据,满足不同属性的查询需求;如果oid/buyer_uid/seller_uid同时存在,可以综合使用上述两种方案解决“多key”业务的数据库水平切分问题;要点总结了前后端的差异化需求,可以采用前后端分离的架构设计;对于复杂的系统设计,要一一分解;遗传方法是“一对多”业务和数据库水平切分的常用解决方案;数据冗余是一种常见的解决“多对多”业务和数据库水平切分的解决方案;最终一致性,这是高吞吐互联网业务一致性的普遍做法。【本文为专栏作者《58神剑》原创稿件,转载请联系原作者】点此阅读更多该作者好文
