票据系统(补充)

关于票据系统设计在之前的博客中也聊过,今天做一个补充

1、架构

票据系统主要就是和票交所进行交互,围绕这一核心,我们把系统划分为三大部分,分别是:票据网关服务、票据业务服务、票据库存服务。

网关服务:对接票交所,负责和票交所的交互,主要是收发报文。

业务服务:负责票据业务的处理,比如出票、背书、贴现等等。

库存服务:负责票据信息的存储,比如票据的交易、正面、背面等。

我简单画了个草图,大概就是如下图这样:

说明一下:图中“指令服务”和“对接票交所”其实它俩可以看成是一个整体,都是为了对接票交所,我这里就统称网关服务了。指令服务其实是在票交所返回的报文基础上做了封装和其它的业务处理,比如申请的时候做一些校验和参数组装,应答的时候可以统一做待应答处理

1)用户在浏览器上操作,请求发送到服务端,业务服务开始处理,包括各种校验和业务数据存储,以及审批

2)业务服务调用网关服务发送申请报文,并在收到确认报文后进行库存等相关处理

3)业务服务和网关服务是互相调用的,是全双工通信

2、库存

库存包含的表比较多,主要的有:主表、子表、应收/应付表、背面表、交易表、加锁表、余额表等

2.1、交易

所有的业务都需要票交所的中转,因此每次跟票交所的交互就是一次交易。以贴现为例,如果是内部贴现就会有两条交易记录,一条出库交易,一条入库交易,如果是贴现给外部,则申请方只有一条出库交易,应答方只有一条入库交易。

2.2、状态

票据的状态比较多,而且变化也很复杂,目前是直接修改,这样造成的问题就是排查问题的时候都不知道是在哪里被修改的,后续考虑用状态机管理,比如:COLA中提供了一个状态机组件cola-component-statemachine

2.3、挑票

不同的创建可供挑选的票不一样,这个需要配合票据状态来实现。从自己的应收/应付表中选择那些条件的票,这些条件包括但不限定于票据状态、流通状态、风险状态、库存状态、交易类型、票据类型、余额等等

2.4、拆票

发起业务申请,审批通过后,发送报文。这里有两个区间,一个是票据原本的区间,一个是发送的区间。收到票交所返回的确认报文后,进行拆票。实际上,是先预拆票,发给票交所,票交所返回成功后,再真正的进行本地拆票。每拆一次票就多出两个子票,原票置为已拆票已出库,新票为未拆票在库。这样做可以很好的保存拆票历史,一目了然。最后,切记拆票这里要加锁,防止并发问题导致的子票区间错误。

有人问:直接拆,拆完再发票交所,行不行?答案是行,但是很麻烦,如果票交所返回失败,还得把票再合回去。这就好比是一张钞票你先撕成两半,再想恢复如初,想想都觉得麻烦。

2.5、加锁

最简单的加锁就是给某一条数据加一个标识位(或者锁标识的字段),通过修改字段的值来达到加锁与解锁的目的。

在业务申请后需要人工审批,审批通过后才会发报文,对方需要应答,整个过程很长,为了防止在此期间该业务中使用的票被其它业务使用,需要对该业务中用到的票据进行加锁,加锁之后其它业务就不能用了。这里有几张方案:

方案一:直接在票据字表上加一个字段,用来表示该票据是否被锁定。这种方案是可行的,但是直接对整张票加锁的话粒度有点大,这就意味着同一时间这张票只能用于一个业务,即使另一个业务中只是想用部分区间那也不行,因为整个区间都被锁死了。

方案二:加一张表,加锁就往表中插入一条数据,解锁就逻辑删除这条数据。这种方案也是可行的,这个时候就重点要考虑锁粒度的问题,如果只锁票号和区间的话就跟第一种方案没区别。那能不能锁预拆票的区间呢?好像也不太行,同样是2块钱的票,由于区间不同,所以它们是不同的两张票,但是没拆票之前区间都是一样的,所以没法证明你的2块跟我的2块是不一样的,所以,由于票据区间不能重叠,且预拆票的存在,导致不能只锁票号和区间。

方案三:在方案二的基础上,我们锁票号、区间、业务编号。这样的话,这张票可以同时用在多个业务中,但是仅仅这样还不够,还需要扣减占用的金额,为了防止用超了。借鉴一下电商中对库存分段加锁的设计,本来10个库存,可以分成5组,每组2个库存,这样并发能力一下子提示了5倍,各抢各的互不干预。

应收表的票据还有一个余额字段,每次加锁后,直接扣减余额。这样的话,加锁表中的记录就失去了锁的意义,纯粹变成了一个记录而已,一旦业务失败,可以把扣掉的金额再加回去。

从这个意义上讲,有了余额字段,这就相当于没加锁,是的,此处的加锁表是为了做一个记录,谁对那张票加了锁,以便解锁的时候可以恢复余额。

回到最初的出发点上,我们再来思考,其实没有必要担心同一张票同时被多个业务使用,因为一张100块的票,虽然咱俩都用了10块,但是由于票据是可以拆分的,所以你用的10块,跟我用的这10块,不是同一个10块,只要别用超了就行,此时这张票的余额还剩80块。

综上,加锁就是往加锁表中插入数据,同时扣减占用金额,更新余额。解锁就是删除加锁表中的数据,恢复余额。

以上是申请类的业务加锁,还有应答类的业务也需要加锁,这个就不赘述了,直接在待应答表上加锁即可

3、个性化需求

不同的客户有不同的定制化需求,因此如何屏蔽差异,尽可能的复用现有的代码,成为了一个急需解决的问题。

通过分析,这些定制化需求通常表现为在某一个业务之前加校验,或者在业务结束的时候推送数据

思路一:

1)就像模板方法一样,在核心的方法中预留一些方法,以便子类可以做自己的逻辑处理。或者,接口都给出默认实现,不同的项目可以自己实现,也可以用默认的实现。或者用SPI

2)将核心功能做成组件,每个项目自己引入组件,有需要的话可以对组件进行扩展,扩展的方式可以是AOP拦截,或者实现相应的接口

思路二:

引入Groovy,将差异化的代码可以写在配置文件或者数据库中,执行的时候动态生成执行逻辑

 

posted @ 2023-08-17 18:05  废物大师兄  阅读(660)  评论(0编辑  收藏  举报