首页 存档 技术 查看内容

12306出票的一种算法设计

2018-3-30 13:00 |来自: 互联网 418 0

摘要: 引子12306 一度成为技术圈的评论热点,期间被鄙视过, 也被理解过。在我 2.5 年的技术生涯中, 至少 读到过 8 篇关于 12306 的文章,当然,最早的文章是骂 12306 的。今年的春节也不出意外读到了关于 12306 的文章, ...

引子

12306 一度成为技术圈的评论热点,期间被鄙视过, 也被理解过。在我 2.5 年的技术生涯中, 至少 读到过 8 篇关于 12306 的文章,当然,最早的文章是骂 12306 的。今年的春节也不出意外读到了关于 12306 的文章,当然是对 12306 的理解与包容,甚至是赞美。前面两个春节读到 12306 的文章并没有 引发我的思考,而这个春节,可能由于我将近 1 个月未上班的原因,我倒是认真思考起 12306 的业务 模型,进而在头脑中有了这个初步设计。


本文的目的不是对 12306 作评价,而是基于本人对 12306 业务的理解,自己重新实现一套 12306 的系统设计。 本人对自己的这个设计方案比较满意,认为该方案可以很好的解决 12306 的性能瓶颈。


免喷声明

  1. 由于本人不是 12306 的工作人员,对铁道部的运行方案不是很了解,难免有些分析不当的地方,请专业人士批判指出,请圈外人士勿喷。

  2. 本人对自己的文字表达能力相当有怀疑, 如有疑问, 欢迎交流。

设计步骤
  1. 业务的抽象与提取

  2. 数据结构,算法的设计

  3. 数据库设计


设计容易犯的错误

我们容易忽略 1 步骤,交换 2,3 步骤, 也是设计思路是变成了:

  1. 数据库设计

  2. 算法设计(数据结构被数据库决定了)

业务抽象和提取

以后详细描述, 现在简单提取

Nothing but Motto: 交流的时候统一语言,计算的时候统一量纲。


业务流程

  1. 用户输入起点站,终点站,选择日期

  2. 根据输入找到相应的班次

  3. 查询每个班次的剩余票数

  4. 用户点击购买,填写乘客人信息

  5. 根据起点站,终点站和日期分配票

  6. 准备出票:

  • 用户支付成功,出票

  • 用户超时,票回收(回滚操作)


提取

  1. 系统的核心功能是查票,购票。票作为我们的商品, 我们需要根据具体情况来定义票的概念,合理化票的量纲。

  2. 火车票的最小单位是:1(座位)* 1(个站区间), 我们约定这个最小单位是ticket。后续提到都是指用户购买的票(n 个ticket构造而成)。

  3. 对于有座位的票,要统一票 id(反正我坐火车从来没有中途被要求换个位置)。对于这一点, 解决还是比较方便的, 对于同一座位, 放入一个票池子, 同时限制一张车票,只能从同一个池子里面去。

  4. 对于无座位的票, 统一放入一个票池子,或者分车厢放入几个票池子。

  5. 站票的最小单位是:1(个站区间),由于站票的无固定位置特性,站票的最小单位和座位票有区别,因此站票的数据应该跟着列车的站点走。


抽象

根据上述定义,我们可以构造一张tickets表,座位号为行,站区间为列。假设列车经过 ABCDEFG 7 个站点,上面有 5 个座位号,那么tickets表为(先忽略站票):



实例

火车经过 ABCDEFG 7 个站点, 小王买了一张 B 到 D 的票。

  1. 小王买了几张ticket

    答:2 张,分别为BCCD区间的ticket

  2. 假设小王买的票是 2 号座位, 请标记相关ticket

    答: 如下图:

  3. 下图出票方式是否合理?

    答: 不合理,其中红色的票使得tickets池碎片化。 后续只能出 2 张全程票(AG)。 如果红色的票分配到 1 号座位,后续还可以出 3 张全程票,可使收益最大化,12306 也是要赚钱的了。

    那这样是不是会使得位置特别不均匀呢?确实是的,坐过火车的人会注意到这点的。不过这 并不是什么问题,大家总是会自动向座位空空的地方移。但实际上,这根本不是问题, 这图上的位置不过是逻辑位置而已,把它跟实际物理位置做个映射就行。 学过计算机的人都知道内存有逻辑地址物理地址的概念,他们有个映射关系。

    综上所述:出票分配位置时应该从上向下遍历,找到第一个可用的。

数据结构和算法的设计

这里不讨论我对有关设计的演进了,直接指出最终设计。


tickets 数据结构

我们观察上述图片的时候,有没有想过 白色区域代表 1, 着色区域代表 0, 就可以得到一个内存图。每个座位对应的行用一个int64就可以代替了(我们假设列车最多经过 65 个站点,这个假设应该是合理的,假设不正确也没关系, 不影响我们的算法)。


查询

假设小王要查询 BD 的票,占据第 2 和第 3 个区间。 我们只需要构建位置值 (后续称ticket_value) 6, 分别和每一个位置的值做位与运算,如果结果为 6, 说明位置可用, 遍历一遍, 就可以得到剩余位置数。


购买

假设小王要购买 BD 的票,从可分配的位置里面选取第一个减去


程序系统设计


数据库系统设计

  1. trains

    id

    code

    type




    7

    G77

    高铁

    8

    D63

    动车

  2. sites

    id

    name



    1

    上海

    2

    济南

    3

    北京

  3. site_train_xRef

    site_id

    train_id

    site_nth

    n_sro





    1

    7

    0

    3

    2

    7

    7

    3

    3

    7

    9

    3

  • site_nth 为 0 代表始发站。

  • n_sro 为站票剩余数目,跟着列车的站点走。

  • tickets

    id

    pos_id

    n_remain




    1

    1A

    int_max

    1

    3F

    int_max

    • 建议为每一辆列车建立一个tickets表, 这样也很容易分配到不同的机器上去查询。也可以在 tickets 中加入一个train_id字段。


    查票步骤

    1. 根据site_train_xRef找到符合要求的的列车班次。

    2. 分别各列车计算出票值ticket_value

    3. 分别在找出的班次中检索余票, 判断该座位可用:(n_remain

    声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

    路过

    雷人

    握手

    鲜花

    鸡蛋

    相关分类

    返回顶部