前言(2018年03月21日)
随着美团配送业务的飞速发展,单量已经达到千万级别,同时每天产生的资金额已经超过几千万,清结算系统在保证线上服务稳定可靠的前提下,如何系统化的保障资金安全是非常核心且重要的课题,配送清结算系统经过近3年的建设和打磨,在资金安全保障的多个方面均有一些总结和实践,保障资金安全是值得系统思考的课题,只言片语难以全面概括,需要更多的着墨才能较完整阐述,本文侧重点会阐述“对账”的概念,在支付&清结算领域,这是一个非常重要的专业名词,下文将介绍“对账”在分布式系统建设中的实践和解决方案,力求在系统覆盖度、资金准确性、时效等多个维度为系统资金安全保驾护航,实现更健壮可靠的资金履约。
背景&问题
随着美团外卖配送事业的蓬勃发展,配送清结算业务的复杂性也在不断的增高,总结起来,主要有以下几个特点:
- 场景多:包括专送、众包、快送、跑腿、外部单等多条业务线;订单补贴、活动发放、奖惩、餐损、打赏、保险等多种结算场景;对接外部十多个系统。
- 链路长:清结算内部经历定价、记账、汇总账单、付款等多个流程。
- 单量大:目前日单量已达到千万级别。
在这样的业务背景下,我们的系统可谓险象环生。因业务高度复杂,稍有不慎就会出现问题,面对千万级的日单量,同时还要确保结算金额的准确,这就让我们对问题的容忍度变得极低。这也给我们的资金安全保障造成了巨大的挑战。下面我们列举了一些系统日常运行过程中出现的问题。
场景
可以看出,这些都是一些上下游交互的边界场景,以及遇到的问题。当然不仅限于这些场景,凡是有系统交互、数据交互边界的场景,都会出现此类问题,我们称之为“一致性问题”。经粗略统计,我们清结算系统建立以来有70%左右的问题都属于一致性问题。
导致一致性问题的原因有很多,诸如:
- 幂等、并发控制不当。
- 基础环境故障:比如网络、数据库、消息中间将发生故障。
- 其他代码bug。
目前配送的日结算金额已达到千万级别,每个一致性问题都有可能给我们整个美团造成巨大的损失。因此如何解决系统的一致性问题成为我们保障资金安全的重中之重。关于一致性问题,业内已经论述的非常成熟了,搜索引擎中搜索“一致性问题”,随处可见此概念的定义、问题阐述、意义以及解决思路,诸如:
- 强一致性协议: 两阶段提交、三阶段提交、TCC (Try-Confirm-Cancel)等
- 最终一致性: 主动轮询、异步确保、可靠消息、消息事务等
这些手段的目标都是在事中避免问题的发生。但是在实际场景中,无论是系统的内部逻辑还是外部环境都十分复杂多变、不可预知,我们很难完全避免问题。因此事后对于问题数据的发现以及修复就显得尤为重要。这些也正是我们这篇文章要论述的“对账”的核心使命。我们力求总结对账领域内最专业的思路和方法,并结合自身的业务特点,建设配送清结算的对账体系,构筑配送资金安全的坚固防线。在系统的整个构建过程中我们主要围绕以下几个目标:
- 场景覆盖的完整性:无死角覆盖清结算业务涉及的各个场景。
- 问题发现的准确性:能够准确的发现问题,保证不漏报,不误报。
- 问题处理的实时性:尽可能缩短问题处理的周期,极力避免可能造成的损失。
下面开始正式介绍美团配送清结算对账体系的构建经验。
对账的定义
对账的概念随着金融、互联网行业的发展,定义上也经历了几个阶段的变化,如下:
- stage 1 :对账最初来源于会计核算,是为保证账簿记录正确可靠,对账簿中的相关数据进行检查和核对的工作。
- stage 2 :随着互联网金融或电商行业的发展,对账也扩大了应用范围,这一时期,对账是指在固定周期内,支付使用方和支付提供方(银行和第三方支付)相互确认交易、资金的正确性,保证双方的交易、资金一致正确。
- stage 3 :从广义来看,所有的跨端系统之间的数据核对都应该叫对账,主要是检查和发现数据在流转过程中的不一致问题。通常分为信息流的核对和资金流的核对。信息流核对主要是对业务数据之间的核对,资金流是对资金交易数据进行核对。
对账系统的构建思路
系统概况
配送结算做为核心交易履约系统,上游对接了订单、奖惩、活动等十多个外部系统,下游又承担了对接支付平台、财务系统的职责,不仅“承上启下”,而且涉及业务复杂。而系统内部又历经定价、计费(清算)、记账、汇总账单、付款等多个环节,系统的高度复杂性给对账的全面性和准确性造成了极大的困难,如图:
为了系统更加专业化的实现对账、做好对账,我们对支付、清结算等资金领域进行了体系化的调研和学习,并结合业务的自身的特点,总结了一套对账系统构建的思路方法,并基于该思路进行了较完整的系统化实现。
设计思路
从整体来看,按照时序维度的先后,系统对账主要分为三阶段的工作。分别是数据准备、数据核对和差错处理。在对账专业概念中,数据核对和差错处理又叫轧账和平账。三个环节紧密相连,从前期准备、问题发现、问题处理三个角度展开对账工作。
数据准备
数据准备,顾名思义,我们需要把对账所需的全部数据,接入到我们的对账系统。该模块主要实现两个目标:
- 为不同的外部系统提供多元化的接入机制。
- 通过数据适配的手段把外部数据以统一的格式进行转换和存储。
在数据接入层,我们会针对不同的数据接入方提供三种不同的数据接入模式。
- 数据拉取:我们主动拉取数据,并通过数据适配的方式,将数据存储到对账数据池中。
- 数据推送:由数据接入方将数据通过ETL(Extract-Transform-Load)等方式直接推送到我们的对账数据池中,数据格式由数据接入方自行适配。
- 文件上传:我们会提供标准的文件模板,由数据接入方填充数据,通过文件上传的方式将数据接入到我们的数据池。
其中第二种方式是我们最优选择的,因为数据推送这种形式对于数据接入方来说只需要一次性编写相关的代码,定期运行,一劳永逸,减少了人工上传的成本。对于我们结算来说,也不需要感知对方的数据格式以及业务逻辑。
数据核对(轧账)
数据核对是对账中最核心的一个阶段。其目标是发现问题数据。数据核对阶段我们的两个目标是保障数据核对的覆盖度和准确性。经过总结和梳理,数据核对过程可以分为以下5个环节。
1. 问题梳理
由于数据核对的目标是发现问题,那么我们进行数据核对就要从问题出发,首先明确我们要通过对账发现哪些问题,只有这样才能保证数据核对的覆盖度。经过梳理,我们发现在数据流转中过程中数据的不一致问题可以统一归结为三类,分别是漏结、重复结、错结。我们可以从这三个角度去统一进行问题梳理。下面介绍一下这三种错误类型的具体含义。
- 漏结:发起方有数据,而接收方没有数据。举个例子,目前清结算系统会在订单送达时给骑手结算。如果订单的状态是送达,而没有给骑手生成对应的结算数据。就是一种典型的漏结算场景。
- 重复结:接收方重复处理。还是上面的例子,如果订单送达,给骑手结算了两次,产生了重复的结算数据,就是重复结算。
- 错结:发起方和接受方数据不一致。一般会发生在金额和状态两个字段。比如说订单上的数据是用户加小费3元。结算这边只产生了2元的小费结算数据,就是错结。
2. 对账方式
对账方式主要分为两种,单向对账和双向对账。
- 单向对账:以一方数据为基准进行对账。比如结算跟支付平台,以结算数据为基准和支付平台核对,用来发现结算数据为支付成功,支付平台支付失败等问题。
- 双向对账:以双方的数据互为基准对账。既要保证结算数据为成功的,支付平台也要成功,又要保证支付平台数据为成功的,结算数据也要成功。
显而易见,双向对账更能够全面的发现问题。因此在条件允许的情况下,我们会优先选择双向对账。
3. 对账粒度
对账粒度也分为两种,分别是明细对账和总数对账。
- 明细对账:对双方的每条数据依次进行比对。它的优点是可以准确定位问题数据。缺点是对账口径的设计比较复杂。因为我们需要同时针对漏、重、错三种错误类别设计不同的对账口径,同时还要考虑到业务的边缘场景。稍有不慎,就会影响对账的准确性。
- 总数对账:选择一个维度,进行总数级别的对账。总数级别的对账好处是对账口径的设计比较简单,可以快速实现,不易出错。缺点就是无法定位问题数据,一旦对账发现问题。还需要进一步寻找问题数据。
因此,推荐的做法应该是以明细对账为主,定位具体问题。以总数对账为辅,对明细对账的结果进行复核兜底。
4. 对账口径
对账口径,也就是具体的对账逻辑的设计。我们会提供固定的对账模板,供不同的对账场景选取。如果某些特殊场景对账模板不能覆盖,也可以采取对账逻辑自定义的方式进行对账。
经过总结我们发现,对账的形式无非就是两方比对和自身异常检测两种。两方比对又可以细分为一对一、多对一、一对多。比对方法也主要是分为条目匹配和金额匹配。自身异常检测主要是重复性和异常状态的检测。我们把这些通用的对账逻辑模板化,减少重复的开发工作。
5. 对账时机
数据核对的最后一步就是对账时机的选择。分为离线对账和在线对账。离线对账主要是通过固定的周期进行对账。最短周期为T+1。它的好处是适用性较强,基本可以覆盖所有的对账场景。而在线对账又分为实时对账和准实时对账。实时对账和准实时对账的区别主要是实时对账耦合在结算链路中,可以在发现问题数据时,对结算流程进行拦截,而准实时对账是异步进行的,不具备拦截能力。在线对账有一定的局限性,一方面它依赖于对账数据是否能实时的准备好,另一方面也比较占用系统资源。因此我们的做法应该是以周期对账为主,在某些实时性要求比较高,且条件满足的场景使用在线对账。
差错处理(平账)
差错处理主要是对数据核对过程中发现的问题数据进行处理。我们会建立一个统一结构的差错记录,将数据核对发现的问题进行统一存储。差错记录中的数据会进行二次核对,避免由于日切等原因造成的问题错报。对于那些真实存在问题的数据我们会提供两种解决模式,如果是常见的问题,且有一套标准的解决方案的话,我们会把它系统化,采取系统自动修复的方式;如果系统无法自动修复,那么我们会进行系统报警,并进行人工处理。
对账系统设计实现
总体架构
综上所述,对账体系的整体架构,分为三个模块,分别是离线对账平台,在线对账平台和平账中心。完全是按照我们上面的对账思路设计的。三个模块互相协作,一体化的完成数据准备、数据核对、问题处理三部分工作。由于我们整个清结算系统是围绕不同的费用项建立的,因此费用项也是我们设计对账、执行的对账一个最小粒度的单元。
具体介绍下三个模块。
离线对账模块
离线对账分为三个子模块,分别是数据接入层、对账管理层和对账执行层。
在数据接入层我们提供拉取和推送两种模式,经历一个数据适配的过程,将数据存储到我们统一的对账数据池当中。
在对账管理层当中,我们抽象出了一个对账场景的概念,我们基于对账场景进行对账属性的配置:首先要选取对账双方的数据源;然后进行对账口径编辑,这里提供了自定义和模板选择两种方式;最后配置对账的周期。这里我们是通过cron表达式来进行周期配置的。
在对账执行层,我们会拉取对账数据池和对账核心配置中的相关数据,经历配置解析,数据抽取,策略执行的过程,最终输出对账结果。
在线对账模块
下图左边是在线对账平台的架构图,右边是在线对账的实例。我们通过RPC、监听消息队列(MQ)、监听数据库binlog三种方式进行对账接入。在线对账平台分为管理层和执行层。管理层主要是承担策略编辑、策略绑定和拦截管理的相关工作。而执行层分为异步(准实时)对账和同步(实时)对账两个模块。
右边两图分别是分别是异步对账和同步对账的实例。在异步对账的实例中,是运单和结算单元的对账。
- 运单是什么?对应外卖订单,作为配送内部的基础交易数据。
- 结算单元是什么? 清结算系统内部的模型,和运单是一对一关系,记录运单各个节点的结算状态。
①异步对账:我们分别监听运单和结算单元的Binlog,通过Kafka->Storm的经典架构,进行对账策略的执行。实际的流程比较复杂,这里只是一张简图,大概就是:(细节可以忽略)
收单运单消息后,我们会把对于的运单以List的形式存储到Squirrel(Redis)中,当结算消息来了以后,就把对应运单记录Delete掉。如果有运单记录一直停留在List当中,也就是说明结算消息没有来,应该是发生了漏结算。我们通过过定时任务轮询运单List将问题数据输出。
②同步对账:示例中是结算内部的流程,经历结算单、账单、付款几个流程。因为付款是最后一个流程,如果这个时候数据存在问题,那么就会造成实际的资金损失。因此我们会在付款环节之前,对前面的数据进行对账。如果发现账单和结算单的数据不一致,我们就会进行数据拦截。
差错处理模块
在差错处理阶段,我们会建立一个统一的差错记录模型,核心字段包括对账场景、对账批次,数据来源,错误类型编码和数据处理状态等。通过定时任务定期轮询差错记录的方式发起差错处理流程:首先对差错记录的数据进行二次核对,如果二次核对确认这条数据并没有问题,我们就会回更差错记录的处理状态。如果二次核对发现数据确实有问题。我们会提供两种处理模式。一种是通过系统的手段自动修复。另外一种是通过报警的方式,人为介入。此外我们还建立了一个问题的人工处理模块。可以对一次结算流程的整个生命周期进行回放,并针对特定场景提供一键修复的能力。
小结与展望
按照计划实施后,系统的各个节点都会有行之有效的对账手段覆盖,实现资金安全、数据一致性的保障,示意图:
本篇文章的内容是我们根据业务的特点,经过长期的思考和外部调研,总结的一套关于对账的思路以及实施落地方法。目前我们对账体系还在分布实施阶段,我们最终的目标是:
- 覆盖度:实现全链路无死角的对账。
- 处理效率:对于问题的处理尽可能的去人工化,实现自动化或者工具化。
- 接入成本:后续新的业务场景实现对账尽可能的降低成本。
目前外卖配送的单量与日剧增,资金安全所面临的挑战越来越大。需多次强调的是:资金安全是一个很大的课题,需要投入大量的时间和精力去系统思考,对账只是其中一环。我们目前围绕资金安全进行了一系列的治理动作,未来还将会继续加强我们对于资金安全的理解深度,通过更多的对外交流和学习丰富我们保障资金安全的手段。
作者简介
- 甄超,2015年9月加入美团,配送清结算系统核心成员,专注于清结算架构建设、资金安全治理工作。
- 宏伟,2015年4月加入美团,配送清结算系统负责人,参与了美团配送系统建设的全过程。