设计

当前查看的版本是由机器翻译自简体中文,并进行人工校对的结果。若文档中存在任何翻译不当的地方,欢迎点击此处提交您的翻译建议。

业务分析

业务边界

该系统仅包含车票的余票管理部分。即查询剩余座位,下单买票减座。

而生成订单信息,付款,流量控制,请求风控等等都不包含在本次讨论的范围中。

业务用例

  • 查询余票,能够查询两个车站间可用的车次以及剩余座位数量。
  • 查询车次对应的车票余票,能够查询给定的车次,在各个车站之间还有多少剩余座位。
  • 支持选座下单,客户能够选择给定的车次及座位,并下单买票。

实现难点分析

余票管理

火车票余票管理的难点,其实就在于其余票库存的特殊性。

普通的电商商品,以 SKU 为最小单位,每个 SKU 之间相互独立,互不影响。

火车票余票,却有所不同,因为余票会受到已卖票起终点而受到影响。下面结合一个简单的逻辑模型,来详细的了解一下这种特殊性。

现在,我们假设存在一个车次,分别经过 a,b,c,d 四个站点,同时,我们简化场景,假设车次中只有一个座位。

那么在没有任何人购票之前,这个车次的余票情况就如下所示:

起终点余票量
a,b1
a,c1
a,d1
b,c1
b,d1
c,d1

如果现在有一位客户购买了一张 a,c 的车票。那么由于只有一个座位,所以除了 c,d 之外的余票也就都没有。余票情况就变成了如下所示:

起终点余票量
a,b0
a,c0
a,d0
b,c0
b,d0
c,d1

更直白一点,如果有一位客户购买了全程车票 a,d,那么所有的余票都将全部变为 0。因为这个座位上始终都坐着这位乘客。

这也就是火车票的特殊性:同一个车次的同一个座位,其各个起终点的余票数量,会受到已售出的车票的起终点的影响。

延伸一点,很容易得出,同一车次的不同座位之间是没有这种影响的。

余票查询

正如上一节所述,由于余票库存的特殊性。对于同一个车次 a,b,c,d,其可能的购票选择就有 6 种。

并且我们很容易就得出,选择的种类数的计算方法实际上就是在 n 个站点中选取 2 个的组合数,即 c(n,2) 。

那么如果有一辆经过 34 个站点的车次,其可能的组合就是 c(34,2) = 561 。

如何高效应对可能存在的多种查询也是该系统需要解决的问题。

Claptrap 主体设计

Train Ticketing System Design

将同一车次上的每个座位都设计为一个 Claptrap - SeatGrain

该 Claptrap 的 State 包含有一个基本信息

类型名称说明
IList<int>Stations途径车站的 id 列表,开头为始发站,结尾为终点站。主要购票时进行验证。
Dictionary<int, int>StationDic途径车站 id 的索引反向字典。Stations 是 index-id 的列表,而该字典是对应的 id-index 的字典,为了加快查询。
List<string>RequestIds关键属性。每个区间上,已购票的购票 id。例如,index 为 0 ,即表示车站 0 到车站 1 的购票 id。如果为空则表示暂无认购票。

有了这数据结构的设计,那么就可以来实现两个业务了。

验证是否可以购买

通过传入两个车站 id,可以查询到这个作为是否属于这个 SeatGrain 。并且查询到起终点对应的所有区间段。只要判断这个从 RequestIds 中判断是否所有的区间段都没有购票 Id 即可。若都没有,则说明可以购买。如果有任何一段上已有购票 Id,则说明已经无法购买了。

举例来说,当前 Stations 的情况是 10,11,12,13. 而 RequestIds 是 0,1,0。

那么,如果要购买 10->12 的车票,则不行,因为 RequestIds 第二个区间已经被购买。

但是,如果要购买 10->11 的车票,则可以,因为 RequestIds 第一个区间还无人购买。

购买

将起终点对应在 RequestIds 中所有的区间段设置上购票 Id 即可。

将同一车次上的所有座位的余票情况设计为一个 Claptrap - TrainGran

该 Claptrap 的 State 包含有一些基本信息

类型名称说明
IReadOnlyList<int>Stations途径车站的 id 列表,开头为始发站,结尾为终点站。主查询时进行验证。
IDictionary<StationTuple, int>SeatCount关键属性。StationTuple 表示一个起终点。集合包含了所有可能的起终点的余票情况。例如,根据上文,如果该车次经过 34 个地点,则该字典包含有 561 个键值对

基于以上的数据结构,只需要在每次 SeatGrain 完成下单后,将对应的信息同步到该 Grain 即可。

例如,假如 a,c 发生了一次购票,则将 a,c / a,b / b,c 的余票都减一即可。

这里可以借助本框架内置的 Minion 机制来实现。

值得一提的是,这是一个比“最小竞争资源”大的设计。因为查询场景在该业务场景中不需要绝对的快速。这样设计可以减少系统的复杂度。

Id

Train Ticketing System Id