【IT168 专稿】在2011年与2012年的新旧交替之际,铁道部的12306网上购票系统着实“火”了一把,在中国境内可谓是无人不知无人不晓,曾有人在网上戏称12306为“史上最牛电商”。12306购票系统的初衷是系统通过在线购票方式以免除半夜早起,在瑟瑟寒风中排队挨冻购票的痛苦,然而种种技术短板使得12306根本无法面对“春运”期间的瞬间海量高并发,一度出现用户无法登陆、访问速度过慢以及频繁报错等现象,引起怨声一片。
据目前的公开资料显示,12306购票系统的高峰已超过14亿PV,但是这么大量的PV很大一部分来源于系统慢,导致很多人不断在上面通过手工刷和通过机器人程序自动刷产生很大一部分流量(到底有多少流量是这个问题导致的没有统计数据可查)。例如正如公开资料的信息显示由于现有系统的处理能力有限所以采用相应的机制,让用户经常看到““当前访问用户过多,请稍候重试”、“对不起,当前提交订单用户过多,请稍候重试”或“系统忙”等提示” 这种信息,这种信息的出现更让用户焦虑,不多刷几次简直对不起自己。
从12306网站暴露出来的种种影响正常购票的现象,诸多网友在互联网上纷纷提出了自己的看法,从前端的网页优化到服务器架构、数据库优化、缓存设置、负载均衡以及整体架构提出了诸多的意见和建议,以改进和解决12306出现的各种问题。在本篇文章中,将对目前12306购票系统争议较大的几点进行整理,并在后续专题中邀请业内专家、技术大牛对类似于12306这类高性能高并发系统进行探讨。
焦点一:实现高性能高并发系统到底有多难?
据铁道部副部长胡亚东介绍,网络售票和电话订票每天已经达到了200万张,网络售票的注册用户已经超过了1000万人。从1月1日到1月7日,“12306”网站日均点击次数已经超过了10亿次……这确实是12306所面临的难题之一,但有网友认为,看似高达10亿的PV量,一旦经过分解之后,其均摊到每分钟的并发并不算高。但实际上并不能这样算,12306网站在晚上是不售票的,另外,大部分的并发就集中在开始售票的一段时间。由于瞬间的海量并发造成了12306“爆机”。高并发、高性能、瞬间并发一下子成为互联网上的热点话题。
清华大学Web与软件技术研究中心电子商务研究室主任王津在某微博上发表看法认为,“海量事务高速处理系统”是一种非常特别的系统,应用的场合很少,中国目前研究这种系统的人不多,有真正的实践经验的人更少。多年前末学本人在接触这种系统之前也无法想象“到了某个时刻”系统的性能下降之剧烈乃至崩溃。恳请大家不臆测不轻视类似12306系统的难度。
这一微博一经发布刻遭到了诸多网友的反对,但同时也有支持这一观点的网友。面对海量的并发,之所以引发12306“爆机”的重要因素之一在于,在开始售票前后一段时间内,不断的查询数据库和刷新操作使得12306难以应付。基于这一点,有网友提出,可利用SSD的高速读取优势来充当缓存层,当数据库有变化时,再通知更新缓存更新,这样就可极大地解决频繁的库查询引发的系统“爆机”。并且该网友还举例:在某次大型体育盛事期间的直播项目之中,就是利用上述的缓存设计从而避免了系统“爆机”。这其中谁对谁错,我们无法评判,但值得注意的是,视频流跟类似12306的高性能高并发系统还是有一定的差别,在开始售票后,多人的并发操作必然会引发数据库的频繁读写,几乎是每秒都有变化,而缓存层的数据跟数据库一旦不一致,必然就会出现之前12306曾出现的现象——查询有票,却买不到票。
对于售票时的高并发,网名为“云风”的网友则认为可以在售票环节中加入排队系统,这就如之前的网络游戏“魔兽争霸”一样,当服务器达到饱和之后,采取排队的形式来购票。更有“前卫”的网友认为,“春运”期间之所以买票难,除了一年一度的春节因素之外,另一个重要的因素在于,买票的人过多,已经超出了铁道部的运输能力。所以根据这一点,提出延长订票时间,在截止售票后,进行随机抽取。
如果采用排队系统的话,有人就质疑万一有插队的呢?这个问题基本上是任何人都说不清的问题,而买票这等严肃的事情如果要“抽签”这种方式的话,就未免太过儿戏。对于类似12306的高性能高并发系统并没有一个标准的答案,同时也有一种说不清道不明的感觉,虽然诸多IT技术大牛都真相提出了很多的建议,但总有些地方会遭到质疑,并引发另一场讨论。既然谁也没有一个“服众”的解决方法,那么是否可以借助新浪、淘宝等已有的成熟架构呢?由此又引发了关于高性能高并发系统的另一个争议。
焦点二:类似12306的海量高并发系统与新浪、淘宝有何不同?
2011年在IT业内是一个很热火的一年,云计算、大数据以及Hadoop等概念铺天盖地袭来,并有诸如淘宝、人人网、即刻搜索以及Facebook等许多率先实践Hadoop的技术人员来分享应用经验。从表面看来,类似12306的高性能高并发系统与Facebook、微博以及淘宝节假日抢购活动非常类似,并且,淘宝、新浪以及Facebook等都在这一块做得非常的好。那么他们的经验是否可以借鉴呢?
这一问题刚一抛出,就引起诸多的网友的关注,很多人认为,12306完全可以借鉴这些先进公司的经验。但也有网友认为,这其间还是存在本质的区别。在2011年的“掘宝Hadoop——中国云计算大会”上的“大数据”分论坛中,在Facebook公司专注于大数据的邵铮曾就Facebook的数据处理分享了相关的经验。Facebook的瞬间并发最大的特点就是写操作非常频繁,而Facebook的解决之道则在于通过利用HBase写速度快的特点来构建数据库(据邵铮的说法是HBase在读取上并不尽如人意),并对整个架构进行优化来达到缩短时间的目的,微博跟Facebook在本质上差不太多。而对于12306而言,在前面已经说到,其实质上应当属于频繁的混合读写操作,而且是小随机频繁读写,这在业内是公认的难题。
那么Facebook等新兴SNS社区的经验无法借鉴,那么淘宝的经验可应用于12306之上吗?比较看起来,他们都逃脱不出电商的范畴。Amazon Global Selling 高级研发经理陈皓在其博客中说,与网游或QQ的后端负载相比,12306购票系统的压力显然要大得多。因为网游和QQ在线或是登录时访问的更多的是用户自己的数据,而订票系统访问的是中心的票量数据,这是不一样的。
并且,这与淘宝的秒杀活动在本质上也是有所不同,12306购票系统包含了很多查询操作,查时间,查座位,查铺位,一个车次不行,又查另一个车次,其伴随着大量的查询操作,下单的时候需要对数据库操作。而秒杀完全可以做成只接受前N个用户的请求(完全不操作后端的任何数据, 仅仅只是对用户的下单操作log),这种业务,只要把各个服务器的时间精确同步了就可以了,无需在当时操作任何数据库。
陈皓认为,订票系统应该和电子商务的订单系统很相似,都是需要对库存进行:1)占住库存,2)支付(可选),3)扣除库存的操作。这个是需要有一致性的检查的,也就是在并发时需要对数据加锁的。相比于12306,B2C的电商基本上都会把这个事干成异步的,也就是说,你下的订单并不是马上处理的,而是延时处理的,只有成功处理了,系统才会给你一封确认邮件说是订单成功。数据一致性是高并发下的一大难题。
而12306购票系统的难题在于其采用的是突然放票,而有的票又远远不够大家分,所以,大家才会有抢票这种有做法。于是当票放出来的时候,就会有几百万人甚至上千万人杀上去,查询,下单。几十分钟内,一个网站能接受几千万的访问量,这个是很恐怖的事情。据说12306的高峰访问是10亿PV,集中在早8点到10点,每秒PV在高峰时上千万。
通过上述分析可以看出,12306购票系统所面对的几乎是前所未有的难题,与号称最高同时在线人数1亿的QQ客户端在本质上有明显的不同,与微博、Facebook等新兴SNS社区亦不同,单纯的并发写远远没有频繁的随机混合读写那么难。而与12306业务模式有些许类似的B2C电商却远远没有12306面对的那么大规模人群,可以说12306购票系统的出现是目前IT界的一大难题,其不仅是硬件,还包括软件层面、架构层面以及整体优化都带来了极大的挑战,面对如此大规模的并发,任何一个小细节都极有可能造成规模效应,而12306的仓促上线使得这些问题“并发”,最终导致“爆机”,而反观淘宝、亚马逊以及Facebook等,无一不是经过多年的技术积淀后才勉强能够应付,而12306正式上线运营至今不过短短半年多时间而已。
焦点三:高性能高并发系统架构到底该怎样设计?
关于12306购票系统的问题讨论到这个阶段,几乎所有人都明白,就目前情况下,使用单台服务器根本就不可能应对这种千万级PV的并发,只能用集群来解决这个问题,那么说到集群,就不可避免地谈到系统架构,可以说架构将直接影响整个系统的性能发挥。如果架构不合理,单台服务器性能再好也是无处着力;而反之则情况未必,并且这种情况在互联网行业相当常见,诸如谷歌、Facebook等公司均是采用普通性能的服务器来搭建集群,并通过系统架构和特定优化来发挥其最大性能,对于业内人士而言,这些都不必废话。
百度首席架构师林仕鼎认为,类似于12306的在线交易系统可采用Scale-out这种模式来做,即通过简单地不断添加机器的方式。也就是说,架设这个系统本身并不复杂,12306系统之所以崩溃,主要原因在于请求的尖峰,10倍于平常的压力是很正常的。普通模型到达性能瓶颈后,开始堆积请求(可能在web server,也可能在请求队列,不过通常不会在CDN),吞吐急剧下降,延迟急剧上升,而随着堆积请求越多,情况越糟,引起雪崩效应。而12306的问题就是属于这种情况,这样的压力通常不会持续很久,如果性能不急剧下降的话,一段时间后其实也就能把请求都响应了。但12306的情况则是人们没有买到票,于是不停是刷新,这个操作是不间断的,而且是大规模范围内的,所以宕机也就实属必然。
林仕鼎随后在第二篇博文中说,(类似于12306)系统的复杂度在于海量的并发请求,并发性可以通过scale-out(简单来说,就是堆机器)加以解决,但最难的却是保证系统的稳定吞吐。值得注意的是,在线系统应以保证极限情况下的稳定输出(sustained throughput)为首要设计目标,而这是不容易实现的。至于如何切分数据,如何scale-out,这和具体业务特点关系密切。这些都是软件层需要解决的问题,如何用软件架构的方法来实现scale-up就很困难,做得好与不好可能性能差异能达几倍到一个量级。
IBM软件架构师景文童认为12306 互联网售票系统应该是一个高性能、高伸缩性、高可靠性的系统,可以在高峰期(例如春运时刻)增加机器能够应对高峰期的峰值用户群。而目前的传统做法是用一大堆好机器来做数据库集群和应用服务器集群,把用J2EE架构做出来的功能部署在应用服务器集群上,而把大部分压力都放在数据库上。景文童认为,传统的做法并不特别关注高性能、高可靠性、高伸缩性的应用架构设计、数据架构的设计和相应的代码质量。而这也正是12306系统所缺失的地方。
12306与中国著名的互联网企业进行合作解决—整体架构
针对类似于12306的高性能高并发系统设计,童文童认为12306网站完全可以和新浪、淘宝等大型互联网公司进行合作,通过他们的平台进行登录,利用这些大型互联网公司的资源与12306的平台相对接,以分散海量并发所带来的压力,具体架构设计可参考上图。
在集成架构方面,可采用以消息队列为核心的异步机制把新浪微博、淘宝、腾讯这些公司平台提供的互联网售票应用与12306互联网售票数据服务系统集成起来。这种消息队列为核心的异步机制进行解耦的架构有几个最大的好处:
当大量的并发的用户(例如千万级别的)在几分钟之内甚至1分钟之内压到新浪微博、淘宝、腾讯这些公司平台提供的互联网售票WEB应用,所产生的压力由相应的网络、均衡负载器、互联网售票WEB应用的服务器给分别的承受掉。并且转换成相应的消息异步的传到12306互联网售票数据服务系统进行处理,这样转换给12306互联网售票数据服务系统的并发压力将会下降几个数量级。
12306互联网售票数据服务系统可以根据相应的需求按需配置所需要的资源(例如机器数目和线程数目进行处理)对不同的队列进行处理。并且由于采用了消息队列为核心的异步机制,在高峰期的时候肯定是大量的消息涌入以期待处理,在没有采用消息队列为核心的异步机制的时候我们需要的一次一条条进行处理,而这种情况下例如我们可以对登录实现一次处理10条消息的批量处理,从而大大地降低对数据库的压力。
12306可以将前端交给这些公司合作一起解决高并发问题,当然也可以自己独立解决前端的并发问题,以避免合作过程中可能出现的问题。这对于整体系统架构设计而言,并不会有太大的变化,只是需要投入大量的成本而已。详细可查看《12306系统高性能高并发架构设计遐想初稿》。
焦点四:软件层需要做哪些工作?
软件层将直接决定整个系统应对高并发的能力,其将应用服务器与数据库、存储结合成为一个有机的整体,使数据在这些IT设备之间传输,可以说软件层做得好与不好可能性能差异能达几倍到一个量级。那么在以高并发为前提的情况下,软件层要做什么呢?
百度首席架构师林仕鼎认为在解决请求尖峰时可采用threadpool或event-driven两种做法。这两种做法都需要自己维护请求队列,也带来了提高稳定性的可能性。简单地说,就是增加flow control机制,上游根据下游的负载做throttling,反馈可以有ack/poll等多种做法,有时需要由最下游一路反馈到最前端。并采用延迟截断,对于太老的请求,直接给出拒绝响应,让它在应用层重试。
4399架构师曹政则提供了更多更具体的方式,他认为可将存储key-value化,因为基本上查询都是直线式的,所以key-value就是很好的工具;因为出票可能需要找一下车次,座位,只能一一对应的查询就不好用;弄个redis带个列表结构进去就可以了。春节放票总共多少张?又不是一次放出来,每张票对应一个key,一个value,能吃多少内存?后面跟个数据库做同步,这点数据量对于现在的服务器来说根本不是问题。并且在注册登陆也可以在 mysql基础上弄个redis挂在前头响应以提高查询速度。
实际上,在12306在线购票系统中,查询操作是很常见,因为你需要查询车次,还需要查询余票。基于这点,曹政认为可将查询结果缓存化、静态化,这可通过两个步骤实现,起始地,目标地查询 - 常见查询目标(如北京到成都)全部预制缓存。非常见查询目标,基于第一次查询的结果缓存,这样查询车次基本上无压力。
查询有票状态就更简单了,因为票数只有有票,无票两个状态,某日某车次作为一个key-value类型存储(仍用redis即可)。某类车票发生从有到无或从无到有的变化,才通知缓存更新。更新是后台通知的,而非基于用户查询。比如某车次硬卧票售完,通知一次更新,硬座售完,通知一次更新,软座售完,通知一次更新。以此类推,这样的缓存更新次数极少。而且可以给前端返回甚至静态结果(基于查询条件生成静态结果,是个Seoer都会的,后台在票数变化时通知更新,这样结构上就与前端查询无关了,而且一样可以保持实时性)。
最后还可对前端缓存进行处理来提高响应速度。可能大多数人都被10亿PV给吓到了,但实际上这里面有很大的水分,因为有很多可能是利用代码或者脚本进行的自动刷新,自然而然就会产生较大的泡沫,而实际上绝对不会有那么多。基于这点,则可通过缓存来避免“爆机”,并且有例子证明,这样做的效果是能够应对一小时20亿的刷新,12306的10亿刷新根本就不成问题。
当然,网上的各种关于软件层的建议还有很多很多,包括之前提到的云风的排队系统也应该属于软件层的内容,因为篇幅的原因,在此我们仅选取了几个较为典型的建议。
焦点五:系统该如何优化?
系统优化是必不可少的环节,每一个成熟的网站和系统都是通过不断优化而来的。而12306网站最早暴露出来的也是优化问题,随后经过网友深挖,从12306网站前端的网页设计到后端的数据、缓存、负载均衡、数据分区等一系列问题都提出不同的优化方案。
12306网站前端性能优化技术分析
从时间上来看,首先引发12306网站拥堵的是网页的问题,从12306的首页来看,其总文件大小在900K左右,在极端情况下,如果所有人都是第一次访问的话,那么每个人都需要下载1M大小的文件,如果需要在两分钟内返回的话,那么所需的带宽需要66Gbps,当然这是极端的情况。那么在这种情况下就需要对网页大小进行调整,尽量少用图片,因为图片非常消耗带宽,在这种高并发的情况下,即使1K大小的文件也极有可能引发“蝴蝶效应”。
同时,由于对12306网站的访问是部分地域的,可能不同的Web服务器承受这不同的压力,这种情况下可通过DNS负载均衡器将用户访问平均分配到各个Web服务器上,因为Http的请求都是短时的,所以很简单的负载均衡器就能完成这一动作。并且,CDN在这个环节能够提供很大的帮助。
并且,IBM 软件架构师景文童认为,12306网站在后期应该增加对智能移动终端的支持,那么从当前的整体带宽来看,尽量不用或少用图片,并对网页进行优化将有助于提高网站的访问速度。在都不是非首次访问的前提下,浏览器会缓存相当一部分内容,这就会明显减少带宽占用,于是负载一下子从前端迁移到了后端,数据处理瓶颈一下就凸显出来,访问者不断刷出的500错误即是明证。
除了上述所说的这些之外,还可通过减少前端网页的链接数、页面静态化以及之前提到的缓存技术来对网站前端进行优化。这些方法能够明显解决用户无法登陆或者访问缓慢等问题,但并不能解决问题真正的实质——购票。因为涉及到对数据库进行查询以及写入等操作,后端优化就显得尤为重要。
12306网站后端性能优化技术分析
关于后端性能优化技术,陈皓在其博客中表示,可通过数据冗余、数据镜像、数据分区以及后端的动态负载均衡来达到提高访问速度的目的。
数据冗余就是将数据库的数据冗余处理,也就是减少表连接这样的开销比较大的操作,但这样会牺牲数据的一致性。但这样做的风险较大,并且现在常用的方法就是利用NoSQL来做数据,数据冗余了,访问加快了,但是数据一致性就会存在较大的问题。
利用后端优化来提高访问速度的第二个方法则是数据镜像,现在几乎所有的主流数据库都支持镜像。镜像的好处就是可以做负载均衡。把一台数据库的负载均分到多台上,同时又保证了数据一致性(Oracle的SCN)。最重要的是,这样还可以有高可用性,一台废了,还有另一台在服务。但数据镜像后的数据一致性仍然是一个复杂的问题,因为要对单条数据进行分区,将其均分到不同的服务器上。
数据镜像不能解决的一个问题就是数据表里的记录太多,导致数据库操作太慢。所以,把数据分区。数据分区有很多种做法,一般来说,数据分区包含以下几种主要的方式:把数据把某种逻辑来分类、把数据按字段分,也就是竖着分表、平均分表或者同一数据分表等。关于各种方式的优缺点可查看《前后端并重 由12306.cn谈网站性能技术》。http://storage.it168.com/a2012/0117/1303/000001303243_2.shtml
数据分区可以在一定程度上减轻负载,但是无法减轻热销商品的负载,对于火车票来说,可以认为是大城市的某些主干线上的车票。这就需要使用数据镜像来减轻负载。使用数据镜像,你必然要使用负载均衡,在后端,我们可能很难使用像路由器上的负载均衡器,因为那是均衡流量的,因为流量并不代表服务器的繁忙程度。因此,我们需要一个任务分配系统,其还能监控各个服务器的负载情况。当然服务器负载均衡的技术有很多,每种方法都有各自的优缺点,这个可能要根据实际情况进行选择。
12306高性能高并发系统特点总结
实际上,通过上述的这么热议焦点,我们可以发现,要建设一个类似于12306在线购票系统还是面临着诸多难题,从系统层面上讲,其必须必备高性能、高可扩展性以及高可靠性等特点。同时,整个系统从架构设计到网页设计,甚至每段代码的编写都必须经得起严格的考验,网页代码力求简单、实用,因为那怕一小段代码问题在面对瞬时海量高并发时都有可能引发“蝴蝶效应”。
在春运期间,对于广大的贫苦老百姓而言,火车票的超高性价比和相应的方便性,使得每张票都成为稀缺资源和紧俏商品。而在12306系统开始售票那一瞬间,必然有成千上百万的人一拥而上。尽管从铁道部发布的数据来看,其最高的一天一共卖出了188万多张票,这个数据放在一天来看的话,确实不多。但是如果是在短短几分钟之内呢?那整个系统承受的压力自然不言而喻。
尽管此次铁道部采用的是分地区分时段售票,但所售票数跟抢购人数之间是没有关系的。同时因为在最早那一时段售票时就没有解决并发所带来的压力,使得已经开始发售第二时段的票时,仍有大量的人在不停地刷新,使得访问人数再次增加,系统压力亦随着增长。那么整个系统就必须使得在第一时段内卖出所有的票,而这个时间点可能就只有短短的几分钟(开始售票那几分钟,因为火车票是远远小于想要买票的人数的)。
除了整个系统需要满足高性能需求之外,还得同时具备高可扩展性(高可伸缩性)。因为从历年来的经验来看,铁道部的并发高峰通常是在节假日发生,如五一、十一长假等,春运则是最大的一个并发高峰。而目前的情况看来,12306在线购票系统虽然不能应对春运高峰,但在平时售票却还是没有问题。这就使得这个系统必须具备高可伸缩性,在并发高峰来临之前,能够通过简单的加机器或者与新浪、淘宝、腾讯等大型互联网公司合作来共同应对这些并发高峰。
在具备上述两个特点的同时,还得具备高可靠性。这么大的并发单靠一台机器是不可能实现,必须采用集群来分散压力。而在集群中,必须防备机器故障,单台机器故障之后不能影响其他机器的正常运转,并且还必须在短时而将故障修复。
除了上述三大必备性能之外,如果想拥有更好的用户体验,那么还得具备一些其他的特点。目前移动互联网正飞速向前发展,各种智能移动终端(如智能手机、平板电脑)层出不穷,作为一个方便可行的系统,那么还应该对这些移动终端提供支持。
另外,从现今角度除外,铁道部所售出的总票数是远远小于想要买票的人数的,供小于求,必然导致投机分子的存在,如“黄牛”,那么这个系统就还得作出一些措施来防范利用脚本、程序进行刷票的行为。刷票也是增加并发的一个因素之一,防止刷票也从另一个方面减少了并发,提高整个系统的可用性。