[关闭]
@eric1989 2016-03-14T17:20:25.000000Z 字数 3987 阅读 950

票据中心基础设施概要设计

更新历史

更新时间 更新内容
2016-3-11 1.修改redis3号key和4号key的内容。2.增加了redis5号key。3.在初始化票号池的时候增加了使用5号key推送内容到redis的内容
2016-3-11 14:20 更新了出票逻辑,适应了使用属性进行查询的情况.目前属性查询使用的redis进行快速存储,对业务的限制是开票方式和票据性质必须同时存在

名词说明

设计约束

MongoDB文档预定义

票号段集合

字段 类型 含义
path String 路径值,内容为rgnCode-agenIdCode-placeId-batchCode
rgnCode String 行政区划
agenIdCode String 单位端标识,值为uuid字符串
placeId String 开票点标识,值为uuid字符串
isEBill boolean 是否电子票
invoiceMode String 开票方式
billNature String 票据性质
batchCode String 号段批次,也就是票据代码
bgnBillNo string 号段起始号码,格式可能为00001
ednBillNo string 号段终止号码,格式可能为00100
effDate date 该号段的创建时间,这个创建时间是该号段自身的信息属性
expDate date 该号段的失效时间,意味着该号段在这之后不可以使用
stockWarnNum int 该号段的警戒数量
initState int 号段初始化标志位.0代表未启动,1表示票号段已经分割成功,并且持久化到数据库中,2代表所有的票已经插入redis中并且生成了队列
poolCount int 该号段生成的票号池个数
availableNum int 当前该票号段可用的票数

票集合

字段 类型 含义
path String 路径值,内容为rgnCode-agenIdCode-placeId-batchCode-ticketNum
rgnCode String 行政区划
agenIdCode String 单位端标识,值为uuid字符串
placeId String 开票点标识,值为uuid字符串
isEBill boolean 是否电子票
invoiceMode String 开票方式
billNature String 票据性质
batchCode String 号段批次,也就是票据代码
bgnBillNo string 号段起始号码,格式可能为00001
ednBillNo string 号段终止号码,格式可能为00100
effDate date 该号段的创建时间,这个创建时间是该号段自身的信息属性
expDate date 该号段的失效时间,意味着该号段在这之后不可以使用
ticketNum String 该票的号码
state int 该票的状态。0代表未使用,1代表已经推送到redis,2代表被出票但是客户端未确认,3代表出票并且客户端已确认,-1代表该票过期,-2代表数据出现异常,可能是因为出票的瞬间服务器失效,导致状态没有从1更新为2,系统在后期检查时会暂时先将这张票的状态标记为-2

redis key含义

编号 key名称 存储类型 存储内容 数据示例
1 ticketcenter:中心名称:行政区划:单位端标识:开票点标识:票据代码:批次:池名称 list 票据池,里面存储是具体的票
2 ticketcenter:中心名称:waitfordistributionsegment list 财政端或者客户端放入的待分配的票号段
3 ticketcenter:中心名称:行政区划:单位端标识:开票点标识:用户标识 String 该路径下该用户申请到的票据信息。该key会在指定的时间内触发失效过期
4 ticketcenter:中心名称:行政区划:单位端标识:开票点标识:用户标识:snapshot String 该路径下该用户申请到的票据信息的快照。也就是说和3号key存储的内容是一致的
5 ticketcenter:中心名称:行政区划:单位端标识:开票点标识:开票方式:票据性质:batchCodeList set 存储该路径下所有的批次

票据中心

以票据中心为顶点,以可出票的票号池为叶子节点构成了一个树形结构。具体如下
|--票据中心
|----行政区划
|------单位端标识(uuid)
|--------开票点
|----------票据代码
|---------------票号池(单张票据的存储地址)

可能存在的情况有
1. 某个开票点使用单位端标识和开票点标识(01,02这样的字符串)来取票。但是该开票点没有自己的票号池,此时返回无票号池响应。开票点收到该信息后会完成对上级单位的检索,使用上级单位的信息进行出票请求。

票据中心使用Map结构存储路径和对应的票据池列表。简称票据池存储结构,如Map<String,List<Pool>>

初始化

初始化票号池

票号中心启动时第一个步骤就是初始化票号池。首先执行的步骤是

当将所有的initState!=2的票号段文档对象全部更新到initState=2时,进入下一个步骤。
在redis中初始化票号池的list的步骤如下

在内存中创建Pool对象的这个过程中,在系统初始化的时候做这件事情,会导致pool的存储对象poolMap的值,也就是list中存在一些相同Pool路径(包含了poolname)的pool。这些pool各自持有不同或者相同的availableNum,而且也不一定与内存中的存储ticket的list的长度相同。但是在使用中不影响整体的出票逻辑。因为如果取不出票,后续就会进行删除流程。对于持久化的数据是不造成影响的。

启动定时取待分配票线程

单位端和财政端会不定时的往票号池中放入新的票号段。所以需要有一个定时的线程去不断的将这些票从待分配池中取出来分配给不同节点的票号池。定时取待分配票的逻辑如下:


上面的流程结束后,就可以在MongoDB的票号段集合中查询到该文档记录。接着使用该票号段记录来初始化对应的票号池,流程如下。

上面的流程结束后,redis中已经存在了初始好的票号池(针对上面的路径)。此时需要在程序的内存中创建对象池对象与redis中的数据关联起来。步骤如下

出票

客户端会使用如下参数进行出票请求

出票确认

客户端请求票的时候,不会马上就消耗掉。需要发送一个出票确认信息来表明票据确实被使用。使用与出票逻辑相同的参数来表明该出票被确认消耗.操作逻辑如下

  1. 使用与出票相同的参数进行确认出票。
  2. 根据参数构建3号key和4号key。将redis中的对应key的内容删除。
  3. 使用参数构建查询Path,对MongoDb的票据集合的对应文档进行state更新到3.

出票确认包含了对单位端的一个约束。也就是单位端在拿到票之后需要确认一次该票并未在其业务系统中使用过,如果已经使用过(也就是上一次的出票确认请求丢失或者被异常处理),需要先发送一次出票确认。然后再执行一次出票请求来得到新的票。

票据回收

由于异常原因(客户端不当下线,客户端回送出票确认信息失败等),导致使用5号key存储的票据信息超时。
超时的时候触发回收机制。票据回收需要和客户端业务系统进行确认。因为可能是因为客户端回送的出票确认信息失败导致,而实际上票据确实已经被消耗掉了。
纸质票和电子票的回收规则也不相同。电子票可以直接放入到池子中就可以使用了,而纸质票现在的回收规则尚未确认。由于票据在业务上存在过期机制。可以考虑这些票据全部都让其过期而不是回收。这样在业务上会简单许多。因为实际上发生票据回收在业务上也是很少的。

业务并发分析

Pool对象初始化和Pool对象删除

在取票分配线程中的第三部具有增加Pool对象的步骤。而在取票操作中也可能存在删除Pool对象的操作。首先需要明确的是,在redis中,同一个路径下的list只有一份。比如总数为100份票据的list。但是在系统中这100份可能是两次50份分配造成的结果。那么在内存中存在两个pool,他们的路径都是相同的,而号段是不同的(起始号-结束号)。他们的availableNum可能都是50.在这样的设定下,并发删除和添加就不会互相影响,也不会造成redis或者内存中的数据混乱

数据库票号段文档的availableNum数字可能比实际值大

这个问题是由于在出票过程中,执行到票号段文档的availableNum的原子减操作之前发生了异常。导致票从redis中弹出,但是数据库却没有对应的数量减操作。这会导致内存的Pool认为自己还能取票,但是取票后却失败的问题(假定此时只有一个Pool)。该问题在程序中可以正常忽略,因为不会影响到数据的持久性。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注