Newbe.Claptrap 框架非常适合于解决具有并发问题的业务系统。火车票售票系统,就是一个非常典型的场景用例。

本系列我们将逐步从业务、代码、测试和部署多方面来介绍,如何使用 Newbe.Claptrap 框架来构建一个简易的火车票售票系统。

吹牛先打草稿

让我们来首先界定一个这个简易的火车售票系统所需要实现的业务边界和性能要求。

业务边界

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

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

业务用例

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

性能要求

  • 查询余票和下单购票消耗平均不得超过 20ms。该时间仅包含服务端处理时间,即页面网络传输,页面渲染等等不是框架关心的部分不计算在内。
  • 余票查询可以存在延时,但不是超过 5 秒钟。延时,即表示,可能查询有票,但是下单无票的情况是被允许的。

难点分析

余票管理

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

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

例如:当前我正在售卖原产自赛博坦星球的阿姆斯特朗回旋加速炮,红色和白色两款分别一万个。那么用户在下单时,只要分别控制红色和白色两款的库存分别不超卖即可。他们之间没有相互关系。

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

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

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

起终点 余票量
a,b 1
a,c 1
a,d 1
b,c 1
b,d 1
c,d 1

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

起终点 余票量
a,b 0
a,c 0
a,d 1
b,c 0
b,d 1
c,d 1

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

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

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

余票查询

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

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

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

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

Claptrap 逻辑设计

Actor 模式是天生适合于解决并发问题的设计模式。基于该理念之上的 Newbe.Claptrap 框架自然也能够应对以上提到的难点。

最小竞争资源

类比多线程编程中 “资源竞争” 的概念,这里笔者提出在业务系统中的 “最小竞争资源” 概念。借助这个概念可以很简单的找到如何应用 Newbe.Claptrap 的设计点。

例如在笔者售卖阿布斯特朗回旋加速炮的例子中,同款颜色下的每个商品都是一个 “最小竞争资源”。

注意,这里不是说,同款颜色下的所有商品是一个 “最小竞争资源”。因为,如果对一万个商品进行编号,那么抢购一号商品和二号商品,本身其实不存在竞争关系。因此,每个商品都是一个最小竞争资源。

那么在火车票余票的例子中,最小竞争资源则是:同一车次上的同一个座位。

正如上面所述,同一车次上的同一座位,在选择不同的起终点是,余票情况时存在竞争关系的。具体一点,比如笔者想购买 a,c 的车票,而读者想买 a,b 的车票。那么我们就有竞争关系,我们只会有一个人能够成功的购买到这个 “最小竞争资源”。

这里有一些笔者认为可用的例子:

  • 在一个只允许单端登录的业务系统中,一个用户的登录票据就是最小竞争资源
  • 在一个配置系统中,每个配置项都是最小竞争资源
  • 在一个股票交易市场中,每个买单或者卖单都是最小竞争资源

这是笔者自己的臆造词,没有参考其他资料,如果有类似资料或者名词可以佐证该内容,也希望读者可以留言说明。

最小竞争资源 与 Claptrap

之所以要提及 “最小竞争资源”,是因为在设计 Claptrap 的 State 时,区别最小竞争资源是对系统设计的一个重要依据。

这里列出一条笔者的结论:Claptrap 的 State 至少应该大于等于 “最小竞争资源” 的范围。

结合阿布斯特朗回旋加速炮的例子,如果同款颜色的所有商品设计在同一个 Claptrap 的 State 中(大于最小竞争资源)。那么,不同用户购买商品就会相互影响,因为,Claptrap 基于的 Actor 模式是排队处理请求的。也就是说,假设每个商品需要处理 10ms,那么最快也需要 10000 * 10 ms 来处理所有的购买请求。但如果每个商品都进行编号,每个商品设计为单独的 Claptrap 的 State。那么由于他们是互不相关的。卖掉所有商品,理论上就只需要 10ms。

也就是说:如果 Claptrap 的 State 大于最小竞争资源的范围,系统不会有正确性的问题,但可能有一些性能损失。

再进一步,前文提到在火车售票的例子中,同一车次上的同一个座位是最小竞争资源,因此,我们可以将这个业务实体设计为 Claptrap 的 State 。但如果设计范围比这个还小呢?

例如:我们将 Claptrap 的 State 设计为同一车次上同一座位在不同起终点的余票。那么,就会遇到一个很传统的问题:“如何确保分布式系统中数据的正确性”。对于这点,笔者无法展开来说,因为笔者也说不清楚,就只是草率的丢下一句结论:“如果 Claptrap 的 State 小于最小竞争资源的范围,Claptrap 间的关系将会变得难以处理,存在风险。”

Claptrap 主体设计

接下来,结合上面所述的理论。我们直接丢出设计方案。

将同一车次上的每个座位都设计为一个 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 机制来实现。

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

小结

本篇,我们通过业务分析,得出了火车票余票管理和 Newbe.Claptrap 的结合点。

后续我们将围绕本篇的设计,说明如何进行开发、测试和部署。

实际上,项目源码已经构建完毕,读者可以从以下地址获取:

特别感谢 wangjunjx8868 采用 Blazor 为本样例制作的界面。

最后但是最重要!

最近作者正在构建以反应式Actor模式事件溯源为理论基础的一套服务端开发框架。希望为开发者提供能够便于开发出 “分布式”、“可水平扩展”、“可测试性高” 的应用系统 ——Newbe.Claptrap

本篇文章是该框架的一篇技术选文,属于技术构成的一部分。如果读者对该内容感兴趣,欢迎转发、评论、收藏文章以及项目。您的支持是促进项目成功的关键。

联系方式:

您还可以查阅本系列的其他选文:

  1. Newbe.Claptrap - 一套以 “事件溯源” 和 “Actor 模式” 作为基本理论的服务端开发框架
  2. 十万同时在线用户,需要多少内存?——Newbe.Claptrap 框架水平扩展实验
  3. 谈反应式编程在服务端中的应用,数据库操作优化,从 20 秒到 0.5 秒
  4. 谈反应式编程在服务端中的应用,数据库操作优化,提速 Upsert
  5. docker-mcr 助您全速下载 dotnet 镜像
  6. Newbe.Claptrap 项目周报 1 - 还没轮影,先用轮跑
  7. Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车
  8. Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车
  9. Newbe.Claptrap 框架中为什么用 Claptrap 和 Minion 两个词?
  10. 构建一个简易的火车票售票系统,Newbe.Claptrap 框架用例,第一步 —— 业务分析

GitHub 项目地址:https://github.com/newbe36524/Newbe.Claptrap

Gitee 项目地址:https://gitee.com/yks/Newbe.Claptrap

您当前查看的是先行发布于 www.newbe.pro 上的博客文章,实际开发文档随版本而迭代。若要查看最新的开发文档,需要移步 http://claptrap.newbe.pro

轻松应对并发问题,简易的火车票售票系统,Newbe.Claptrap 框架用例,第一步 —— 业务分析的更多相关文章

  1. Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车

    让我们来实现一个简单的 “电商购物车” 需求来了解一下如何使用 Newbe.Claptrap 进行开发. 业务需求 实现一个简单的 “电商购物车” 需求,这里实现几个简单的业务: 获取当前购物车中的商 ...

  2. 轻松应对并发问题,Newbe.Claptrap 框架中 State 和 Event 应该如何理解?

    Newbe.Claptrap 框架中 State 和 Event 应该如何理解?最近整理了一下项目的术语表.今天就谈谈什么是 Event 和 State. Newbe.Claptrap 是一个用于轻松 ...

  3. 轻松应对并发,Newbe.Claptrap 框架入门,第四步 —— 利用 Minion,商品下单

    接上一篇 Newbe.Claptrap 框架入门,第三步 —— 定义 Claptrap,管理商品库存 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开 ...

  4. COGS247. 售票系统[线段树 RMQ]

    247. 售票系统 ★★☆   输入文件:railway.in   输出文件:railway.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 某次列车途经C个城市,城市 ...

  5. 【cogs247】售票系统

    [问题描述] 某次列车途经C个城市,城市编号依次为1到C,列车上共有S个座位,铁路局规定售出的车票只能是坐票, 即车上所有的旅客都有座.售票系统是由计算机执行的,每一个售票申请包含三个参数,分别用O. ...

  6. 【cogs247】售票系统【线段树】

    售票系统 输入文件:railway.in 输出文件:railway.out 时间限制:1 s 内存限制:128 MB [问题描述] 某次列车途经C个城市,城市编号依次为1到C,列车上共有S个座位,铁路 ...

  7. cogs 247. 售票系统 线段树

    247. 售票系统 ★★☆   输入文件:railway.in   输出文件:railway.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 某次列车途经C个城市,城市 ...

  8. 虚拟节点轻松应对 LOL S11 百万并发流量——腾竞体育的弹性容器实践

    作者 刘如梦,腾竞体育研发工程师,擅长高并发.微服务治理.DevOps,主要负责电竞服务平台架构设计和基础设施建设. 詹雪娇,腾讯云弹性容器服务EKS产品经理,主要负责 EKS 虚拟节点.容器实例相关 ...

  9. 轻松应对C10k问题

    http://blog.csdn.net/u011011917/article/details/17203539 传统的.教科书里的I/O复用等待函数select/poll在处理数以万计的客户端连接时 ...

随机推荐

  1. JavaWeb网上图书商城完整项目--21.用户模块各层相关类的创建

    1.现在要为user用户模块创建类 用户模块功能包括:注册.激活.登录.退出.修改密码. User类对照着t_user表来写即可.我们要保证User类的属性名称与t_user表的列名称完全相同. 我们 ...

  2. 入门大数据---Kylin搭建与应用

    由于Kylin官网已经是中文的了,而且写的很详细,这里就不再重述. 学习右转即可. 这里说个遇到的问题,当在Kylin使用SQL关键字时,要加上双引号,并且里面的内容要大写,这个和MySql有点区别需 ...

  3. Python实用笔记 (5)使用dictionary和set

    dictionary 通过键值存储,具有极快的查找速度,但占用空间比list大很多 举个例子,假设要根据同学的名字查找对应的成绩,如果用list实现,需要两个list: names = ['Micha ...

  4. 51单片机入门(补充)1--与C语言的交接

    我写完上一个文章,发现我写的还是不够全面,所以,这篇文章将会延续上一个文章的内容,并且再次补充新的东西,如果还有什么地方需要补充,还请各位一一指出,如果你已经学过这些东西,大可以直接跳过,假如说之后有 ...

  5. pl/sql案例

    项目生命周期: 瀑布模型 拿到一个项目后,首先:分析需要用到的SQL语句: 其次:分析需要定义的变量初始值是多少,怎么得到最终值: 案例一: 统计每年入职的员工数量以及总数量: SQL语句:selec ...

  6. 基于C#实现DXF文件读取显示

    工控领域的制图软件仍然以AutoCAD为主,很多时候我们希望上位机软件可以读取CAD的图纸文件,从而控制设备按照绘制的路线进行运行,今天给大家分享的是如何使用C#读取DXF文件并进行显示. 公众号:[ ...

  7. EOS基础全家桶(十四)智能合约进阶

    简介 通过上一期的学习,大家应该能写一些简单的功能了,但是在实际生产中的功能需求往往要复杂很多,今天我就继续和大家分享下智能合约中的一些高级用法和功能. 使用docker编译 如果你需要使用不同版本的 ...

  8. C#数据结构与算法系列(二十一):希尔排序算法(ShellSort)

    1.介绍 希尔排序是希尔(Donald Shell)于1959年提出的一种排序算法.希尔排序也是一种插入排序,它是简单插入排序经过改进之后的一个更高效的版本,也称为缩小增量排序. 2.基本思想 希尔排 ...

  9. BZOJ3573 米特运输 题解

    题目 米特是D星球上一种非常神秘的物质,蕴含着巨大的能量.在以米特为主要能源的D星上,这种米特能源的运输和储存一直是一个大问题.D星上有N个城市,我们将其顺序编号为1到N,1号城市为首都.这N个城市由 ...

  10. PE文件格式详解(七)

    PE文件格式详解(七)   Ox00 前言 前面好几篇在讲输入表,今天要讲的是输出表和地址的是地址重定位.有了前面的基础,其实对于怎么找输出表地址重定位的表已经非常熟悉了.   0x01 输出表结构 ...