1

发票业务介绍

发票平台主要业务是帮助企业开具发票,并记录订单、发票、申请单等信息以及同步状态。

企业用户发票业务呈现以下两个特点:

一方面企业用户请求相对较小,企业大多选择集中处理发票业务,且绝大多数企业在月末或者月初集中开具发票。这和个人用户随订单开票的方式明显不同。

另一方面企业用户单次请求的数据量通常比较大。这主要是由于企业自身性质决定的。这就导致企业用户单次请求的数据量过大,系统如果只是通过简单的接口调用进行处理,很难满足用户的性能要求,而且也会降低系统的稳定性,增加请求的超时率和失败率。

此外,随着企业用户业务量增加,企业用户自身所需处理的数据随之增大(如图一所示),部分用户数据甚至超过了百万。

图一 某公司每月需要开具发票的订单数量

传统的小批次数据(500订单)处理使得用户不得不进行大量的重复工作,既增加人力投入又耗费大量时间(如图二所示),这就产生了万单合开的业务需求。

图二 某公司传统方式开具发票的次数(500单)

2

万单合开流程

目前发票平台的万单合开流程主要分为以下六个部分(如图三所示):

图三 万单合开流程图

1

用户提交请求流程。

用户获取订单数据,然后将订单数据存储到京东云存储中,然后将必要的申请信息提交到发票平台,平台做一些基本校验后,若通过,则将申请推送至任务引擎,等待后续处理,至此对于用户来说已经完成了发票开具的大部分工作,后续操作完全由发票平台处理。

这样做的目的主要是为了有更好的用户体验,避免用户长时间的等待。

2

校验阶段。

校验阶段主要是对用户提高的订单数据做有效性和正确性校验。

基于用户请求有大量的订单,若同步校验会导致单个线程的执行时间过长,也不利于系统的并发。

在校验阶段,首先将校验任务拆分为多个校验子任务,校验子任务异步并发执行,降低校验时间,提高系统的吞吐量。

3

存储阶段

存储阶段是将用户提交数据存储到数据库的过程。

存储阶段需要存储申请单表和订单申请单关系表(用于记录订单和申请单的对应关系),而且需要保证事务性。但由于数据量过大,若事务执行,则会将数据库锁较长时间,影响其他线程的数据库操作,降低数据库性能。

我们将存储任务拆分为多个子存储任务。子存储任务也同样是异步并发的存储数据至数据库。当所有订单存储完成后再存储申请单。

4

审核阶段

审核阶段相对较简单,用户审核申请单是否可开具发票,若审核通过,则可将申请单提交至财务开具发票。否则,将申请单驳回,并更新申请单、订单、关系表的状态。

5

发票开具阶段

发票开具阶段主要是组装相关信息,将申请单推送至财务系统,并由财务系统开具发票。若信息有误,申请单会被财务系统驳回,并发送驳回的MQ消息;若正确,则开具发票,并发送发票开具的MQ消息,以便各系统进行相关操作。

6

更新阶段

更新阶段主要用于更新系统中各个信息的状态。更新主要在用户驳回、财务开具发票财务驳回申请单、财务作废发票时触发。此时同样存在存储阶段的大事务问题,因此将更新任务分为若干子任务,子任务异步并发的执行。

3

万单合开要素

图四 万单合开设计的四要素

万单合开设计有四个要素:

数据交换异步分组一致性(如图四所示)。下面对四个要素做详细的解释说明。

1

数据交换

数据交换是实现万单合开的前提条件。

传统使用接口进行数据传递和交换的方式,对于万单合开存在多种问题,例如接口限制、处理超时等等。因此使用合适的数据交换方式成为了处理万单合开的基础条件。

万单合开选择京东云存储作为数据中间件进行数据交换。此外,在分布式环境下可以选择FTP、公有云作为数据交换的中间件,甚至可以根据自身需要定制研发数据交换中间件。

在使用数据中间件也应该考虑如下问题:

1、 数据中间件防重。由于数据中间件中的数据都是由各个外部系统写入,因此如何做到防重,避免数据被误操作是一项很重要的工作。万单合开将系统进行分组,对系统进行防重,同一系统下的数据由各个系统自己负责防重,这样减少了系统设计的复杂度。

2、 数据中间件存储的内容。虽然数据中间件可以存储各种各样的数据,但是我们建议存储的内容尽量单一化,即只存储一类数据,比如万单合开中只存储订单号,订单号用逗号隔开或者换行符。当系统获取这些数据时能够快速解析数据。

2

异步

异步是实现万单合开的必要手段。

数据异步可以有效的降低接口的时延,在没有同步要求的情况下需要优先考虑。在高并发情况下可以有效的防止性能瓶颈,起到“削峰”的作用。在系统设计可以针对业务流程将业务分成多个异步过程进行处理。

从图三中所示,万单合开分成了六个异步流程,各个异步流程是通过MQ或者任务引擎进行触发,这样就使得万单合开各个流程相互解耦,也大大提高了系统的复用性。

3

分组

分组是实现万单合开的核心。

万单合开会导致系统出现瓶颈的主要问题在于数据量过大,所以一种很简单的方式就是将其拆分成多个小请求。

分组可以实现数据处理的并发,提高系统的吞吐量,充分利用系统资源。此外,分组也能够减少程序的处理时长,避免接口超时等。

4

数据一致性

数据一致性是万单合开的最终保证。

由于数据采用了异步的分批次的处理,且系统采用分布式部署,导致数据处理过程中存在众多的分布式事务。

分布式事务的实现相对来说较为复杂,因此系统考虑保证数据的最终一致性。

我们系统中使用多种方案来保障数据的一致性,包括失败重试、JMQ、任务引擎、定时任务(定时worker)等。

4

万单合开使用场景

1

数据量较大

万单合开适用于需要处理的数据量较大的场景。由于万单合开的处理流程较为复杂,实现较困难,若处理数据较简单,可以只采用万单合开中的部分方式进行处理,减少系统的复杂性。

2

实时性要求不高

万单合开设计处理流程较长,处理逻辑较为复杂,若对实时性有要求则不建议完全采用该方式进行处理。

3

要求最终一致性

万单合开采用分布式设计,万单合开目前仅保证最终一致性,在中间阶段会存在数据不一致的情况。若要求强一致性,则需要对强一致性部分做特殊处理。