【系统设计】12306架构设计难点(上)

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!

在我后台回复 「资料」 可领取编程高频电子书
在我后台回复「面试」可领取硬核面试笔记

文章导读地址:点击查看文章导读!

感谢你的关注!

12306 架构设计难点

12306 中有哪些难点呢?

参考:B 站 up 主《极海 Channel》,海哥讲的非常好!

先从业务角度上来说的话:

  • 对于抢票来说,瞬时抢票会导致对服务器有瞬间很大的压力,因此从业务设计上来说需要将抢票的压力给分散开,比如今天才开启抢 15 天之后的车票

  • 对于库存来说,车票库存的设计是个难点,就比如 A -> B -> C -> D 共 4 个车站,假如乘客买了 B -> C 的车票,那么同时会影响到 A->C,A->D,B->C,B->D,涉及了多个车站的排列组合(这里计算是比较耗费性能的)

    那么这里就涉及到了 “读扩散”“写扩散” 的问题,在 12 年的时候,12306 使用的就是读扩散,也就是在扣减余票库存的时候,直接扣减对应车站,而在查询的时候,进行动态计算,而写扩散就是在写的时候,就动态计算每个车站应该扣除多少余票库存,在查询的时候直接查即可

    12306 是读多写少的场景,海哥认为使用写扩散比较好一些,这样可以减轻查询端的压力

  • 对于扩容来说,在节假日与非节假日 12306 的流量差别是非常大的,因此必须要有动态扩容的能力

那么在技术角度上来看,难点主要有:

  • 首页读多写少,可以给首页部分内容做静态化处理,比如个人身份的信息、列车班次等不会变化的信息
  • 抢票时,是一个高并发的操作

12306 为什么选择 Pivotal Gemfire 而不是 Redis 呢?

Redis 在互联网公司中使用的是比较多的,而在银行、12306 很多实时交易的系统中,很多采用 Pivotal Gemfire 作为解决方案

Redis 是开源的缓存解决方案,而 Pivotal Gemfire 是商用的,我们在互联网项目中为什么使用 Redis 比较多呢,就是因为 Redis 是开源的,不要钱,开源对应的也就是稳定性不是那么的强,并且开源社区也不会给你提供解决方案,毕竟你是白嫖的,而在银行以及 12306 这些系统中,它们对可靠性要求非常的高,因此会选择商用的 Pivotal Gemfire,不仅性能强、高可用,而且 Gemfire 还会提供一系列的解决方案,据说做到了分布式系统中的 CAP(常识:分布式系统中,CAP 无法同时满足)

12306 的性能瓶颈

12306 的性能瓶颈就在于余票的查询操作上,上边已经说了,12306 是采用读扩散,也就是客户买票之后,扣减库存只扣减对应车站之间的余票库存,在读的时候,再来动态的计算每个站点应该有多少余票,因此读性能是 12306 的性能瓶颈

当时 12306 也尝试了许多其他的解决方案,比如 cassandra 和 mamcached,都扛不住查询的流量,而使用 Gemfire 之后扛住了流量,因此就使用了 Gemfire

Gemfire 的亮点

Gemfire 的存储和计算都在一个地方,它的存储和实时计算的性能目前还没有其他中间件可以取代

但是 Gemfire 也存在不足的地方,对于扩容的支持不太友好的,因为它里边有一个 Bucket 类似于 Topic 的概念,定好 Bucket 之后,扩容是比较难的,在 12306 中,也有过测试,需要几十个 T 的内存就可以将业务数据全部放到内存中来,因此直接将内存给加够,也就不需要很频繁的扩容

db-engines.com 这个网站可以对比主流数据库之间的差异

每个车站余票的设计

就比如, A->B->C->D 共 4 个车站,车上只有 100 个座位,给哪些区间分配多少余票呢?

这个是通过运营部来进行设计,首先考虑的肯定是要盈利,远途票价比较贵,因此比较倾向于远途的旅客,因此不会存在 B->C 站点比较火爆而导致 A->D 买不到票的情况

但是短途旅客又不能没有票,因此给每个车站都会放置一些余票

余票库存的表如何设计?

这里的设计思路都是猜测的,并不一定是 12306 真实设计方案

12306 余票库存的表的设计是非常特色并且重要的

首先说一下需要几个表来表示余票的库存信息:

1、基础的车次表:表示车次的编号以及发车时间等具体的车次信息,属于比较稳定的数据

2、车的座位表:表示每个座位的具体信息,包括在几车厢、几行、几列,以及 该座位的售卖情况

3、车的余票表:通过座位表可以计算出每个车位在各个车站区间还有多少余票,但是动态计算比较浪费性能,因此再添加余票表,通过定时计算余票信息放入到余票表中,提高查询的性能

(其实还应该有一个车厢表,不过不太重要,这里直接就省略了)

这里说一下这 3 个表的对应关系:

比如车次为 K123,该车上有很多的座位,每个座位对应座位表中的一条数据

而余票表指的是 K123 车次上,硬座、硬卧、软卧、无座各有多少张余票,余票表的信息可以由座位表来计算得到

接下来说一下如何通过通过座位表来表示用户购买的车票:

12306 中的车票信息其实是比较复杂的,因为各个车站之间是有依赖关系的,比如 4 个车站 A->B->C->D

如果乘客购买 B->C 的车票的话,不仅 B->C 的库存要减一,B->D 的库存也要减一,这是排列组合的情况,可以考虑通过二进制去简化车票的表示

在座位表中,我们设置一个字段 sell varchar(50) 表示该座位的售卖情况,如果该车次有 4 个站 A->B->C->D,那么 sell 字段的长度就为 3,sell 字段的第一位表示该座位 A->B 的票是否已经被买了,第二位表示 B->C 的票是否已经被买了…

如果乘客购买 B->C 的车票,则 sell 字段的值为:010

如果乘客购买 B->D 的车票,此时发现该座位在 B->C 已经被卖出去了,因此不能将该座位出售给这位乘客

如果乘客购买 C->D 的车票,则 sell 字段的值为:011 ,表示 B->C,C->D 都已经有人了

通过余票表提升查询性能

这里余票表就相当于是数据库中的视图

如果要去查询一个车次中某一个类型的余票还有多少,还需要去对座位表进行计算,这个消耗是比较大的 ,因此通过余票表来加快对于余票的查询

可以定时去计算座位表中的数据,将每种类型的座位的余票给统计出来,比如:

硬卧:xx张
硬座:xx张
软卧:xx张
...

再将余票表的信息给放入到缓存中,大大提高查询的性能

我们在使用 12306 的时候,也会发现,有时候显示的有票,但是真正去买的时候发现已经没有余票了,这就说明 12306 没有保证实时的一致性,只要保证了最终一致性即可,也就是用户真正去买的时候,保证对于余票数量的查询是准确的就可以了

怎么避免远程旅客买不到票的情况:

这个就是处于业务方面的考虑了,比如 A->B->C->D,对于一个车次中的座位来说,如果 B->C 的乘客非常多,那么是不是就会导致 A->D 买不到票了?

其实不会的,我们可以在业务层面去避免这个问题,比如给每个车站区间都留有一些余票,那么就不会因为某一个区间非常火爆,而导致其他乘客买不到长途的票了

至于具体留多少余票,这个就不是我们考虑的事情了,营业部根据具体的实际情况以及盈利情况来定一下各个区间预留多少票

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

11来了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值