微信号:kaitao-1234567

介绍:开涛技术点滴

京东订单台账系统简述

2018-11-06 08:55 张子敬

“ 订单台账,用于保存订单的应收和实收数据。通过对账比较应收和实收金额,确保订单可以正常流转”


系统简介

订单台账,用于保存订单的应收和实收数据。通过对账比较应收和实收金额,确保订单可以正常流转。


系统主要分为三个模块:

正向模块:用于对账,确保订单生产,包括订单台账服务、订单台帐任务

务;

逆向模块:用于逆向流程处理,主要用于虚拟资产的退款发起和实收金额的冲销,包括订单台帐退款服务和订单台帐退款任务;

ERP 模块:用于展示订单台帐相关信息和退款数据,方便客服和运维人员查看。


名词解释

订单管道:处理订单任务,包括调用一次拆分、给台账推数据、给ofw推数据、发送下单消息等一系列操作;

订单拆分:将原始单拆分为可以履约的生产单;

OFW:控制订单履约流程;

先款订单:下单后,用户无需在线付款即能正常履约的订单,一般都是用户收货后通过POS机刷卡或者现金支付;

订单应收:下单后,用户需要支付给京东的金额: SKU的实际金额+运费+服务费-优惠;

订单实收:下单时或者下单后,用户已支付给京东的金额,包括虚拟资产(优惠券、京豆、余额、礼品卡)和在线支付等。


正向模块

先款(流程图)


用户下单以后,订单管道就会把订单xml推送到台账系统,台账系统会解析出订单的应收信息和虚拟资产,计算出应收金额。


用户跳到收银台,就会来查询台账的订单应收金额接口,台账计算并返回订单的应收金额。


用户支付之后,支付交易系统会把订单的实收信息推送到台账系统.台账接收到订单的实收信息计算订单实收金额,直接对比订单应收金额,产生对账任务.对账任务异步执行,发送对账消息,同时调用订单中间件接口修改订单状态为暂停。


此时可以理解为订单对账完成.对账完成后的订单,订单中间件会通知OFW系统,OFW系统调用拆分系统返回拆分结果。


如果订单满足拆分条件,拆分系统会把子单的应收推送给订单管道,子单的实收推送给台账系统,同时发送订单拆分消息。


台账拿到子单的应收和实收再次对账,重复上诉流程,进入订单履约流程。


先货(流程图)


先货跟先款的最大区别是生产流程不依赖台账对账,直接推送OFW进入生产。


台账系统订阅订单转移消息,把订单应收信息发送给POS系统。


用户收到货以后通过POS刷卡或者现金支付,T+1日POS系统把订单的实收信息推送到台账,台账对完账以后,同样产生对账任务。


对账任务异步执行,发送对账消息,同时调用订单中间件修改订单状态为完成。


逆向模块

多支付

多支付是指订单的实收大于订单的应收.在发送对账消息时,如果是多支付,会再发送一个多支付的对账消息,通知多支付退款系统,把多余的钱退给用户。多支付需要人工审核,一般是 T+1 退款。


整单退

整单退是指用户在收到货之前取消订单或者受到货时直接拒收.订单实收分为两部分:虚拟资产和在线支付.用户取消订单或者拒收后,最终会发出订单取消消息.

台账系统通过订阅订单取消消息,发起虚拟资产退款,最终通过删单平台把虚拟资产退给用户.在线支付部分由业务系统发起,在退给用户之前,需要冲销台账实收.

冲销成功后,再调用支付系统退款给用户。


售后退

用户收到货以后发起的退款就是售后退款.售后退款审核通过之后,同样是先冲销台账实收,冲销成功后再调动支付系统退款给用户。


ERP 模块

1. 根据订单号查询台账详细信息,退款信息。

2. 批量重置任务。


性能优化

数据拆分

台账根据订单号路由来分库分表,所以扩容比较容易.取一个新的号段即可写入扩容后的库,不需清洗老数据。


由于台账处于生产流程的核心环节,对性能和吞吐量要求都很高,随着分库数量的增加,性能和吞吐量也都有很大的提升。


但台账系统本质上要解决是热点数据的性能问题,不需要所有的订单业务都要求很高的性能,比如逆向退款.这个热点数据可能是几天的,也可能是几周的。


目前正在建设一个归档历史库,把冷数据迁入归档历史库,尽量不用再扩容数据库。


最理想的是应用实例跟数据库打包成一个对账单元,整个台账系统由若干个对账单元组成.当性能不满足业务要求时,我只要增加对账单元就可以了。


有了对账单元,就可以考虑做多中心,把对账单元部署到多个机房.不过关于多中心的设计会更加复杂,比如一个机房挂掉了,能否把这个机房的流量安全并且快速迁移到其他机房,在保证数据无损的情况下。


对账单元这种设计的局限性就是要求路由字段为整型才可行.如果路由字段是字符串,暂时还没有想到可行方案。


异步执行

台账的应收和实收都是异步写入的,即先把应收或者实收数据落到任务表里,在任务执行时才把应收或者实收数据写入到业务数据库。


这样接应收或者接实收的任务是可以独立出来作为一个单独的任务数据库.写任务表是非常快的,而且任务库可以任意扩容。


可以想象成一个漏斗,入口很大,后面是慢慢流下去的,流下去的速度取决于任务的执行速度.对于台账来说,流下去的速度越快越好。


台账的任务执行也做了一些优化.最初的任务模块就是通过部署多个 worker,多个 worker 一起抓任务,抓到任务之后再锁定任务,哪个 worker 锁定成功就执行任务。


这样做的好处就是去中心化,任何一个 worker 节点挂了,不会影响整体任务的执行.缺点也非常明显,同一个任务会被多个 worker 抓到,多个 worker 一起抢任务锁,如果并发量很大会对数据库有一定的压力。


我们首先是接入了财务的分布式任务框架,通过数据分片,让每个 worker 抓取不同的任务,依然是先加锁再执行任务。


感觉扫库的效率还是不够高,当瞬间任务量特别大的时候,任务执行会有一定的延迟,又引入消息二级驱动.每创建一个任务,异步发送一个带任务的消息,通过消息来驱动任务执行。


消息接到任务后同样是先锁定,再执行.分布式任务作为兜底方案,为了避免 worker 跟消息抢任务, worker 默认抓取15S 之前的数据,可动态调整。


这种任务执行的效率和吞吐量都很高,但是毕竟引入了一个中间件,它就可能成为一个风险点.比如消息延迟了,这种情况我就需要人工调整 worker 抓取任务的时间范围。


所以我们又做了一次优化,创建一个任务后,把任务放到异步线程池中去执行,省略任务锁定环节.这样的任务执行几乎是没有延迟的,高效而且稳定。


这样调整之后,基本所有的任务都是通过异步线程池执行的,由于省略了任务锁定环节,也减少了对任务数据库的更新。


如果线程池执行任务失败或者被丢弃,分布式 worker 还是可以抓到这个任务继续执行。


减少事务 串行更新

台账接收到订单实收后就会对账.之前对账分为两个事务,一个是写实收,更新总账实收金额,另一个是对账后更新总账对账状态,生成对账任务。


这样一笔实收过来,就会产生两次更新.把两个事务合并,就只有一次更新操作。


考虑到订单会有多笔实收,需要同时更新总账,就容易产生数据库锁的竞争,导致TP99的跳点.通过redis实现分布式锁,保证同一时刻,只有一个线程更新总账。


业务监控

台账的数据都来自于上游系统的推送,如果有其中一方没推送给台账,就会导致订单不对账不生产,客服或者运维认为台账系统有问题。


经过各种排查,发现可能是上游没有推送订单应收或者实收数据.效率低下,而且非常被动。


所以我们单独做了一个台账业务监控系统,可以监控订单待对账数量,如果超过阈值就自动报警,同时可以作为上线后的监控指标。


还有一些其他指标的监控,最终目的就是为了研发能第一时间发现问题并及时处理。


总结

订单台账本身的业务并不复杂,主要是随着京东订单量的增长,能够保证对账的时效性和准确性。


Q:

感谢子敬的分享。我想问下,通过线程池之行任务的过程中,任务会持久化吗?如何保证不丢数据?


先把任务写入数据库,再通过线程池执行任务。


Q:

子敬好,我对你们根据订单号来分库分表而不用清洗老数据感兴趣,能详细讲一下是怎么路由的么?


比如订单号路由区间 1-1000 分2个库,现在扩容至4个库,再加一个新的路由区间 1000-5000.过来一个订单号,先判断在哪个路由区间,再根据订单号路由。


Q:

非常感谢你的分享。我想问下你们在redis并发锁处理同时更新总账时,如果没有拿到锁会一直等待还是?


拿不到锁,先让它sleep(200),默认重试5次.如果重试之后还拿不到,认为失败,等待任务下次重试。


 
开涛的博客 更多文章 构建一个持续发展的项目 服务器端的图像处理 | 请召唤ImageMagick助你解忧 京东到家订单派发的技术实战 拜托,面试别再问我JVM了!!! 一个后端架构师的体系构建成长之路
猜您喜欢 直播预告 | Unity面部捕捉解决方案课程(第一期) 浅谈redis超时(一) Spark 1.4 新特性概述 黑客破解iPhone 6至少需要5年 移动开发周刊:剖析 Android 消息机制、MultiDex 实现原理