要解决这个问题也不是不可能,网上除了上述这些损人利己的极客手段外,还涌现出很多为12306”支招“的文章,其中有很多关于大容量web站点架构的思想值得我们去学习和思考。比如酷壳、风云和这里。作为一个研究网站架构技术的从业人员,我也谈一下这个问题。
大型购票系统的设计
用过12306.cn的人,尤其是互联网从业人员,均会觉得这个网站很“烂”。依我之见,烂体现在两个方面,一个是界面以及使用体验之烂;另一个是架构理念上的烂 -- 仍然是在用N年前做ERP的思路在做一个web 2.0时代大型的电子商务网站。第一点是顾客能够感受到的,而第二点则不是那么明显了。
那么,这个系统正确的做法是什么呢?上文引用的几篇博文无一例外地为铁道部指明了正确的方向:使用队列系统。我完全赞同他们的思路,只是在实现的细节上有我自己的思考。
使用流程
我设想的购票流程是这样的:
1. 车次查询
客户输入所需的出发地、目的地和出发日期/时间查询列车班次。 在这一步骤中,只有出发和目的地是关键信息。客户可以一次性按照他自己的优先次序选择多个不同日期/时间出发的车次,全部加入“购物篮”。
2. 订单确认和预付款
3. 竞购
系统后台建立一个队列,分下单和订单处理两个阶段运作。例如,设定每个周期为20分钟,其中每个周期的后10分钟为订单处理阶段。没能在周期前10分钟内完成押金支付的客户的订单将累积到下个周期的订单处理阶段中处理。每周期的竞购结束后,若客户购票成功,系统将发送一条短信至客户手机,此短信包含在订单确认环节中生成的确认码;若客户购票不成功,订单将被自动累积到下一个处理周期,同时,若此轮为该客户第一次参与竞购,系统也将发送一条提示短信(后续周期中如果该客户仍然未成功购得车票,系统将不再发送短信提示)。
4. 定位和取票
竞购成功的客户可以凭手机号码(或身份证号码)以及确认码登录取票网站选择自己的座位,也可以在代售点或车站进行座位确认,并取票,取票时需使用身份证。
5. 退款
发生退款的情形有三种:
- 未成功购得车票,这里又分两种原因:
- 身份信息不正确(如:姓名和身份证号不匹配):押金将退回到付款所用的银行账户
- 竞购失败:押金将退回到乘车人所拥有的银行账户
- 退票:押金将退回到乘车人所拥有的银行账户
退款操作系统自动执行,无需用户或运营管理人员干预。
6. 查询及退票
客户可以通过专门的网站查询界面通过手机号、身份证号以及确认码来查询订票情况、执行退票操作(包括输入退票所用的账户信息)。
为什么这样设计
铁路售票系统最需要解决的问题就是:在僧多粥少的情况下,如何公平地分配稀缺资源?引申出来的问题是:如何设计一个高效的在线售票系统应对大流量?如何防范黄牛或恶意攻击?在流程设计层面,我的设计方案做到了两点:
有效而且公平
与本文开头所引用的几篇博文的一个小区别是,我在设计队列系统的时候特别考虑了所谓“秒杀”的做法,并在流程设计上加以杜绝。同时,方案中又保留了队列系统的基本特征,即先来后到。
鲁棒而且安全
本系统最大的特色是免注册、免登录(查询除外)。通过简化流程和技术手段的配合,可以极大地提升系统的吞吐量,而又不降低系统的鲁棒性和安全性:
- 充分利用车票实名制,完全避免注册和登录过程。
- 通过预付款方式,避免黄牛、恶意攻击者和“秒杀”者对服务器产生过大压力。
- 通过退款账户限制,从根本上消灭黄牛的生存空间。同时,由于使用了完全异步化的队列系统和唯一的编号(身份证),系统无需想方设法去防止“占着茅坑不拉屎”(或者说DOS攻击)或者重复不合理购买(比如一人购买同一天的多个车次等),这样也就避免了“错杀”(即一个心怀不轨的人乱输入别人的身份证号码,导致真正想购票的人无法购票),提高了用户体验。
我自信这套购票流程设计是比较完善的(当然,如果你发现了问题或有所欠缺,欢迎留言或通过email和我联系)。但它也不是没有问题的。在设计上,它的主要问题就是和外部系统的互通上。它需要和公安系统联系,验证身份信息的有效性(验证是后台异步进行的,这样可以避免流量压力,也可以保护公民身份信息);还需要和银行支付系统联系,尤其是退款通道上是否会有由于银行系统规范而导致的一些问题,我不得而知。
但与外部系统沟通这个问题现有的12306网站也是要做的,而且我相信这些事情对于铁道部来说是小菜一碟了。这并不是本文所要讨论的内容,不展开了。
技术上的要点
客户端查询
导致当前系统的压力过大的主要原因是购票过程中必不可少的查询,包括车次的查询和剩余票量的查询。我的解决方案是:车次查询完全在客户端完成;不提供实时的剩余票量查询。
具体的解决方案是,客户告诉服务器他的出发和到达地点,凭这点信息,服务器立即返回一套这两个地点之间的车次的所有数据,包括车次、时间、是否有票或剩余票量(不区分车型是否动车、不区分是否始发站,所有数据一次性返回)。这些数据以gzip形式压缩存储于内存数据库(如redis)中,web服务器返回此类数据的速度可以超过读取磁盘文件的速度。客户端取得数据后,以javascript查询本地数据,往“购物篮”中添加订单,直到订单提交时才会再次与服务器沟通。 注意:剩余票量在开始取回以后不实时更新,直到下一个竞购周期才会刷新。
通过这种手段,服务器端的链接数和PV可以至少有数量级以上的下降。用这里提到的数据估算,12306的峰值访问量可能达到两个小时5亿PV,也就是说,每秒钟大约7万个hit。假设新方案降低PV到原来的1/10,也就是不超过7000hits/s。这种量也就一台NginX就可以搞定。当然,实际可能还有HA、热备但也不至于太复杂了。而查询所需的基于JavaScript技术的“列车时刻表”,我没有测算过全国有多少个车站,又有多少种换乘方案,随便估算一下,弄两台128G内存的服务器做Master-Slave,肯定可以搞定了(其实不会有那么多,因为只有热数据才会保存在内存中)。
按功能集群
这个问题我引用的博文中被多次提到,也是架构设计中需注意的“最佳实践”之一。例如,订票网站叫做ticket.12306.cn,提供查询功能,下单服务器是order.12306.cn,而后台还有一堆集群负责订单的处理,这才是本系统工作量集中的地方。后台订单处理系统的任务分配可以根据出发地和到达地进行合理的聚合,即同一(或近似)路线的放在同一服务器上处理。
恰当的防DDOS系统
为了提供最鲁棒的系统,我认为没有必要限制每个用户每次能购买多少票,在设计中也做了种种考虑来实现这种“完全开放”的下单方式。但在IT层面,恰当的防止攻击还是需要的,比如防止同一个IP过快地访问服务器等等。
吾道一以贯之
谈到架构,很多人可能认为是“后端” 的问题,脑子里面浮现出来的可能是CISCO的路由器、IBM的服务器、NginX等等。经验丰富的工程师同时会考虑前端的优化,比如使用AJAX技术、 页面静态化、用YSlow去分析一下等等。但较少有人会将架构和产品设计联系在一起。其实,“架构”是贯穿互联网产品方方面面的灵魂。一个好的架构可以令 你的产品光芒四射,而一个坏的产品设计则会让架构师伤透脑筋,让耗资不菲的硬件设备不堪重负。
上面,我从12306这个活生生的例子阐述了在“架构”这个问题上我的思考:架构问题是一个从需求分析到产品设计到技术实现的大问题。一个优秀的架构师不仅仅要懂得技术实现的手段,而且还必须是一个合格的需求分析人员和产品设计师。
没有评论:
发表评论