写的不错,推荐给你们。css
12306.cn 网站挂了,被全国人民骂了,以这个事来粗略地讨论一下网站性能的问题。这是一篇长文,只讨论性能问题,不讨论那些UI,用户体验,或是是否把支付和购票下单环节分开的功能性的东西。html
最近铁道部1.9亿外包订票网的事成为技术圈的热题.小编对钱和内幕神马的不懂,但当初和一个在亚马逊工做的高帅富码农聊时,被视无知,其实订票网站和百度,QQ,淘宝秒杀等负载不一样.但愿你们看看,就当学习,也没坏处.前端
业务linux
任何技术都离不开业务需求,因此,要说明性能问题,首先仍是想先说说业务问题。nginx
一,有人可能把这个东西和QQ或是网游相比。算法
但我以为这二者是不同的,网游和QQ在线或是登陆时访问的更多的是用户本身的数据,而订票系统访问的是中心的票量数据,这是不同的。不要以为网游或是QQ能行你就觉得这是同样的。网游和QQ 的后端负载相对于电子商务的系统仍是简单。数据库
二,有人说春节期间订火车的这个事好像网站的秒杀活动。后端
的确很类似,可是若是你的思考不在表面的话,你会发现这也有些不同。火车票这个事,还有不少查询操做,查时间,查座位,查铺位,一个车次不 行,又查另外一个车次,其伴随着大量的查询操做,下单的时候须要对数据库操做。而秒杀,直接杀就行了。另外,关于秒杀,彻底能够作成只接受前N个用户的请求(彻底不操做后端的任何数据, 仅仅只是对用户的下单操做log),这种业务,只要把各个服务器的时间精确同步了就能够了,无需在当时操做任何数据库。能够订单数够后,中止秒杀,而后批量写数据库。火车票这个岂止是秒杀那么简单。能不能买到票得当时告诉用户啊。浏览器
三,有人拿这个系统和奥运会的票务系统比较。缓存
我以为仍是不同。虽然奥运会的票务系统当年也一上线就废了。可是奥运会用的是抽奖的方式,也就是说不存在先来先得的抢的方式,并且,是过后抽奖,事前只须要收信息,事前不须要保证数据一致性,没有锁,很容易水平扩展。
四,订票系统应该和电子商务的订单系统很类似。
都是须要对库存进行:1)占住库存,2)支付(可选),3)扣除库存的操做。这个是须要有一致性的检查的,也就是在并发时须要对数据加锁的。B2C的电商基本上都会把这个事干成异步的,也就是说,你下的订单并非立刻处理的,而是延时处理的,只有成功处理了,系统才会给你一封确认邮件说是订单成功。我相信有不少朋友都收到认单不成功的邮件。这就是说,数据一致性在并发下是一个瓶颈。
五,铁路的票务业务很变态。
其采用的是忽然放票,而有的票又远远不够你们分,因此,你们才会有抢票这种有中国特点的业务的作法。因而当票放出来的时候,就会有几百万人甚至上千万人杀上去,查询,下单。几十分钟内,一个网站能接受几千万的访问量,这个是很恐怖的事情。听说12306的高峰访问是10亿PV,集中在早8点到10点,每秒PV在高峰时上千万。
多说几句:
1、库存是B2C的恶梦,库存管理至关的复杂。
不信,你能够问问全部传统和电务零售业的企业,看看他们管理库存是多么难的一件事。否则,就不会有那么多人在问凡客的库存问题了。(你还能够看看《乔布斯传》,你就知道为何Tim会接任Apple的CEO了,由于他搞定了苹果的库存问题)
2、对于一个网站来讲,浏览网页的高负载很容易搞定,查询的负载有必定的难度去处理,不过仍是能够经过缓存查询结果来搞定,最难的就是下单的负载。
由于要访问库存啊,对于下单,基本上是用异步来搞定的。去年双11节,淘宝的每小时的订单数大约在60万左右,京东一天也才能支持40万(竟然比12306还差),亚马逊5年前一小时可支持70万订单量。可见,下订单的操做并无咱们相像的那么性能高。
3、淘宝要比B2C的网站要简单得多,由于没有仓库。
因此,不存在像B2C这样有N个仓库对同一商品库存更新和查询的操做。下单的时候,B2C的 网站要去找一个仓库,又要离用户近,又要有库存,这须要不少计算。试想,你在北京买了一本书,北京的仓库没货了,就要从周边的仓库调,那就要去看看沈阳或 是西安的仓库有没有货,若是没有,又得看看江苏的仓库,等等。淘宝的就没有那么多事了,每一个商户有本身的库存,库存分到商户头上了,反而有利于性能。
4、数据一致性才是真正的性能瓶颈。
有人说nginx能够搞定每秒10万的静态请求,我不怀疑。但这只是静态请求,理论值,只要带宽、I/O够强,服务器计算能力够,并支持的并发链接数顶得住10万TCP连接的创建 的话,那没有问题。但在数据一致性面前,这10万就完彻底全成了一个可望不可及的理论值了。
我说那么多,我只是想从业务上告诉你们,咱们须要从业务上真正了解春运铁路订票这样业务的变态之处。
前端性能优化技术
要解决性能的问题,有不少种经常使用的方法,我在下面列举一下,我相信12306这个网站使用下面的这些技术会让其性能有质的飞跃。
1、前端负载均衡
经过DNS的负载均衡器(通常在路由器上根据路由的负载重定向)能够把用户的访问均匀地分散在多个Web服务器上。这样能够减小Web服务器的请求负载。由于http的请求都是短做业,因此,能够经过很简单的负载均衡器来完成这一功能。最好是有CDN网络让用户链接与其最近的服务器(CDN一般伴随着分布式存储)。(关于负载均衡更为详细的说明见“后端的负载均衡”)
2、减小前端连接数
我看了一下12306.cn,打开主页须要建60多个HTTP链接,车票预订页面则有70多个HTTP请求,如今的浏览器都是并发请求的。因此,只要有100万个用户,就会有6000万个连接,太多了。一个登陆查询页面就行了。把js打成一个文件,把css也打成一个文件,把图标也打成一个文件,用css分块展现。把连接数减到最低。
3、减小网页大小增长带宽
这个世界不是哪一个公司都敢作图片服务的,由于图片太耗带宽了。如今宽带时代很难有人能体会到当拨号时代作个图页都不敢用图片的情形(如今在手机端浏览也是这个情形)。我查看了一下12306首页的须要下载的总文件大小大约在900KB左右,若是你访问过了,浏览器会帮你缓存不少,只需下载10K左右的文件。可是咱们能够想像一个极端一点的案例,1百万用户同时访问,且都是第一次访问,每人下载量须要1M,若是须要在120秒内返回,那么就须要,1M * 1M /120 * 8 = 66Gbps的带宽。很惊人吧。因此,我估计在当天,12306的阻塞基本上应该是网络带宽,因此,你可能看到的是没有响应。后面随着浏览器的缓存帮助12306减小不少带宽占用,因而负载一下就到了后端,后端的数据处理瓶颈一下就出来。因而你会看到不少http 500之类的错误。这说明服务器垮了。
4、前端页面静态化
静态化一些不常变的页面和数据,并gzip一下。还有一个并态的方法是把这些静态页面放在/dev/shm下,这个目录就是内存,直接从内存中把文件读出来返回,这样能够减小昂贵的磁盘I/O。
5、优化查询
不少人查询都是在查同样的,彻底能够用反向代理合并这些并发的相同的查询。这样的技术主要用查询结果缓存来实现,第一次查询走数据库得到数据,并把数据放到缓存,后面的查询通通直接访问高速缓存。为每一个查询作Hash,使用NoSQL的技术能够完成这个优化(这个技术也能够用作静态页面)对于火车票量的查询,我的以为不要显示数字,就显示一个“有”或“无”就行了,这样能够大大简化系统复杂度,并提高性能。
6、缓存的问题
缓存能够用来缓存动态页面,也能够用来缓存查询的数据。缓存一般有那么几个问题:
1)缓存的更新。也叫缓存和数据库的同步。有这么几种方法,一是缓存time out,让缓存失效,重查,二是,由后端通知更新,一量后端发生变化,通知前端更新。前者实现起来比较简单,但实时性不高,后者实现起来比较复杂 ,但实时性高。
2)缓存的换页。内存可能不够,因此,须要把一些不活跃的数据换出内存,这个和操做系统的内存换页和交换内存很类似。FIFO、LRU、LFU都是比较经典的换页算法。相关内容参看Wikipeida的缓存算法。
3)缓存的重建和持久化。缓存在内存,系统总要维护,因此,缓存就会丢失,若是缓存没了,就须要重建,若是数据量很大,缓存重建的过程会很慢,这会影响生产环境,因此,缓存的持久化也是须要考虑的。诸多强大的NoSQL都很好支持了上述三大缓存的问题。
后端性能优化技术
前面讨论了前端性能的优化技术,因而前端可能就不是瓶颈问题了。那么性能问题就会到后端数据上来了。下面说几个后端常见的性能优化技术。
1、数据冗余
关于数据冗余,也就是说,把咱们的数据库的数据冗余处理,也就是减小表链接这样的开销比较大的操做,但这样会牺牲数据的一致性。风险比较大。不少人把NoSQL用作数据,快是快了,由于数据冗余了,但这对数据一致性有大的风险。这须要根据不一样的业务进行分析和处理。(注意:用关系型数据库很容易移植到NoSQL上,可是反过来从NoSQL到关系型就难了)
2、数据镜像
几乎全部主流的数据库都支持镜像,也就是replication。数据库的镜像带来的好处就是能够作负载均衡。把一台数据库的负载均分到多台上,同时又保证了数据一致性(Oracle的SCN)。最重要的是,这样还能够有高可用性,一台废了,还有另外一台在服务。数据镜像的数据一致性多是个复杂的问题,因此咱们要在单条数据上进行数据分区,也就是说,把一个畅销商品的库存均分到不一样的服务器上,如,一个畅销商品有1万的库存,咱们能够设置10台服务器,每台服务器上有1000个库存,这就好像B2C的仓库同样。
3、数据分区
数据镜像不能解决的一个问题就是数据表里的记录太多,致使数据库操做太慢。因此,把数据分区。数据分区有不少种作法,通常来讲有下面这几种:
1)把数据把某种逻辑来分类。好比火车票的订票系统能够按各铁路局来分,可按各类车型分,能够按始发站分,能够按目的地分……,反正就是把一张表拆成多张有同样的字段可是不一样种类的表,这样,这些表就能够存在不一样的机器上以达到分担负载的目的。
2)把数据按字段分,也就是竖着分表。好比把一些不常常改的数据放在一个表里,常常改的数据放在另外多个表里。把一张表变成1对1的关系,这样,你能够减小表的字段个数,一样能够提高必定的性能。另外,字段多会形成一条记录的存储会被放到不一样的页表里,这对于读写性能都有问题。但这样一来会有不少复杂的控制。
3)平均分表。由于第一种方法是并不必定平均分均,可能某个种类的数据仍是不少。因此,也有采用平均分配的方式,经过主键ID的范围来分表。
4)同一数据分区。这个在上面数据镜像提过。也就是把同一商品的库存值分到不一样的服务器上,好比有10000个库存,能够分到10台服务器上,一台上有1000个库存。而后负载均衡。
这三种分区都有好有坏。最经常使用的仍是第一种。数据一旦分区,你就须要有一个或是多个调度来让你的前端程序知道去哪里找数据。把火车票的数据分区,并放在各个省市,会对12306这个系统有很是有意义的质的性能的提升。
4、后端系统负载均衡
前面说了数据分区,数据分区能够在必定程度上减轻负载,可是没法减轻热销商品的负载,对于火车票来讲,能够认为是大城市的某些主干线上的车票。这就须要使用数据镜像来减轻负载。使用数据镜像,你必然要使用负载均衡,在后端,咱们可能很难使用像路由器上的负载均衡器,由于那是均衡流量的,由于流量并不表明服务器的繁忙程度。所以,咱们须要一个任务分配系统,其还能监控各个服务器的负载状况。
任务分配服务器有一些难点:
负载状况比较复杂。什么叫忙?是CPU高?仍是磁盘I/O高?仍是内存使用高?仍是并发高?仍是内存换页率高?你可能须要所有都要考虑。这些信息要发送给那个任务分配器上,由任务分配器挑选一台负载最轻的服务器来处理。
任务分配服务器上须要对任务队列,不能丢任务啊,因此还须要持久化。而且能够以批量的方式把任务分配给计算服务器。
任务分配服务器死了怎么办?这里须要一些如Live-Standby或是failover等高可用性的技术。咱们还须要注意那些持久化了的任务的队列如何转移到别的服务器上的问题。
我看到有不少系统都用静态的方式来分配,有的用hash,有的就简单地轮流分析。这些都不够好,一个是不能完美地负载均衡,另外一个静态的方法的致命缺陷是,若是有一台计算服务器死机了,或是咱们须要加入新的服务器,对于咱们的分配器来讲,都须要知道的。
还有一种方法是使用抢占式的方式进行负载均衡,由下游的计算服务器去任务服务器上拿任务。让这些计算服务器本身决定本身是否要任务。这样的好处是能够简化系统的复杂度,并且还能够任意实时地减小或增长计算服务器。可是惟一很差的就是,若是有一些任务只能在某种服务器上处理,这可能会引入一些复杂度。不过整体来讲,这种方法多是比较好的负载均衡。
5、异步、 throttle 和 批量处理
异步、throttle(节流阀) 和批量处理都须要对并发请求数作队列处理的。
异步在业务上通常来讲就是收集请求,而后延时处理。在技术上就是能够把各个处理程序作成并行的,也就能够水平扩展了。可是异步的技术问题大概有这些,a)被调用方的结果返回,会涉及进程线程间通讯的问题。b)若是程序须要回滚,回滚会有点复杂。c)异步一般都会伴随多线程多进程,并发的控制也相对麻烦一些。d)不少异步系统都用消息机制,消息的丢失和乱序也会是比较复杂的问题。
throttle 技术其实并不提高性能,这个技术主要是防止系统被超过本身不能处理的流量给搞垮了,这实际上是个保护机制。使用throttle技术通常来讲是对于一些本身没法控制的系统,好比,和你网站对接的银行系统。
批量处理的技术,是把一堆基本相同的请求批量处理。好比,你们同时购买同一个商品,没有必要你买一个我就写一次数据库,彻底能够收集到必定数量的请求,一次操做。这个技术能够用做不少方面。好比节省网络带宽,咱们都知道网络上的MTU(最大传输单元),以态网是1500字节,光纤能够达到4000多个字节,若是你的一个网络包没有放满这个MTU,那就是在浪费网络带宽,由于网卡的驱动程序只有一块一块地读效率才会高。所以,网络发包时,咱们须要收集到足够多的信息后再作网络I/O,这也是一种批量处理的方式。批量处理的敌人是流量低,因此,批量处理的系统通常都会设置上两个阀值,一个是做业量,另外一个是timeout,只要有一个条件知足,就会开始提交处理。
因此,只要是异步,通常都会有throttle机制,通常都会有队列来排队,有队列,就会有持久化,而系统通常都会使用批量的方式来处理。云风同窗设计的“排队系统” 就是这个技术。这和电子商务的订单系统很类似,就是说,个人系统收到了你的购票下单请求,可是我尚未真正处理,个人系统会跟据我本身的处理能力来throttle住这些大量的请求,并一点一点地处理。一旦处理完成,我就能够发邮件或短信告诉用户你来能够真正购票了。
在这里,我想经过业务和用户需求方面讨论一下云风同窗的这个排队系统,由于其从技术上看似解决了这个问题,可是从业务和用户需求上来讲可能仍是有一些值得咱们去深刻思考的地方:
1)队列的DoS攻击。首先,咱们思考一下,这个队是个单纯地排队的吗?这样作还不够好,由于这样咱们不能杜绝黄牛,并且单纯的ticket_id很容易发生DoS攻击,好比,我发起N个 ticket_id,进入购票流程后,我不买,我就耗你半个小时,很容易我就可让想买票的人几天都买不到票。有人说,用户应该要用身份证来排队, 这样在购买里就必须要用这个身份证来买,但这也还不能杜绝黄牛排队或是号贩子。由于他们能够注册N个账号来排队,但就是不买。黄牛这些人这个时候只须要干一个事,把网站搞得正常人不能访问,让用户只能经过他们来买。
2)对列的一致性?对这个队列的操做是否是须要锁?只要有锁,性能必定上不去。试想,100万我的同时要求你来分配位置号,这个队列将会成为性能瓶颈。你必定没有数据库实现得性能好,因此,可能比如今还差
3)队列的等待时间。购票时间半小时够不够?多很少?要是那时用户正好不能上网呢?若是时间短了,用户不够时间操做也会抱怨,若是时间长了,后面在排队的那些人也会抱怨。这个方法可能在实际操做上会有不少问题。另外,半个小时太长了,这彻底不现实,咱们用15分钟来举例:有1千万用户,每个时刻只能放进去1万个,这1万个用户须要15分钟完成全部操做,那么,这1千万用户所有处理完,须要1000*15m = 250小时,10天半,火车早开了。(我并不是乱说,根据铁道部专家的说明:这几天,平均一天下单100万,因此,处理1000万的用户须要十天。这个计算可能有点简单了,我只是想说,在这样低负载的系统下用排队可能都不能解决问题)
4)队列的分布式。这个排队系统只有一个队列好吗?还不足够好。由于,若是你放进去的能够购票的人若是在买同一个车次的一样的类型的票(好比某动车卧铺),仍是等于在抢票,也就是说系统的负载仍是会有可能集中到其中某台服务器上。所以,最好的方法是根据用户的需求——提供出发地和目的地,来对用户进行排队。而这样一来,队列也就能够是多个,只要是多个队列,就能够水平扩展了。
我以为彻底能够向网上购物学习。在排队(下单)的时候,收集好用户的信息和想要买的票,并容许用户设置购票的优先级,好比,A车次卧铺买 不到就买 B车次的卧铺,若是还买不到就买硬座等等,而后用户把所需的钱先充值好,接下来就是系统彻底自动地异步处理订单。成功不成功都发短信或邮件通知用户。这样,系统不只能够省去那半个小时的用户交互时间,自动化加快处理,还能够合并相同购票请求的人,进行批处理(减小数据库的操做次数)。这种方法最妙的事是能够知道这些排队用户的需求,不但能够优化用户的队列,把用户分布到不一样的队列,还能够像亚马逊的心愿单同样,让铁道部作车次统筹安排和调整(最后,排队系统(下单系统)仍是要保存在数据库里的或作持久化,不能只放在内存中,否则机器一down,就等着被骂吧)。
小结
写了那么多,我小结一下:
0)不管你怎么设计,你的系统必定要能容易地水平扩展。也就是说,你的整个数据流中,全部的环节都要可以水平扩展。这样,当你的系统有性能问题时,“加3倍的服务器”才不会被人讥笑。
1)上述的技术不是一朝一夕能搞定的,没有长期的积累,基本无望。咱们能够看到,不管你用哪一种都会引起一些复杂性。
2)集中式的卖票很难搞定,使用上述的技术可让订票系统能有几佰倍的性能提高。而在各个省市建分站,分开卖票,是能让现有系统性能有质的提高的最好方法。
3)春运前夕抢票且票量供远小于求这种业务模式是至关变态的,让几千万甚至上亿的人在某个早晨的8点钟同时登陆同时抢票的这种业务模式是变态中的变态。业务形态的变态决定了不管他们怎么办干必定会被骂。
4)为了那么一两个星期而搞那么大的系统,而其它时间都在闲着,有些惋惜了,这也就是铁路才干得出来这样的事了。
本文转载,请勿于记商业目的