订单流程引擎功能架构 订单流程优化_缓存

本文解决实际问题中,下单流程链路过长,导致购买支付完成之后,不能即时看到已购买服务的问题。在读《亿级流量网站核心技术》书中15.10章节下单系统水平可扩展架构后,深有感悟,并亲自当面请教开涛兄关于下单流程优化的细节,如方梦初醒,总结思想撰成此文,与大家分享。

订单流程背后的万里长征

为什么说是万里长征?其实在商城下单支付完成之后,后续还有很多流程需要处理,如订单完成之后,还需要将订单完成的消息通知给履约系统、结算系统、发票系统等等,而这往往是通过消息队列的方式异步处理的。那如何保证下单流程的高可用和高性能,是交易系统的核心之一。

交易系统一般强依赖数据库(MySQL),而电商平台高流量、高并发的特性会对数据库造成极大的压力,所以,系统会进行分库分表、读写分离和数据异构等等,最终架构殊途同归,通过多台数据库进行接单写服务,然后将数据通过消息队列异构到缓存中提供读服务,如 Redis、Elasticsearch、Solr 等等。

以服务市场下单流程举个栗子,商家在结算页点击下单,下单服务生成订单写入数据库,订单完成之后更新数据库,数据在数据库主从间同步,通过监听数据库 binlog 进行数据异构,写入缓存,再通过缓存提供数据查询服务,简单示意图如下:

订单流程引擎功能架构 订单流程优化_数据_02

但是,上述这些后端的操作流程是对用户无感知的,用户能感知的只是结算支付完成后,页面跳转到我的服务页去查看服务。可是,如果这个过程网络出现抖动,造成消息延迟,则会对用户产生影响,即下单完成之后可能不能立即看到服务。

读《亿级流量网站核心技术》深受启发,简单总结,下单首先会有一组接单数据库提供下单服务,重点是,在写数据库后,同步双写缓存,会通过缓存提供服务查询,数据库中数据会通过 Worker 同步到下游订单中心。

订单流程引擎功能架构 订单流程优化_缓存_03

(图引自:《亿级流量网站核心技术》)

基于此,对服务市场下单流程重新设计,在下单服务写入 MySQL 之后,双写缓存到 Redis,一写新订单列表 ID,二写新订单的详情,后续流程不变,关键在用户查询时,查询服务时对数据进行合并提供完整数据查询。简单示意图如下:

订单流程引擎功能架构 订单流程优化_订单流程引擎功能架构_04

双写缓存的目的是为了保证新订单没有即时同步 Elasticserach 时,也能保证查询服务时能查询到服务。

具体分析,双写缓存和数据异构的数据是否存在数据不一致的情况:

  • 1,双写缓存的数据比数据异构数据新,如缓存10条,异构数据9条,说明订单有延迟,未同步到 Elasticserach 中
  • 2,双写缓存的数据与数据异构数据一致,如缓存10条,异构数据10条,说明订单无延迟,数据同步即时
  • 3,双写缓存的数据比数据异构数据少,如缓存9条,异构数据10条,说明缓存丢数据,但异构数据有最新数据

针对不同的情况,设计查询服务的处理逻辑:查询服务先查询缓存,如果有缓存,则认为有新订单未同步或正在同步,因为缓存数据会在订单完成后,通过监听数据库的 binlog 删除已同步的缓存,但因为有延迟,所以会再查询 Elasticsearch 的最新订单数据,之后对比两份数据,进行整合操作,逻辑介绍如下:

  • 如果缓存的订单 ID 在 Elasticsearch 未查到,则该订单 ID 未同步,从缓存查询订单详情,整合服务列表
  • 如果缓存的订单 ID 在 Elasticsearch 查到,但订单状态不一致,以数据库为准,整合服务列表
  • 如果 Elasticsearch 中存在缓存中没有的订单 ID,以数据库为准,整合服务列表

所以,查询服务的数据可能是由一部分缓存数据和数据异构的老数据组成的。

订单流程引擎功能架构 订单流程优化_订单流程引擎功能架构_05

双写操作是否优雅

为什么不优雅?因为双写可能会出现数据不一致的问题,造成难以排查的问题,如 MySQL 是强事务型的数据库,而 Redis 不是,又因为 MySQL 和 Redis 是两种数据库,所以这又是一个复杂的分布式事务问题,又何况要写两份数据到缓存,所以,这种明显有数据一致性问题的设计,就显得不那么优雅。

但是,一是考虑 Redis 出现问题的概率是很小的,二是数据主要以 MySQL 为主,即使 Redis 数据丢失或不准确,是不会影响合并后的数据,三是双写的简单实现,可以极大的降低系统的复杂度和实现成本,综合来看,又不失为一种合适的架构设计。

总结

架构,对于架构师,是解决问题的工具,如果架构师搞出来的架构不能解决问题,架构对于架构师就是一个玩具。 —— 王新栋

鸣谢

感谢张开涛、王洪涛对本文的校订。