西西河

主题:【原创】无责任推测12306网站遇到的麻烦 -- 代码ABC

共:💬135 🌺246
全看分页树展 · 主题
家园 【原创】无责任推测12306网站遇到的麻烦

首先声明,这是技术贴,也许有点阴阳怪气,但不洗地,咱纯粹看戏。

其次本人无任何内幕信息,也没有参与铁路部门任何项目,所以许多假设仅仅是假设。如有雷同那是运气,如果与事实不符也很正常。

我的假设主要基于两个数字:一个是新闻里说的铁路春运期间,每天平均运送旅客7000万人次,另一个是每列客车可以载1000人上路。另一个假定是12306网站和铁路的内部票务系统无直接的数据连接——这个假定我认为是靠谱的,因为窗口售票并不受网站瘫痪影响,证明铁路内部系统运作正常。因此进一步推断:铁路部门每天给会将一张可售票总表交给网站,里面大致应包含时间、车次、各车次可受票的种类和数量。12306网站则定时提供一张汇总表报告售票情况。

那么我们来分析一下设计这个网上售票系统所需要考虑的问题。

相信通过这几天的表现我们知道整个系统的核心在于用户的使用压力上,其实网站的功能并不复杂,核心的功能(按使用频度顺序)就是票务查询、订票、用户身份验证和注册,另外就是一些杂七杂八的辅助功能,如:订单管理、个人资料修改等等。数据库(假设使用数据库解决方案)内的数据主要就是用户数据、车票数据和订单数据。

我们先看最简单的用户数据部分,这部分相关的操作是注册、登录,当然在订票的时候也会用到,但一方面在订票数据里这应该只是一个外键,另一方面可以暂时缓存在用户的交易会话中,所以实际会操作到这个数据的就是上述两个动作。注册是一次性的,登录则不是,因此查询动作会多一些。但无论如何这个数据表(假设设计成一张表)的查询非常简单,优化也很方便。所以这个表虽然很大(目前数据超过1000万),但乱子不会出现在这个地方。其实12305的登录动作是很快的,那个人数太多只是一个临时措施免得太多人同时进行交易而已,留意一下只要能登录进去那个登录页面反应其实不慢就是这个道理。

我们再看订单数据,这个数据也会很大,因为需要存放每张票的详细信息,如果每天有四分之一的票是通过网络出售的那么这个数据是每天增加近2000万条。不过这个数据表(假设还是一张表)主要的动作就是插入和修改,几乎不会查询。所以优化也不困难。这里设计的麻烦在在线支付和相关的车票数据锁定的问题上。当我们选好票进入支付环节的时候,系统必须首先将选好的票锁定起来,也就是余票查询将会扣减相应的票数。这个操作的复杂性比登录要高就在于这点,需要同时修改两个地方:车票数据和订单数据,而且必须是同时的。在数据库设计上这叫要求事务的完整性。通常这种操作需要通过锁定一些数据记录来完成,也就是当系统修改车票数据和生成订单期间,车票数据和订单相关的车次查询将被暂时中止。这样订票就影响了查询性能。反过来也一样,查询的时候也不允许生成相应车次的订单。其实这有点扯,系统真正要防止的是当只有一张票的时候,如果有多个人同时订票那么只能有一个人成功,虽然他们之前都查到有一张余票。所以实际上需要保证的是一个时间只有一个人可以修改车票信息。因此,为提高性能我们可以放宽对查询准确性的要求,即查询操作不锁定车票信息,不理会当前有多少人在订票。反正大家心里有准备最后几张票都是抢的。当然锁定范围肯定不是整个数据表,而是订单相关的车次记录。也许12306的第一个瓶颈问题先发生在这里吧。因为如果查询和订票会相互影响的话,那么怎么优化都有问题。我猜他们后来搞出一个30分钟更新的数据就是为了提供一个单独的车票数据表供查询用,这样大致上隔离的订票的查询之间的冲突。也许他们忘记数据库事务控制中锁的作用吧。我这样大而化之的分析肯定有很多错误,不过这是一个所有DBA和程序员都应该知道的基本常识。接下来的车票数据可能会更复杂,也许是当前即使使用用一个单独的数据库来进行余票查询也非常慢的原因吧。

车票数据我的估计是这样的,每天有7万个车次(7000万/1000)。靠谱吗?我估计数量级大概是靠谱的。谁能给更准确的数字?请不吝赐教,谢谢。车次的问题在于除非是直达车,否则一个车次可以分开好几段来售票。那么7万车次就可能不只7万条记录,这里还会牵涉到几个让一般程序员感到棘手的算法问题。算法?拜托都还给老师了吧。实际上怎么存放车票信息将牵涉到具体如何实现余票查询的算法。举个例子:有一趟车从广州开往北京,经停南昌、株洲(铁路知识一片空白,地名乱填的,别拍砖哈)有10张卧铺票。甲买了一张广州到株洲的,这时株洲到北京还剩10张,广州到北京就剩下9张了。实际还需要扩展一下,如果有人查南昌到株洲呢?还是9张,广州到株洲呢——9张。经停站越多,起始站和终点站的组合就越多,这个算法复杂性就在这里。设计这种信息的存放就是一个挑战。如果列车的售票模式是允许这样的话,我想我知道他们的麻烦在哪了。

首先最笨的方法是,每个车次只存一条数据记录,然后关联另一张节点表(存放经停站点用)。这样查询余票的时候需要联合这两张表(还可能有计算)并汇总已有订票记录表。这种存放方式的特点在于数据冗余少。但是查询开销极大,因为需要访问一个庞大的订票数据表(每天2000万的增长)。如果哪个坚持数据库范式的DBA这么干,那么一定就悲剧鸟。

不过我们可以从上面开始优化,其中可以将订票数据表生成一张汇总表。(毕竟我们只需要知道已经出售的票数,而不需要知道哪张票是谁买的)只记录车次、起始站、终点站的售票张数。这样可以把每天2000万的数据缩减为每天差不多70万的数据,具体估算如下:假设一次列车平均5个站,那么起止组合就平均有10左右(不严格)车次。那么每天汇总就是7万x10,约70万条数据。几天下来就是几百万。不过由于需要联合多个表查询,其处理集合的数量级还是上千万甚至上亿。这样对于高负荷网站来说还是有点受不了。而且汇总数据和订票数据相关,不能实时生成。也许这能解释为什么余票数据改成30分钟一次。

再进一步,其实我们可以把车次数据直接拆开了存放,也就是每个车次直接存放为各种起止点的组合,而且直接把余票数据放进来。这样查询的时候一张表就搞定了。每天增加70万数据,12天的票也在1000万的量级。这样性能好点的服务器应该就可以解决问题了。麻烦只在于这时候订票操作需要修改多个数据。但是订票操作应该远远小于查询操作。整体来看系统性能还是可以提升不少的。

实际上,这个问题有很多解决方法,由于手头没有详细的车次数据、铁路局的节点数据、用户查询习惯等支持。所以没有什么建议,但有一个直觉就是纯数据库解决方法可能不是最优解。也许转换成有向图直接在内存中计算会更快。而且还能增加转车计算的功能。没有数据再掰下去就太YY了。鞠躬,下台。

通宝推:雪君,文字君子,铁手,muilho,
全看分页树展 · 主题


有趣有益,互惠互利;开阔视野,博采众长。
虚拟的网络,真实的人。天南地北客,相逢皆朋友

Copyright © cchere 西西河