12306系统

月伴飞鱼 2024-10-25 15:00:42
系统设计
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!

12306成就

创下全球最大实时票务交易系统世界记录,春运一个月抵欧洲一年。

最高可达百万并发,承受了这个世界上能秒杀任何系统的QPS

网站浏览量一天最高超1500亿次,峰值是双11的三倍。

抢票软件推荐

https://www.bypass.cn/

12306系统特点

跟淘宝天猫等相比,业务简单(卖票)。

流量极大。

动态库存。

12306难点

业务角度:

对于抢票来说:

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

对于库存来说:

  • 车票库存的设计是个难点,比如 A -> B -> C -> D 共 4 个车站。
  • 假如乘客买了 B -> C 的车票,那么同时会影响到 A->C,A->D,B->C,B->D,涉及了多个车站的排列组合。

库存扣减

目前 12306 最大的难点,在于库存扣减。

它跟传统的电商网站,最大的不同在于它的库存,它的库存是动态变化的,库存之间会互相影响。

比如有一个组合品的需求,A品是由B品和C品通过不同的比例混合而成,用户下单的时候传过来的是A品这个 SKU

  • 但是库存扣减的时候是把它的组合品的单品(B和C),都去扣一遍的。

我们平时各种商品 SKU 库存的话,它是表里面的一行行记录。

某个行程是:

  • 杭州 -> 武汉 -> 成都。

  • 杭州 -> 成都 ,武汉 -> 成都。

每卖出一张 武汉 -> 成都 的票,杭州 -> 成都 的票也会少一张。

计算耗费性能

一些长途,中间会经过十几个站点,而且有些城市是没有直达的车次的,中间只能换乘。

涉及了多个车站的排列组合,这里计算是比较耗费性能的。

行锁竞争激烈

购买一个行程会涉及多个站点的扣减库存,有可能这些多个站点的库存扣减是放在一个事务中的。

  • 如果是在一个事务中,那么一次下单行为,可能要涉及到几十次库存扣减。

锁范围膨胀,事务就会被拉大,线程数可能迅速被占满,导致数据库可能成为性能瓶颈,并且接口性能也会有所下降。

热点问题

火车站不同的站之间都是一个具体库存,中间的库存扣了之后,那么远的这个站的库存也要扣减。

  • 极端情况下,一些热门城市,中间一些站点可能比较火爆,那两端的人是不是永远买不到票。

性能瓶颈

12306 最大的瓶颈主要是在 IO 上面,这跟 12306 使用读扩散有关。

读扩散和写扩散常见于 订阅/聊天/群聊 系统。

读扩散

在扣减余票库存的时候,直接扣减对应车站,而在查询的时候,进行动态计算。

  • 牺牲了读的性能,去提升写的性能。

写扩散

在写的时候,就动态计算每个车站应该扣除多少余票库存,在查询的时候直接查即可。

  • 牺牲了写的性能,去提升读的性能。

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

12306 使用读扩散的原因可能是对数据实时性有要求。

  • 当扩散队列很长的时候,写入时间存在延时,可能导致不同行程的余票对不齐。
  • 而且涉及排列组合过多,使用写扩散,数据冗余会比较严重,浪费存储成本。

优化抢票性能

将库存放在每台机器的本地,比如总共有 1W 个余票库存,共有 100 台机器,那么就在每台机器上方 100 个库存。

当用户抢票之后,就会在本地先扣减库存,如果本地库存不足,此时可以给用户返回一个友好提示。

  • 让用户稍后再重试抢票,再将用户抢票的请求路由到其他有库存的机器上去。

如果本地库存足够的话,就先扣除本地库存,之后再发送一个 MQ 消息异步的生成高铁票的订单,等待用户支付。

  • 如果用户十分钟内不支付的话,订单就失效,返还库存。

少卖和超卖问题

对于超卖来说,每次用户请求时,先扣除库存,再去生成订单。

  • 这样当库存不足时,就不会再生成订单了,因此肯定不会出现超卖的问题。

对于少卖来说,总共有 100 台机器,每台机器有 100 个库存。

  • 如果其中的几台机器宕机了,那么宕机的机器上的库存就没办法继续售卖,就会出现少卖的问题。

解决少卖问题

可以在每台机器上放一些冗余的库存,如果其他机器发生了宕机,就将宕机的机器上的库存给放到健康的机器上去。

  • 就可以避免机器宕机而导致一部分库存卖不出去的问题了。

需要使用 Redis 来统一管理每台机器上的库存,也就是在分布式缓存 Redis 中存储一份缓存。

  • 在每台机器的本地也存储一份缓存,当扣减完机器本地的库存之后,再去发送一个远程请求扣减 Redis 上的库存。

4c36ef10ac715047928230db74ea62fe

用户发出抢票请求,在本地进行扣减库存操作。

如果本地库存不足,返回用户友好提示,可以稍后重试,如果所有机器上的库存都不足的话,可以直接返回用户已售罄的提示。

如果本地库存充足,在本地扣减库存之后,再向 Redis 中发送网络请求,进行库存扣减。

扣减库存之后,再发送 MQ 消息,异步的生成订单,之后等待用户支付即可。

技术选型

Pivotal GemFire

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

在银行以及 12306 这些系统中,它们对可靠性要求非常的高,因此会选择商用的 Pivotal Gemfire

不仅性能强、高可用,而且 Gemfire 还会提供一系列的解决方案。

img
支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者!