12306设计问题

任何设计都要分析问题需求,找到瓶颈。12306如果并发很少,那么就是一个很普通的购物商城的模型。单机器可能就可以解决了。但是由于12306平时访问量很大,而且部分时间段访问量会突然增大所以基本上讨论的问题就集中在以上两点。

看到风云12年的一篇博客上有当时对此问题的分析。提出的方法如下:

“ 我们设置几个网关服务器,用动态 DNS 的方式,把并发的订票请求分摊开。类比现实的话,就是把人分流到不同的购票大厅去。每个购票大厅都可以买到所有车次的票。OK ,这一步的负载均衡怎么做我就不详细说了。

每个网关其实最重要的作用就是让订票的用户排队。其实整个系统也只用做排队,关于实际订票怎么操作,就算每个网关后坐一排售票员,在屏幕上看到有人来买票,输入到内部订票系统中出票,然后再把票号敲回去,这个系统都能无压力的正常工作。否则,以前春运是怎么把票卖出去的?

我们来说说排队系统是怎么做的:

其实就类似我们去热门馆子吃饭拿号。只不过要防止别人伪造号插队而已。

如果你来一个人(一次 HTTP 请求),我就随机产生一个我做过一些签名处理的号码返回给你。暂时称为 ticket id 。这个 ticked id 是很难伪造的。

系统在内存里开一个大数组(32G 内存够排上亿人了吧),就是一循环队列。把这个 ticket id 放在队列尾。

用户现在拿着 ticket id 向网关发起请求。网关利用一次 hash 查询,在内存中的数组队列里查到它的位置,立刻返回给用户。用户的前端就可以看到,他在这个网关(售票大厅)前面还有多少人等着。

这里的关键是,整个队列都在本机的内存中,查询返回队列中的位置,可以实现的比一个处理静态文件的 web server 还要高效。静态文件至少还要去调用文件 IO 呢。静态文件 web server 可以处理多少并发量,不用我介绍了。

同时,前端会控制用户拿着 ticket id 查询队列位置的频率。高负载时可以 1s 一次,甚至更长时间。为了防止用户自己写脚本刷这个请求(虽然没有太大意义,因为刷的多也不会排到前面去),如果见到同一个 ticket id 过于频繁的查询。比如 10s 内查询了 20 次以上。就直接把这个 ticket id 作废。持有这个 ticket 的人就需要重新排队了。

对于最后排到的人,系统会生成一个唯一的不可伪造的 session id ,用户下面就可以通过这个 session id 去做实际的购票流程了。可以连去真正的购票服务器,也可以通过网关中转。非法的 session id 会立刻断掉,用户一旦知道伪造 session id 几乎不可能,只有通过 ticket id 排队拿到,除非是恶意攻击系统,不然不会有人乱拿 session id 去试。

我们再给每个 session id 设置一个最长有效时间,比如半小时。如果超过半小时还没有完整购票流程,那么就重新去排队。

至于同时开放多少个 session id ,也就是相当于开放多少个购票窗口,就取决于购票系统能承受的负载了。不过简单计算一下,就知道有排队系统保证了良好的次序,再以计算机的吞吐能力,解决不过几亿人的购票请求,即使这些人都同来排队,也就是一组机器几小时的处理量而已。

为什么现在的购票系统这么滥?关键在于大量的网络带宽,计算力浪费在了“维持次序”上。系统不稳定时,大量的只做了一半的无效的购票流程浪费掉了这些。要响应高并发的 HTTP 请求,关键就在于迅速反应,不要什么都想着从数据库绕一圈。排队的队伍维持就完全不需要使用数据库。如果所有 HTTP 请求都立刻返回,在短时间内可以处理的 HTTP 请求量也会非常大。而如果你一下处理不了这个请求,又把 TCP 连接保持在那里,就莫怪系统支持不住了。

另外,用户看到了不断在减少的队列前面的人数,他们也会安心等待。只要网站页面刷新流畅(只处理队列信息很容易保证),用户体验会很好。 ”

与现在的购票流程比,现在允许用户直接登录上系统,有票就能够直接下单(下单包括了购票信息购票人信息,这样就不用等到拍到号在买票了。相当于你在饭店吃饭,人很多,只给你一个号,你把菜都点好了,有的话服务员直接上菜(买到票),没有的话可能给你点别的菜(无座),或者说什么也没有了直接让你走。这样免去了点菜(购票)的时间,节省了餐桌资源和服务员等你点菜(占用服务器资源)时间),但是下单后需要排队等待,然后返回购票的结果。与风云提出的用户登录后(或者是发出购票请求,此购票请求不带任何购票信息)在排队相比,操作性好一点(但要系统设计能够允许这么多用户同时登录)。如果系统不允许这么多人登录,风云的方案也是可行的。

提前点菜的过程,把你的请求先说好,发送到服务器,服务器一看没法给你服务器就会立刻返回失败,不在让你在慢慢的一步一步的购票了。

还有查询业务与购票业务可以分离,购票服务器通知查询服务器车票变化(允许有延迟)。车票信息全部缓存在内存(有限数据),不直接读取数据库。晚上在同步。这么看查询服务器可以没有数据库,也不需要身份验证。

排队的方法还能符合生活中拿号买票的思维但是能不能买到票以后基本是看网速了,其实这样已经够了。买任何有限资源的东西都是要抢的么。也有人提出了“摇号”的方案,这种方案可以给你足够的时间选择安排你的买票意愿,有点像大学报志愿。但是最后如果一张票有多个人选,那么最后就要摇号选择了。摇号中的号码可以使用上证指数这东西计算而来,例如直接取余。

不同的硬件资源可以有不同的解决方案,可以让用户在登录时候排队,也可以在买票后排队。目的是让用户稍等,但是不会很卡。没有必要为了过年的那几天专门采购服务器来满足那几天的高并发。满足了甚至会让用户感觉,票都让黄牛买去,自己队都没排上的感觉。

一种节省开销的策略,如果一个操作要分很多步骤完成,那么可能需要用户占有资源与服务器交互,如果可以的话,让用户把需要交互的东西一次性填写好,然后一次发给服务器,异步等待结果。发送给服务器请求后可以很快释放资源,减少服务器开销。

ps:最早的负载均衡技术是通过DNS来实现的,在DNS中为多个地址配置同一个名字,因而查询这个名字的客户机将得到其中一个地址,从而使得不同的客户访问不同的服务器,达到负载均衡的目的。DNS负载均衡是一种简单而有效的方法,但是它不能区分服务器的差异,也不能反映服务器的当前运行状态。

参考:

http://blog.codingnow.com/2012/01/ticket_queue.html

Table of Contents