其实铁路订票系统面临的技术难点无非就是春运期间可能发生的海量并发业务请求。这个加上一个排队系统就可以轻易解决的。

本来我在 weibo 上闲扯两句,这么简单的方案,本以为大家一看就明白的。没想到还是许多人有疑问。好吧,写篇 blog 来解释一下。

简单说,我们设置几个网关服务器,用动态 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 ,也就是相当于开放多少个购票窗口,就取决于购票系统能承受的负载了。不过简单计算一下,就知道有排队系统保证了良好的次序,再以计算机的吞吐能力,解决不过几亿人的购票请求,即使这些人都同来排队,也就是一组机器几小时的处理量而已。

这票 blog 也就是随便写写,可能不太严谨,但意思达到了。中间有很多数据需要估算,也不是太难的事情。

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

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


最后补充几句废话:因为铁路购票系统很多年前就实现了内部网络化,有成熟系统支撑,运作多年。这次做互联网版本,一定不能放弃原有系统新来一套。不然实体购票点也在网页上刷不出票就崩溃了。

所以要做的仅仅是怎么做一个系统和原有系统对接。这样风险最小。两套系统可以分别优化处理能力。基于这个设计起点,所以我才不看好所有企图取代原有系统的方案。

 

 

=================================================================    华丽分割线   (以下是评论)   =====================================================

COMMENTS

火车订票业务分为余票查询和实际订票操作 你的方案能很好的解决订票操作的问题,但是没有办法解决余票实时更新查询的问题,这样用户如何决定是否要排队? 去车站买票你可以看公告牌,车站能解决这个问题是因为车站公告牌对查询接口的请求数量远远小于互联网产生的请求量

单纯的队列解决不了12306的问题!

最近有几次面试主要围绕假如我来设计12306怎样实现高并发等问题,所以我最近两天特意思考了一下, 看到云风提出的排队方式, 这是一个解决办法,不过我想到从买票来说, 票之间并没有直接关联, 是不是可以做一个“化整为零”的方案?譬如把各趟车票平均分配到20~50台服务器, 通过负载均衡, 用户在各个服务器买票需求应该是正态均匀分布的,是不是就用简单的方式解决了并发? 当然还可以保留小部分余票到一个分配票的服务器,隔较长时间如几分钟同步或轮询一次, 再分配剩余的票。从用户的角度说, 我认为大家从票集中高并发的服务器集群上和从平均分配(也可以根据运行情况动态调整分配比例)的概率其实并无差别。由于铁路是每天固定的时间才可以预售某一天的票, 所以可以来做这个分配的。 因为我希望能找到这个问题的答案或可能好一点的方案, 有兴趣的话请评论一下? 谢谢!

“简单设计” “无非就是”“轻易解决” “闲扯两句,这么简单的方案,本以为大家一看就明白的,没想到还是许多人有疑问”

你是有多牛呢?

奇怪了,快一年了,还有些人没看懂这个设计。在短时间内,让大家排好队,慢慢出票呗。排队用的服务器,就算你有10亿的访问量又如何? 看看另一位大虾的 http://blog.sina.com.cn/s/blog_466c66400100cfrj.html 单机(4Core*2CPU 2.33GHz),每秒可处理50万请求,30秒处理1500万,10亿,用10台,排好队要几分钟?

12306从年初就开始有问题,现在还没解决。不过云风的方法真的可以吗?

楼主忽视了一些逻辑的需求,比如需要站-站查询,需要联程票查询。一个买不上买另一个等等。

跟我年初的时候写的 blog 的思路差不多:http://blog.fulin.org/2012/01/12306_with_redis.html 用 redis 实现和保护 12306,排队,时间窗口

相当靠谱。赞成这个应该是实现的最佳方式了。另外如果不排队的方式,可以考虑当用户下订单申请购票就开始就锁定(从网关读取数据放在缓存池锁定),然后为了保证和出票网关的正常出票,在下单的用户必须排队来处理,但是因为99%能买到,在这时候排队是用户是没有任何体验问题的,排到处理了就开始走支付,出票等流程。

最关键的锁你只字不提。

验证码

验证码很有效= =

你完全可以写一个,然后让铁道部购买你的程序了,让他们聘你为专家中的专家。 扯蛋文章

你完全可以写一个,然后让铁道部购买你的程序了,让他们聘你为专家中的专家。 扯蛋文章

我觉得应当从设计网上订票系统的最终目的是什么问题入手分析吧。 网上订票不过就是一个消遣人的东西,是让群众买不到票怪自己点背,因为给你机会了,是你自己没把握住而已。 既然是消遣,我倒是觉得可以改一改消遣的方式,比如说30分钟内任意时间用自己的身份证排一个号,同时输入你的买票信息,座位要求。这样在时间结束时你就可以根据随机结果看到本次是否运气够好买到票了,这个时候可以用手机短信通知的方式,如果你排上了,选择在线付款也好票点付款也好,完全是另外一个系统的事了。春运期间所有订票系统都采用此方式就行了。总之,你的信息提前输入完毕,然后摇号。

哦,原来是这样 果然也是个好办法啊

WOW刚刚出来的时候就有登录用户过多时的排队系统了

一般银行,饭馆的人流应该是符合一定的规律的,而且资源在有限的时间内可以说是无限的。但是12306.cn来说完全是秒杀,有限资源要在瞬间分配完,所有单纯的排队系统就是拼网速,拼操作了。我觉得还是先限时按车次预约,让大家不紧不慢的访问网站,然后随机分配

@Tony

HTTP request 里带上 cookie 也好,带上 session id 也好,都可以看成是有状态的了。就看你怎么实现。

一次 HTTP 请求,这里不能看作一个HTTP请求吧,常识而言,一个页面会是不同的HTTP请求,还是无状态的。不同于游戏设计建立的有状态的连接。

其实关键问题应该在于后端的数据库事务处理。

铁道部哪能听取这么多意见啊!不知道他们怎么想的,其实这个原理简单……可能应该是你说的,两个系统的兼容吧!赞同你的观点!

都没人考虑用户体验的吗?对方是知识文化很低的人,那些复杂的订票流程谁弄得懂? 难道还要培训下?

最初的思路很重要,这时的丝毫偏差,最后就是谬之千里了。 我当时就感觉问题出在查余票、下订单、支付(银行)、出票,都在一个网站,所以一慢就都卡死,一卡大家就反复刷,压力也就更大。 票少人多,暂且不说它。但是,订不到网站不能卡、死。 查余票:定时动态生成静态页面 下订单:客户端动作 支付(银行):与网站几乎无关 出票:这个才需要与铁道部原系统对接。 http://blog.csdn.net/sz_haitao/article/details/7174037

对变态的业务逻辑只能用变态的业务模型来满足。票(大学)是稀缺资源,不够是正常的。但是只看到有人报怨自己没考好,没报好,没人有抱怨志愿填报不合理。 当然我们用不着让买票人也去参加考试然后凭分数来排定买票的排队次序。在互联网售票已经可以开始运作(虽然运作的还不完美),我们就把排队的广场放在网络上吧。

春运期间售票如果能改成以下流程我觉得12306将能满足广大票友的需求 售票分为以下几个阶段: 实名注册期 => 需求收集期 => 秒杀抢票期 => 后台卖票期 => 订单确认付款期 => 取票\刷卡上车期

假定某年春运第一天为1月30号,提前10天(20号)可以拿票来看

10号(所购票前20天12306开放购票意向收集与用户注册),类似大学报志愿一样,每身份证限购五张,可提交最多所购票当天6个不同车次购票请求,购票请求按优先级从上到下将会在后台售票时依次尝试满足  18号零点前为购票需求收集截止期。  18号早上9点开始开放抢票,用户保持已登录状态开始抢位置,系统显示当前排队的位置,刷新没用,越刷越往后排,一次点击就够了!!19号零点前抢票结束 –您要是实在不着急买票,可以等到最后一秒再来抢,不过买不到票您可别怨铁道路 19号零点后台运行卖票程序,按照18号抢票排队的顺序卖票依次处理售票,售一张则发一个邮件与短信确认,24小时内需确认,逾期则收回。–当然,如果您请票心切,提前把钱存到12306的个人购票账号里也行,铁道部巴不得您在那能存些余额呢。  20号可拿到纸票,或付款后即可以在铁道部数据库中查到购票记录可届时刷卡上车

比方说某人今天网速不佳,或选的车次都太热门没买到票,那好,今天没买到30号出发的票,我再修改一下我的车次信息,明天早上9点我再换个网速好点的地方再去抢个好位置,说不定明天他就能如愿买到31的票。

这个方案有两个“亮”点: 1)抢票。不抢是不行的,中国人这么多,资源又那么少,铁道运力有限的情况下你能不抢??你不在网上刷网页抢,就得冬天一大早起来排队去抢,或者打电话抢,你选择怎么抢?? 2)后台卖票程序。这个比起什么高并发,数据库瓶颈,多台server,数据库一致性等等问题简单到不知道哪里去了。后台卖票程序运行的时候定在晚上0点到早上7点之间,7个小时其它地方不卖票,光这个程序读数据库中收集好的购票意向信息逐个处理,7个小时处理不完接下来一天的购票信息吗?如果真处理不完那么具体细节设计上我们再好好探讨一下

排队系统是从云风那里读到的。事在人为,如果不人为设置障碍或者铁道部自己就没打算好好卖,票绝对没有那么难卖

太扯了,完全是凭空想象的,没有那么简单

嗯,思路不错。使用消息队列的思想,一个事务不多的几次数据库请求就可以了。而且和用户网页无关,都是服务器内部搞定的。就不涉及大量的网络负载了。 另外,我觉得12306.cn应该使用Amazon云这样的云计算服务。找家云服务提供商,动态增加虚拟机,以应对春运高峰的大流量大负载。 平时只需要租用很少的虚拟机即可。可大大降低成本。

@大米

排队前把身份证填好,系统编码到 ticket 里去。

排到了这个 ticket 只可以用这个身份证买一张或几张同车次的票。

注册环节完全可以免了。如果需要注册,填个密码就 OK。用户名可以暂时用身份证,买完票可以换,或者去别的服务器上换,不干扰购票流程。

你 8 点出票可以 7 点开始排。系统反正 hold 住你了没人插队。现实就如此。不要去想比同样跟你一样需要票的人更有优先权。

有一个疑问 什么时候让用户登录呢 是排队前 还是排队后 如果要防止加重负载 就只能先排队 再登录 或许新用户还要注册 这就浪费了不少的时间 还有一点 定时出票的问题 如果我要订的票8点出票 那我8点开始排队 可能就买不到票了 还是很难实现抢票 说到底 这个系统只能起到节约体力的作用 不用出门排队了 如何防止黄牛 我相信 黄牛自有自己的渠道

大家都开始把系统的优化改造方案向着真实世界的购票大厅排队体系思考了,这很好,但真实世界里一定就会带来新的人为因素,例如银行的排队,就TM还区分个个人用户、企业用户、VIP用户,高级会员啥的, 只要有人敢把12306改成排队的购票系统,就TM一定会弄出来不同等级的用户权限,到时候排队的优先级、插队等真实世界的需求就都来了,那时候看大家还说哪个公平来着。

生产者消费者模式,有效分流,我认为靠谱。

http://www.iteye.com/topic/1119803 你们还是太关注技术了,忽略了方案的适用场景,欢迎来看为什么排队行不通

像这种高并发, 如果可以的话, 可以换一个思路, 流程不由让客户端来驱动, 而由服务器来控制, 效率应该会高许多

我虽然也喷一次12306, 其是因为填了几个小时的验证码, 有点火, 但是心里还是佩服的, 至少如此高的pv量 , ip量, 网站然后还流畅的运行, 已经很不错了, (即使是说用户过多, 那只是后面系统处理不过来的原因)

因为人太多, 拼命加硬件也可以解决,前面查询与后面支付问题, 但是只是每年春节才用一次, 不值当的, 所以我有一个小小的想法来解决这个问题,

我说的这个问题特指: 目前网站前台接待工作做的很好, 但是把你带到后堂, 就没有人招呼了, 就跟我大学的时候, 吃饭的时间大家都拿水瓶去打水, 结果茶房挤破头, 但是呢最终都有水打, 只是要耗太多的时间在那等, 我就想大家都茶壶放在那, 然后有一个人专门来打水 , 大家都估计(根据送来水壶的时间)水打好了, 就直接到拿走就ok了, 节省时间

所以我想到的方法是: 采用预订机制, (归根结底是因为没有排除)

1. 先查询出我要订哪些票(票的价格都有), 此时直接可以支付(注册时已经认证过身份证信息), 支付好了以后, 大家就该干嘛干嘛, 2. 系统根据预订队列, 按照系统所能使出最大能力, 生成车票, 如果可能可以短信提醒或邮件提醒订票成功与否, (如果有客户选择的时间范围,自动滚动到下一批的队列) 3. 最终还是没有票, 执行退款动作

我想这样做的好处, 大家不用无谓的去查询多张, 再订啊订啊, 增加无谓的服务器和带宽负担, 这样就可以把前台接客的资源节省(甚至支付资源)一大部分, 并用与支付环节

不知道是否可行

全是扯蛋

设计简单票不是很好买

铁道部说了:铁路运力供需矛盾十分突出。这个技术方案虽然不错, 但是有一个问题:如果按照访问次序排队,几ms的差距就大体决定了这票能不能到手,那这些注定买不到票的人的怨气如何发泄。我认为,还是先注册登记,再统一摇号,短信通知限时付款,比较好一点,至少大家各安天命,看起来公平一点。

网上买火车票可以用实名制啊,这样不就不怕黄牛刷票了啊

楼主肯定是资深技术人士,虽然我也是搞技术的,但我知道很多事情并不是技术可以解决的。

今天是因为网上购票系统扛不住了导致买票困难,导致骂声一片,而如果真的是系统很牛叉可以抵挡的了这样的访问,那么新的问题又来了:在放票的一瞬间票就被秒杀光了。

结果会怎样? 全国人民再次抱怨买不到票,甚至怀疑购票系统。 并且这样会导致专门有人开发刷票软件。那样,再牛X的系统、再好的排队系统也无济于事的。。。因为网上到处充斥着刷票程序。这样,真正通过人工登录去购票的人还是买不了票。

我倒是想到了一个非技术的可行办法 简单的说就是把预售改成预约,任何人都可以实名制预约某个班次火车,然后截止预约时间后系统对旅客进行虚拟购票,第一批为分组1,虚拟售罄后再从1车厢1号座位开始虚拟售票,为第2组,再售罄后就是第三组,以此类推。假如总共虚拟售出了5组,那么在通过一个公开的方式选出某一组为获得该车次的购票权(比如当天沪深指数之和对总组数的余数+1即为该组获得购买权),然后在规定的天数内付款买票即可。

这样,即不需要那么牛叉的并非系统,买票的人也不需要到3点去秒杀火车票了。

详细可看我写的 http://blog.sina.com.cn/s/blog_6a64bd150100zp23.html 

虽然有些地方木有看懂。但还是有收益的。。呵呵。

关于老系统的问题:

最简单的方式就是新系统分配到一定配额的车票,独立运行。其实这应该是最佳的方案,否则在正常情况下,网络的出票效率肯定是高于其他方式,如果不做配额,99%的票都会被网络拿走。

稍微复杂一点的就是加一个仲裁的服务器,每几分钟分别向老系统和新系统放票,新老系统也是独立运行。

最后,比较笨的方法新系统通过外挂的形式和老系统交互,系统串行化请求,加个流量控制之后向老系统请票,这样新系统出票速度不会很快,但至少不会让用户卡在UI上。

@Jungledrum 还有可以设计成排队的时候就已经带有需求了, 用户真正排到的时候可能已经不需要操作. 直接把票分发给他就可以了. 不存在不熟悉流程而浪费时间的问题.

另外的话,1千万的注册用户,假设允许其同时排10个队。客户端再次提交的时候,请求里带着上次的队内序号。队列管理程序就不产出新的token,而是刷新一下对内的token。这样,1000万用户,就不用考虑循环队列,直接顺序队列,不用考虑回收了。

这个方案,我觉得靠谱,模拟的就是现实世界的购票方案,或许你前面很多人排队,但是你一旦拍到了,买票就很流畅。至于排队,也要让你看到你前面有几个人在排,这个从外部来看,几乎和现实世界里面的购票流程相似。。

@menha 电话订票的缺陷在于缺少反向回拨机制. 给用户一个排队号,立马挂断通话,让用户等待回拨. 类似好莱坞原则”不要呼叫我,我会呼叫你”.

hash不用了,反正是一个队列,直接用id好了

这个设计是否用客户端(C/S)实现是否会好一些?

另外,设计上,是否可以身份证号、车次和ticket绑定,hash的时候根据车次。热门线路和一般的线路分开排队。

现在系统失败的请求很多,导致很多无效压力,其实我觉得现在这个系统没处理好的地方就是多个用户同时定一个车次的并发问题,所以我觉得可以这样: 1、查询与实际提交请求分开,查询请求不需要队列,车次信息以及余票数放缓存 2、实际提交最好按照车次设置队列,挨个处理,同时更新余票数。 ——-

关键不在技术,订票系统是一些赚钱不干实事的人在搞。现在的结果才正常。

但是黄牛还是可以用脚本抢到一票排到前面的ticket id, 把票买完,我本来以为这次要身份证,是限购的,想不到不限制,我想应该解决的是限制购票,一个身份证只准买一张票这种。

或者春运期间,国家免费送大家两张票?社会主义连这点福利都没有真是。。。不过如何分配是个大问题

最简单的一种方案。全国的窗口售票至少是上万个的吧,每年春运都抗的住,网络购票相当于全部让这些人工售票窗口自动化起来不就 ok 了。这些窗口肯定是满足不了并发的,限制人数排队,咋可能搞不定呢。。。一个非技术的角度考虑。

其实电话订票更靠谱一些,而电话订票跟云风说的道理有相似之处.先排队打电话,打通后每人最多半小时通话时间,超时后挂断。打通后订票交易有后台系统做事务管理。只不过电话没有一个UI而已

@老牛 黄牛倒卖ticket是不可能的,ticket的有效期短,黄牛没法再那么短的时间内完成交易,而且ticket可以绑定IP,还可以进行验证码验证,使得无法用程序批量获取ticket。 换个思路,黄牛代理购票倒是可以的,采用人工刷ticket。

问题是排队等候时间如何控制,用户如何知道他应该等候多久,万一长时间没刷新,过号了怎么办?重新排队?

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

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

黄牛开始卖 tickid 了, 大量的刷,囤这个tickid, 到时候卖给别人就可以了。

这个设计不靠谱。

因为魔兽世界就是这个思路。 排队很难排,但是进去了游戏不卡。

使用异步的响应,排队等待的思路,相当靠谱。。。。而且感觉也相对公平很多。。。。受教了。。

很好的方案。唉,目前的订票系统太不靠谱了。。。

几个补充: 1、用户需求一般是填入出发地点、终点(或加上周边),在一定时间段内查询。可以根据这样的模型,把查询导向几个排队队列。一个从广州到湖南的旅客对西北火车票没有什么兴趣的。这样会有效提高ticket队列的效率。 2、只读的请求可以做特别的优化,从用户打算要下单开始才走ticket流程。这里读、写比应该会相差很大。

这个方案还是大家走一套流程,系统不会挂,但是还是会慢。应该把静态和动态的分开,比如用户查票的时候可以告诉他哪些车没票了,这个数据可以定期更新,这部分用户就不会继续下面的流程。 另外,大家分开排队不如拍一个队。

设计是很好的。但是,的确是但是,铁道部设计的这个的初衷并非售票系统,而是抢票系统……

技术上解决问题的办法总有很多。但天朝非技术的问题总有更多。对于一个“保存密码”就把密码明文存cookies的网站,我觉得还有很远的路要走。。另外做这个网站投入的资金我相信是亿为单位的。。

感觉很靠谱的设计,受益匪浅。关键是fail-fast,提供一个硬性的窗口(可以根据系统情况滑动),控制核心系统(特别数据库)负载,提前避免那些不会成功但会加重负载的请求。 从公平性和用户体验出发,提供排队功能:只要有明确的目的性,用户会接受失败重试和等待。 好的产品果然需要前后端共同努力啊!

“假如有的用户不熟悉购票流程,后面的用户不是也要等好长时间?”

这个肯定有超时时间的吧。

@Jungledrum

首先窗口有很多,我想以现在的系统能力,一台机器 开 100 个窗口毫无压力吧.

如果限制在半小时内操作完,不然就重新排的话. 相当于 2000 秒最长等待时间平摊到 100 个窗口. 及时 100 个用户全部不熟悉流程,把半小时时间用完. 一旦你排到前面, 平均等待时间也不过 20s 而已.

实际系统要比这个好几个数量级.

而且对于不熟悉流程的用户,其实单位时间消耗掉的系统资源很少.完全可以动态增加窗口.

细节就不详细写了.

关键点: 你不是一条队排到死. 只要你排到了前面. 任何一个窗口空了,你都可以上去. 所以我没有和物理的车票售票大厅类比,而是和饭馆(或银行营业厅)叫号系统类比.

假如有的用户不熟悉购票流程,后面的用户不是也要等好长时间?

版权声明:本文为keheng原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/keheng/articles/3513379.html