12306.cn 网站挂了,被全国人民骂了。我这两天也在思考这个事,我想以这个事来粗略地和大家讨论一下网站性能的问题。因为仓促,而且完全基于本人有限的经验和了解, 所以,如果有什么问题还请大家一起讨论和指正。(这又是一篇长文,只讨论性能问题,不讨论那些用户界面、用户体验、或是是否把支付和购票下单环节分开的功 能性的东西)
甲、认识业务的特殊性
任何技术都离不开业务需求,所以,要说明性能问题,首先还是想先说说业务问题。
- 其一,有人可能把这个东西和扣扣或是网游相比。但我觉得这两者是不一样的,网游和扣扣在线或是登录时访问的更多的是用户自己的数据,而订票系统访问的是中心的票量数据,这是不一样的。不要觉得网游或是扣扣能行你就以为这是一样的。网游和扣扣的后台负载相对于电子商务的系统还是简单。
- 其二,有人说春节期间订火车的这个事好像网站的秒杀活动。的确很相似, 但 是如果你的思考不在表面的话,你会发现这也有些不一样。火车票这个事,还有很多查询操作,查时间,查座位,查铺位,一个车次不 行,又查另一个车次,其伴随着大量的查询操作,下单的时候需要对数据库操作。而秒杀,直接杀就好了。另外,关于秒杀,完全可以做成只接受前N个用户的请求 (完全不操作后端的任何数据, 仅仅只是对用户的下单操作log),这种业务,只要把各个服务器的时间精确同步了就可以了,无需在当时操作任何数据库。可以订单数够后,停止秒杀,然后批 量写数据库。火车票这个岂止是秒杀那么简单。能不能买到票得当时告诉用户啊。
- 其三,有人拿这个系统和奥运会的票务系统比较。我觉得还是不一样。虽然奥运会的票务系统当年也一上线就废了。但是奥运会用的是抽奖的方式,也就是说不存在先来先得的抢的方式,而且,是事后抽奖,事前只需要收信息,事前不需要保证数据一致性,没有锁,很容易水平扩展。
- 其四,订票系统应该和电子商务的订单系统很相似,都是需要对库存进 行:1)占住库存,2)支付(可选),3)扣除库存的操作。这个是需要有一致性的检查的,也就是在并发时需要对数据加锁的。B2C的电商基本上都会把这个 事干成异步的,也就是说,你下的订单并不是马上处理的,而是延时处理的,只有成功处理了,系统才会给你一封确认邮件说是订单成功。我相信有很多朋友都收到 认单不成功的邮件。这就是说,数据一致性在并发下是一个瓶颈。
- 其五,铁路的票务业务很变态,其采用的是突然放票,而有的票又远远不够 大家分,所以,大家才会有抢票这种有中国特色的业务的做法。于是当票放出来的时候,就会有几百万人甚至上千万人杀上去、查询、下单。几十分钟内,一个网站 能接受几千万的访问量,这个是很恐怖的事情。据说12306的高峰访问是10亿页面访问量,集中在早8点到10点,每秒页面访问量在高峰时上千万。
多说几句:
- 库存是B2C的恶梦,库存管理相当的复杂。不信,你可以问问所有传统和电务零售业的企业,看看他们管理库存是多么难的一件事。不然,就不会有那么多人在问凡客的库存问题了。(你还可以看看《乔布斯传》,你就知道为什么Tim会接任Apple的CEO了,因为他搞定了苹果的库存问题)
- 对于一个网站来说,浏览网页的高负载很容易搞定,查询的负载有一定的难度去处理,不过还是可以通过缓存查询结果来搞定,最难的就是下单的负载。因为要访问库存啊,对于下单,基本上是用异步来搞定的。去年双11节,淘宝的每小时的订单数大约在60万左右,京东一天也才能支持40万(居然比12306还差),亚马逊5年前一小时可支持70万订单量。可见,下订单的操作并没有我们相像的那么性能高。
- 淘宝要比B2C的网站要简单得多,因为没有仓库,所以,不存在像B2C这样有N个仓库对同一商品库存更新和 查 询的操作。下单的时候,B2C的 网站要去找一个仓库,又要离用户近,又要有库存,这需要很多计算。试想,你在北京买了一本书,北京的仓库没货了,就要从周边的仓库调,那就要去看看沈阳或 是西安的仓库有没有货,如果没有,又得看看江苏的仓库,等等。淘宝的就没有那么多事了,每个商户有自己的库存,库存分到商户头上了,反而有利于性能。
- 数据一致性才是真正的性能瓶颈。有 人说nginx可以搞定每秒10万的静态请求,我不怀疑。但这只是静态请求,理论值,只要带宽、读写性能够强、服务器计算能力够,并支持的并发连接数顶得住 10万TCP链接的建立的话,那没有问题。但在加上数据一致性这一要求,这10万就完完全全成了一个可望不可及的理论值了。
我说那么多,我只是想从业务上告诉大家,我们需要从业务上真正了解春运铁路订票这样业务的变态之处。
乙、前端性能优化技术
要解决性能的问题,有很多种常用的方法,我在下面列举一下,我相信12306这个网站使用下面的这些技术会让其性能有质的飞跃。
一、前端负载均衡
通过DNS的负载均衡器(一般在路由器上根据路由的负载重定向)可以把用户的访问均匀地分散在多个Web服务器上。这样可以减少Web服务器的请求 负载。因为http的请求都是短作业,所以,可以通过很简单的负载均衡器来完成这一功能。最好是有CDN网络让用户连接与其最近的服务器(CDN通常伴随 着分布式存储)。(关于负载均衡更为详细的说明见“后端的负载均衡”)
二、减少前端链接数
我看了一下12306.cn,打开主页需要建60多个HTTP连接,车票预订页面则有70多个HTTP请求,现在的浏览器都是并发请求的。所以,只 要有100万个用户,就会有6000万个链接,太多了。一个登录查询页面就好了。把js打成一个文件,把css也打成一个文件,把图标也打成一个文件,用 css分块展示。把链接数减到最低。
—— 注:以前拨号连接时代,因为带宽小,大图片没下载完,用户已经关闭了浏览器页面,所以对连接数要求不高,但对一次的下载量要求很高。但现在带宽已经足够, 一次的下载量已经不再是瓶颈,所以,不能再以过去那种方式处理这个问题,而应该合并文件,尽量减少请求数,从而减少交互频率,把节约出的交互资源用于网站 和用户的其他更有价值的互动上。
三、减少网页大小增加带宽
这个世界不是哪个公司都敢做图片服务的,因为图片太耗带宽了。现在宽带时代很难有人能体会到当拨号时代做个图页都不敢用图片的情形(现在在手机端浏 览也是这个情形)。我查看了一下12306首页的需要下载的总文件大小大约在900KB左右,如果你访问过了,浏览器会帮你缓存很多,只需下载10K左右 的文件。但是我们可以想像一个极端一点的案例,1百万用户同时访问,且都是第一次访问,每人下载量需要1M,如果需要在120秒内返回,那么就需要,1M * 1M /120 * 8 = 66Gbps的带宽。很惊人吧。所以,我估计在当天,12306的阻塞基本上应该是网络带宽,所以,你可能看到的是没有响应。后面随着浏览器的缓存帮助 12306减少很多带宽占用,于是负载一下就到了后台,后台的数据处理瓶颈一下就出来。于是你会看到很多http 500之类的错误。这说明服务器垮了。
四、前端页面静态化
静态化一些不常变的页面和数据,并压缩一下(比如gzip,当然,也要考虑压缩和读取的资源消耗问题)。还有一个变态的方法是把这些静态页面放在内存,也就是(/dev/shm目录下),直接从内存中把文件读出来返回,这样可以减少对磁盘的读写
注:因为磁盘读写相对于内存读写来说太慢。
五、优化查询
很多人查询都是在查一样的,完全可以用反向代理合并这些并发的相同的查询。这样的技术主要用查询结果缓存来实现,第一次查询走数据库获得数据,并把 数据放到缓存,后面的查询统统直接访问高速缓存。为每个查询做Hash,使用NoSQL的技术可以完成这个优化。(这个技术也可以用做静态页面)
对于火车票量的查询,个人觉得不要显示数字,就显示一个“有”或“无”就好了,这样可以大大简化系统复杂度,并提升性能。
六、缓存的问题
缓存可以用来缓存动态页面,也可以用来缓存查询的数据。缓存通常有那么几个问题:
1)缓存的更新方式:也叫缓存和数据库的同步。有这么几种方法,一是缓存过时失效(也就是time out),在经过一定时间后,让缓存失效,重查;二是,由后端通知更新,一旦后端发生变化,通知前端更新。前者实现起来比较简单,但实时性不高,后者实现 起来比较复杂 ,但实时性高。
2)缓存的换页。内存可能不够,所以,需要把一些不活跃的数据换出内存,这个和操作系统的内存换页和交换内存很相似。FIFO、LRU、LFU都是比较经典的换页算法。相关内容参看Wikipeida的缓存算法。
3)缓存的重建和持久化。缓存在内存,系统总要维护,所以,缓存就会丢失,如果缓存没了,就需要重建,如果数据量很大,缓存重建的过程会很慢,这会影响生产环境,所以,缓存的持久化也是需要考虑的。
诸多强大的NoSQL都很好支持了上述三大缓存的问题。
丙、后端性能优化技术
前面讨论了前端性能的优化技术,于是前端可能就不是瓶颈问题了。那么性能问题就会到后台数据上来了。下面说几个后台常见的性能优化技术。
一、冗余数据
允许数据库出现冗余数据,具体方法就是减少表连接这样的开销比 较大的操作,但这样会牺牲数据的一致性,风险比较大。很多人把NoSQL用做数据,快是快了,因为数据冗余了,但这对数据一致性有大的风险。这需要根据不 同的业务进行分析和处理。(注意:用关系型数据库很容易移植到NoSQL上,但是反过来从NoSQL到关系型就难了)
二、镜像数据
几乎所有主流的数据库都支持镜像(replication)。数据库的镜像带来的好处就是可以做负载均衡。把一台数据库的负载均分到多台上,同时又保证了数据一致性(Oracle的SCN)。最重要的是,这样还可以有高可用性,一台废了,还有另一台在服务。
数据镜像的数据一致性可能是个问题,所以我们要在单条数据上进行数据分区,也就是说,把一个畅销商品的库存均分到不同的服务器上,如,一个畅销商品有1万的库存,我们可以设置10台服务器,每台服务器上有1000个库存,这就好像B2C的仓库一样。
注:数据镜像一致性问题其实没那么好解决,日常现实中经常遇到某一后操作失误,需要恢复到前操作,但因为系统本身是自动化处理,往往只注意到数据完整性这种问题,而没法注意到数据对人的意义问题,从而可能这样的一致性机制反而会造成现实的灾难性,所以,还必须设置一条人工的确认操作(可以有多种方式,比如用户的订单提交再次确认),否则数据一致性会令人抓狂
三、数据拆分(数据分区)
数据镜像不能解决的一个问题就是数据表里的记录太多,导致数据库操作太慢。所以,把数据拆分开来。数据拆分有很多种做法,一般来说有下面这几种:
1)把数据把某种逻辑来分类。比如火车票的订票系统可以按各铁路局来分,可按各种车型分,可以按始发站分,可以按目的地分……,反正就是把一张表拆成多张有一样的字段但是不同种类的表,这样,这些表就可以存在不同的机器上以达到分担负载的目的。
2)把数据按字段分,也就是坚着分表。比如把一些不经常改的数据放在一个表里,经常改的数据放在另一个表里。把一张表变成1对1的关系,这样,你可
以减少表的字段个数,同样可以提升一定的性能。另外,字段多会造成一条记录的存储会被放到不同的页表里,这对于读写性能都有问题。
3)平均分表。因为前两种种方法是并不一定平均分均,可能某个种类的数据还是很多。所以,也有采用平均分配的方式,通过主键ID的范围来分表。
4)同一数据拆开存放。这个在上面数据镜像提过。也就是把同一商品的库存值分到不同的服务器上,比如有10000个库存,可以分到10台服务器上,一台上有1000个库存。然后负载均衡。
这四种分区都有好有坏。最常用的还是第一种。
注:数据一旦拆分存放,就需要有一个或是多个调度来让你的前端程序知道去哪里找数据,这种调度会消耗反应时间,因此,不可分得太细,调度表和拆开来的表大小比例如何?
把火车票的数据分区,并放在各个省市,会对12306这个系统有非常有意义的质的性能的提高。
四、后台系统负载均衡
前面说了数据拆分存放,数据拆分存放可以在一定程度上减轻系统负载,但是无法减轻热销商品的负载,对于火车票来说,可以认为是大城市的某些主干线上
的车票。这就需要镜像数据来减轻负载。镜像了数据,必然要用到负载均衡,在后台,我们可能很难使用像路由器上的负载均衡器,因为那是均衡流量的,因为流量
并不代表服务器的繁忙程序。因此,我们需要一个任务分配系统,其还能监控各个服务器的负载情况。
任务分配服务器有一些难点:
- 负载情况比较复杂。什么叫忙:是核芯处理量高?还是磁盘读写频率高?还是内存使用量高?还是并发数高?你可能需要全部都要考虑。这些信息要发送给那个任务分配器上,由任务分配器挑选一台负载最轻的服务器来处理。
- 任务分配服务器上需要对任务队列,不能丢任务啊,所以还需要持久化。并且可以以批量的方式把任务分配给服务器。
- 任务分配服务器死了怎么办?这里需要一些如即时替用(Live-Standby)或是失效备援(failover)等高可用性的技术。我们还需要注意那些持久化了的任务的队列如果转移到别的服务器上的问题。
*分配式:看到有很多系统都用静态的方式来分配,有的用hash,有的就简单地轮流分析。这些都不够好,一个是不能完美地负载均衡,另一个静态的方法的致命缺陷是,如果有一台计算服务器死机了,或是我们需要加入新的服务器,对于我们的分配器来说,都需要重新刷新。
下游请缨式:由下游的计算服务器去任务服务器上拿任务。让这些计算服务器自己决定自己是否要任务。这样的好处是可以简化系统的复杂度,而且还可以任意实时地减少或增加计算服务器。但是唯一不好的就是,如果有一些任务只能在某种服务器上处理,这可能会引入一些复杂度。
不过总体来说,这种方法可能是比较好的负载均衡。
五、异步处理、 限流阀 和 批量处理
异步、限流阀(throttle) 和批量处理都需要对并发请求数做队列处理的。
- 异步处理:在业务上一般来说就是收集请求,然后延时处理。在技术上就是可以把各个处理程序做成并行的,也就可以水平扩展了。但是异步的技术问题大概有这
些,1)被调用方的结果返回,会涉及进程线程间通信的问题。2)如果程序需要回滚,回滚会有点复杂。3)异步通常都会伴随多线程多进程,并发的控制也相对麻烦一些。4)很多异步系统都用消息机制,消息的丢失和乱序也会是比较复杂的问题。
- 限流阀: 这其实是个保护机制,是在不提升性能和资源消耗的情况下,防止系统被超过自己不能处理量给搞垮了,这样系统看起来会显得稳健!使用限流阀技术一般来说是对于一些自己无法有效控制或者是需要稳定不中断运行的系统,比如,和你网站对接的银行系统,或者是长城防火墙。
- 批量处理的技术,是把一堆基本相同的请求批量处理。
比如,大家同时购买同一个商品,没有必要你买一个我就写一次数据库,完全可以收集到一定数量的请求,一次操作。这个技术可以用作很多方面。比如节省网络带
宽,我们都知道网络上的最大单位传输量(MTU,或者翻译为最大传输单元,就是一次传输的量),以太网是1500字节,光纤可以达到4000多个字节,如果你的一个网络包没有放满这个最大传输单元,那就是在浪费网络带宽,因为网卡的驱动程序只有一块一块地读效率才会高。因此,网络发包时,我们需要收集到足够多的信息后再做网络吞吐,这也是一种批量处理的方式。批量处理的敌人是流量低,所以,批量处理的系统一般都会设置上两个阀值,一个是作业量达标,另一个是自动过时失效,只要有一个条件满足,就会开始提交处理。
所以,只要是异步,一般都会有限流阀机制,一般都会有队列来排队,有队列,就会有持久化,而系统一般都会使用批量的方式来处理。
云风同学设计的“排队系统” 就是这个技术。这和电子商务的订单系统很相似,就是说,我的系统收到了你的购票下单请求,但是我还没有真正处理,我的系统会跟据我自己的处理能力来限制住这些大量的请求,并一点一点地处理。一旦处理完成,我就可以发邮件或短信告诉用户你来可以真正购票了。
在这里,我想通过业务和用户需求方面讨论一下云风同学的这个排队系统,因为其从技术上看似解决了这个问题,但是从业务和用户需求上来说可能还是有一些值得我们去深入思考的地方:
1)队列的DoS攻击。首先,我们思考一下,这个队是个单纯地排队的吗?这样做还不够好,因为这样我们不能杜绝黄牛,而且单纯的ticket_id很容易发生DoS攻击,比如,我发起无数个
ticket_id,进入购票流程后,我不买,我就耗你半个小时,很容易我就可以让想买票的人几天都买不到票。有人说,用户应该要用身份证来排队,
这样在购买里就必须要用这个身份证来买,但这也还不能杜绝黄牛排队或是号贩子。因为他们可以注册无数个帐号来排队,但就是不买。黄牛这些人这个时候只需要干一个事,把网站搞得正常不能访问,让用户只能通过他们来买。
注:可见,除了限流阀机制,还要一个过期机制,外加一些辅助检测手段(比如需要认证的登陆)——其实,此处比拼的就是网站和黄牛的硬件实力了,只要能够把门槛提高到黄牛的硬件投入超越边际效益临界点,即可。
2)对列的一致性?对这个队列的操作是不是需要锁?只要有锁,性能一定上不去。试想,100万个人同时要求你来分配位置号,这个队列将会成为性能瓶颈。你一定没有数据库实现得性能好,所以,可能比现在还差
3)队列的等待时间。购票时间半小时够不够?多不多?要是那时用户正好不能上网呢?如果时间短了,用户也会抱
怨,如果时间长了,后面在排队的那些人也会抱怨。这个方法可能在实际操作上会有很多问题。另外,半个小时太长了,这完全不现实,我们用15分钟来举例:有
1千万用户,每一个时刻只能放进去1万个,这1万个用户需要15分钟完成所有操作,那么,这
1千万用户全部处理完,需要1000*15m = 250小时,10天半,火车早开了。(我并乱说,根据铁道部专家的说明:这几天,平均一天下单100万,所以,处理1000万的用户需要十天。这个计算可能有点简单了,我只是想说,在这样低负载的系统下用排队可能都不能解决问题)
注:
这说法有点问题:首先,一趟车的载人数是有限的,所以并没有必要处理1千万用户的需求,可以通过设定提前购票天数,来分流这些人员。但按照春运总十亿人次
来算的话,如果一次能放进10万个,10万×15分钟=25000小时,则需要1042天处理完,如果一次能放进100万,则需要104天处理完,还是不
够,春运没有那么多天,一次需要放进1000万,才够!
4)队列的分布式。这个排队系统只有一个队列好吗?
还不足够好。因为,如果你放进去的可以购票的人如果在买同一个车次的同样的类型的票(比如某动车卧铺),还是等于在抢票,也就是说系统的负载还是会有可能
集中到其中某台服务器上。因此,最好的方法是根据用户的需求——提供出发地和目的地,来对用户进行排队。而这样一来,队列也就可以是多个,只要是多个队
列,就可以水平扩展了。
我觉得完全可以向网上购物学习。在排队(下单)的时候,收集好用户的信息和想要买的票,并允许用户设置购票的优先级,比如,A车次卧铺买 不到就买
B车次的卧铺,如果还买不到就买硬座等等,然后用户把所需的钱先充值好,接下来就是系统完全自动地异步处理订单。成功不成功都发短信或邮件通知用户。这样,系统不仅可以省去那半个小时的用户交互时间,自动化加快处理,还可以合并相同购票请求的人,进行批处理(减少数据库的操作次数)。这种方法最妙的事是可以知道这些排队用户的需求,不但可以优化用户的队列,把用户分布到不同的队列,还可以像亚马逊的心愿单一样,让铁道部做车次统筹安排和调整(最后,排队
系统(下单系统)还是要保存在数据库里的或做持久化,不能只放在内存中,不然机器一挂,就等着被骂吧)。
丁、小结
写了那么多,我小结一下:
0)无论你怎么设计,你的系统一定要能容易地水平扩展。也就是说,你的整个数据流中,所有的环节都要能够水平扩展。这样,当你的系统有性能问题时,“加3倍的服务器”才不会被人讥笑。
1)上述的技术不是一朝一夕能搞定的,没有长期的积累,基本无望。
2)集中式的卖票很难搞定,使用上述的技术可以让订票系统能有几佰倍的性能提升。而在各个省市建分站,分开卖票,是能让现有系统性能有质的提升的最好方法。
3)春运前夕抢票且票量供远小于求这种业务模式是相当变态的,让几千万甚至上亿的人在某个早晨的8点钟同时登录同时抢票的这种业务模式是变态中的变态。业务形态的变态决定了无论他们怎么办干一定会被骂。
4)为了那么一两个星期而搞那么大的系统,而其它时间都在闲着,也就是铁路才干得出来这样的事了。
戊、附录:
一、云风方案:
其实铁路订票系统面临的技术难点无非就是春运期间可能发生的海量并发业务请求。这个加上一个排队系统就可以轻易解决的。
本来我在 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
连接保持在那里,就莫怪系统支持不住了。
另外,用户看到了不断在减少的队列前面的人数,他们也会安心等待。只要网站页面刷新流畅(只处理队列信息很容易保证),用户体验会很好。
最后补充几句废话:因为铁路购票系统很多年前就实现了内部网络化,有成熟系统支撑,运作多年。这次做互联网版本,一定不能放弃原有系统新来一套。不然实体购票点也在网页上刷不出票就崩溃了。
所以要做的仅仅是怎么做一个系统和原有系统对接。这样风险最小。两套系统可以分别优化处理能力。基于这个设计起点,所以我才不看好所有企图取代原有系统的方案。
再补充:
1、、排队前把身份证填好,系统编码到 ticket 里去。
排到了这个 ticket 只可以用这个身份证买一张或几张同车次的票。
注册环节完全可以免了。如果需要注册,填个密码就 OK。用户名可以暂时用身份证,买完票可以换,或者去别的服务器上换,不干扰购票流程。
你 8 点出票可以 7 点开始排。系统反正认定你了没人插队。现实就如此。不要去想比同样跟你一样需要票的人更有优先权。
======================================================================
二、其他网友看法:
1、唉,一个网站在大数据量访问是挂掉,是很正常的现象,有什么好炒作的!而且数据量不是几万,而是几亿!!!挂掉不丢人!!!!!
2、订票其实还有一个特点:各车次的分管铁路局是固定的!如果按铁路局将系统分割,负载将大大分散。就如10086.cn一样,他会要求你先输入手机号后跳转到各个分公司的系统。——但这样异地购票或者使用异地手机的就麻烦了!
3、“网游和扣扣在线或是登录时访问的更多的是用户自己的数据”,非游戏从业者,不过感觉游戏里某些公共事件都是在读写公共资源,有时候规模也不小呢;不过副本技术确实极大的缩小了这个规模。
4、文中提到可以使用缓存,比如现在12306是10分钟更新一次,缓存每10分钟都会失效,刷新缓存带来的峰值要怎么处理?
5、用排队+配额申请制,
一句话“更新”(update)转换成“插入”(insert)操作, 非要说像的话, 这像个广告系统
6、放票模式不做更改,运力不提升,单靠优化网站还是被骂。
假设铁道部有能力优化,那么可以预见,在很短的时间内,票就被卖出去了,没有买到票的人就会骂铁道部,票都被黄牛买了。
7、
我觉得需要注意一点火车票和电子商务的不同,车次是有限的,不像淘宝这样商品无限多,而且业务逻辑简单得多。这
样可以很容易的按照车次分区,每个车次是单独的队列,甚至完全可以放在内存,不同车次间不存在锁的问题,即使同车次也可以优化锁粒度。另外,查询结果没必
要可刻意要求和实际票务情况同步一致,可以误差一秒钟,查车次和查余票分开,这样90%的查询时可以缓存的。假设车票在开票10分钟内就被抢完,
根据每列车的座位数和每个座位被卖的次数,可以估算每个车次,每秒要处理几个请求,实际也没几个。
另外不同于电子商务的库存,每个座位可以被卖几次,用位运算实现很简单。
人们之所以用外挂刷票,因为网站烂,登陆难,下单难,付款难等等,如果订票过程流畅,外挂自然就没人用了,再加上简单可靠的防刷新和限流措施,应付现在的量没问题,铁道部的数据不可信,看他们说他们处理的是世界顶尖的难题就知道。
8、
技术方面不懂,不过我认为对于12306网站的库存管理,和普通B2C的电商库存管理,有很多不一样的地方
(1). 春运期间,运力不足,订单始终大于库存,但库存是固定的,即车票的最大限额。
(2).闲暇时间,订单小于库存,但车次固定,座位固定,库存也是固定的,即便是一个乘客,也会开一班列车
铁道部几乎不用考虑订单和库存之间的供求关系,普通电商过于看重库存管理,是因为害怕库存积压过多,直接影响到企业资金链的运行,而这个问题对于铁道部根本不存在,铁道部只要考虑怎么把固定的车票数量快速,流畅,均匀的分发到每位乘客手里就可以了。
而且我感觉,这么多年应付春运的经验,铁道部对于减轻售票压力的方案也会很成熟的,不过肯定都没有用在这个网站上面上。
9、因为无票了,用户不甘心,反复刷——访问压力大10倍
因为网页慢、死,所以用户不停的刷——访问压力大100倍
这是恶性循环:越是慢,访问量越大
如果,网页流畅了,哪怕还是无票,就不存在这99倍的刷新了,而只是10倍的刷新
这是良性循环:越是快,访问量反而少了
10、铁老大做了一件费力不讨好的事情,这事情直接拿30%的票给淘宝,30%给腾讯,自己留剩下卖,这一天几千万的点击量是个巨大的广告收入啊,自己不花一个子,冲别人大口要钱,这样自己花了几千万做了个被万人马的东西,不如自己不花钱,轻松拿钱,不行还骂别人。
直接找阿里巴巴或腾讯这样的战略伙伴,垂拱而治,何乐而不为呢?
11、带宽、核芯、存储都不是紧缺资源,一切的根源在于票太少人太多。一到春运运量能翻好几倍,运力只能提升一倍还不到。更好的系统是必须的,但是买不到票还是挨骂。
一个可以考虑的办法是:限制300公里以内的短程票,分流到公路客运去,这样可以缓解一点点压力。
最好能依据统计分析来确定:最小的瓶颈在哪。
12、查询和下单等操作用到的数据,应该是从铁道部客票系统已有的数据接口来直接获取数据。12306.cn和客票系统共享以前的老数据库。老旧的系统,必然适应不了现在的春运的变态需求。
13、票量=票量-1的时候——那么是不是隐含这个意思:针对某个车次有一条数据库记录用于登记剩余的车票,导致订购某车次的所有订单都要等待锁定这条记录并且更新?如果是这么设计的,
确实有点那啥。一般来说一个车次的票量有限,完全可以用一个内存中的test-and-set(reduce)的操作来进行控制,热门车次的票量很快就会降低到零,后面的订单都test不过去,可以直接拒绝了。
(1)数据库锁不可怕,大量订单都要锁定同一资源才可怕
(2)用数据库的锁来做票量更新和统计,带上事务就要慢好几拍;如果这个票量更新要和每笔订单的事务合并提交,那就更要慢上N倍。
————但是,车次那么多,这样的内存量也会非常惊人!!!!
14、并发下的数据一致性。放在内存里也一样要上锁做互斥。
15、我觉得很多人没用过这个网站就来发表意见,这个网站虽然反复的提示用户太多稍后再试,可是绝对没有说挂掉或者慢的情况。只要登陆进去,订单成功提交,后续
过程基本没有页面慢死的情况。估计他是直接从请求中抽取一部分人登陆来控制在线用户数量,然后再提交的订单中抽出一部分进入付款来控制售票速度。————也就是说,本身已经使用了限流阀的方法了。
16、这样一个系统如何实现相对公平性,特别是这次对于电脑操作不会或较弱的人就不公平。
比如如果系统采用了排队系统后,不从技术角度考虑, 我这里还有几个问题想不通。
(1). 什么时候开始排队,因为大家知道票是上午十点或下午三点出票。
这个队伍是一直存在,从系统运行时起。还是每天重新排。
如果每天重新排,在重排的那一刻,还在系统里的人如何理?我想这里大部分人是踢出来,不然重排就没意义了。
如果队伍一直存在,进入的人是可以永远在里面,还是有策略的踢出来。这里 永远在里面应该不可能。
如果有策略的踢出来,比如过一小时踢出来,那如果我要买的票在三点出票,我会想我要在两点多一点进入系统,
而买票的人应该都是这种想法,那我应该什么时候开始排队,才能保证我能在两点多一点进入系统,天哪,感觉这个无解,绝望!
(2). 假如队伍会在每天清空并在每天三点开始排队,但在排队的那一刻起,领到的号靠前的条件是什么?
应该是手速和网速。如果这样的话,我们可以看看排在队伍前面的是些什么人:
程序刷的, 游戏高手, IT重业人员, 都市白领, 普通受过教育接触计算机的人 文化低(对应操作计算机的能力)的外来务工人员。
程序刷可以禁,但效果如何就不清楚了。
如果是这样的话,与现有系统比,不同之处就在于
现有系统在人们试了无数次后,给人绝望
而排队后,一开始就给人绝望,你的号码是1000000,在你的前面还有999999人等待处理。
对于一个具体的会操作电脑的普通人而言:
现有系统:试了无数次,网页崩溃无数次,拼尽了耐心,终于买到了一张票。
排队系统: 放弃吧,你在这买不到票
如果开始排队算作比赛的话,那就是职业运动员和业余运动员的
当然,我绝对不是在说现有的系统好!我只是在说我们所有的技术都作好了,还是不能从业务角度实现它的合理公平性。
没事,瞎想的!
17、所以我觉得正确的方法其实是抽奖,在所有请求中抽奖,中奖的登陆入系统,在所有订单请求中抽奖,中奖的进入支付。这样比排队系统公平很多,每个订单都有中奖的机会。
18、云风的方案可靠就表现在把现实中的购火车票排队系统通过网络来实现, 效率肯定要比现实排队高多了.
先排到队, 然后再查询, 再买票…整体火车购票系统的需求不就是这样吗?
另外, 负载小, 压力小, 实现简单靠谱.
排到队的, 每个队列的后台用人工一对一服务都处理的过来…
19、我认为铁道部的一定都知道上文的所有东西,因为我这样一般的人就知道了,也都会放进去考虑,我的系统正好是跨旧系统查询与追求效能,与铁道部需求很像,当然流量差很多。
我想主要的问题应该是当初估计尖峰流量是一亿,最后来了十亿,第二是后端系统承受不住,或是交换资料的部份承受不住,因为后端系统是内网旧系统,新旧系统
沟通还要兼顾效能会有极大挑战,就算一般采用异步MQ等方式,但是量那么大可能也是吃不完,总不能下单后十小时才给结果。
不过这篇文章很值得想开发大系统的人去思考一下,我想还是要经过这些问题的洗礼后自然就会了,我就是这样系统搞挂几次, PM*几个就会了,反正怎么换也不会换掉技术人员。铁道部的人下次出来应该也都很强了,没有经过这些问题真的死过的,也没人会要你去设计这类的系统。
那么多人用hibernate的甚至EJB,这样的系统这类O/RM的我的经验是挡不住的,还是直接弄memcache/Coherence, 資料庫用 partition table等比较稳
20、我看到的几点如下:
第一,从访问结果来看12306.cn已经使用了CDN网络,提供商名字忘记了,可以查出来
第二,从访问动态页面的结果看,12306.cn用的应该是ngnix+Jboss
第三,12306.cn和奥运订票系统一样由太极集团制作
第四,从访问结果看CDN基本上没出什么问题,每次500都是由Jboss返回的。个人理解,是jboss挂了
21、现在最大的瓶颈是 提交订单非常慢,非常困难,其次是支付接口经常出问题;登录和查询不是那么紧。
提交订单瓶颈的原因我估计是,铁路内部采用了网络支付限制,大约是1万张(忘记从哪里看到的),但是可能同时就是n多人同时抢票,这样,客观造成了一个排队效应;排在后面的人还要等前面的人去支付,再去更新数据库,把事务加到里面了,那是相当慢。
解决问题的首要一步是确定这个系统达到什么效果,铁道部定时发票的节奏是不可能改变的,那套数据库也是不可能改变的,支付银行你也是没办法去改变的。僧多
粥少,刷票也是不可避免的,你也不可能做到人人上去就能买到票。这个系统建设的目的就是,用户能快速登录,查询,如果有票能够很快买到,或者很快被告知不
被买到,而且能够避免黄牛。那么,这个系统就是成功的。
反过来看看,现在的网站呢?用户无法感知是否成功,明明看了有票就是提交不上,提交上了半天不响应,然后说不让你买了,好不容易买了交了钱了,支付那边又超时了。
所以说,我对12306的想法是:
1.找个好的产品经理,像用户看到票有然后就是提交不上去,一点其他可供感知的东西都没有,这种设计水平我就不说了;
2.优化网络流程,例如可以把网络支付做到网站里,或者完全是异步支付;网络放票,可以做成按片不定时放,上午放上海,下午放杭州,随机放票,避免造成过多的并发;
3.再就是做好负载均衡,共享数据信息之类。其实我觉得查询之类的可以用阿里云之类的来搞定啊,春运期间,搞定核心数据流程就OK了。
22、这东西,你让百度、腾讯、阿里巴巴的团队去做都不好使。现在那个订票的web站点固然有很多问题。但是他们的开发人员肯定接触不到铁道部的票务核心网,铁道部最多给他们几个调用接口,至于接口的响应速度等web站点的架构根本无法涉及。
这非常类似淘宝在双11大促销的时候,淘宝的技术非常好,浏览、查询、交易、订单,从购物到支付宝到阿里旺旺,没有任何问题,但是网银页面挂了,因为各大银行的接口跟不上。
现在就算做一个非常牛逼允许十三亿人同时并发的web站点,铁道部的票务核心系统跟不上,你这个web做再好,也只是个架子。最多就是报错提示能更人性化一点。
想要让订票变的顺畅,只有重构铁道部的票务核心系统,那估计就是一个超大的大工程。
——————现在显然是web没做好,后一步卡不卡就不知道了,因为瓶颈在web了
23、大家都开始把系统的优化改造方案向着真实世界的购票大厅排队体系思考了,这很好,但真实世界里一定就会带来新的人为因素,例如银行的排队,就区分
个人用户、企业用户、VIP用户,高级会员啥的,
只要有人敢把12306改成排队的购票系统,就一定会弄出来不同等级的用户权限,到时候排队的优先级、插队等真实世界的需求就都来了,那时候看大家还
说哪个公平来着。
24、排队算法的原型是火车站售票厅的排队买票。
而网上购票的排队,有两种方案选择,一是用户先选好从哪里到哪里的票,再排队。二是先排队,排到你了再选要从哪到哪里。
第一种,你可以想象下这种情景。售票厅为了加快速度,要求所有人排队前先领个小票,上面写着从哪到哪,如果排到你的时候没你写的票了,就不能买
了,你要回去重新领个小票重选车次和首末站,然后必然得回到队尾重排,不回到队尾的话否则你选站会妨碍后面排队的人,导致领小票再排队失去意义。
那买票的人能不*吗?他选票时有票的,排到他时没了,得一次次重选重排,还不如现在售票厅的卖法有效率呢!
第二种,先排,排到再选票,好了,售票厅有熟练的售票员引导你,你两三分钟能买好,而网站,你给多少时间选票呢?一个旅客要先搜出想要的票的即时信息,可能没有,再搜下一趟,再搜转车方案。注意提前搜好是没多大意义的,因为票信息很快过时,只有排到你时搜出来的才有现实意义,只能排到再搜。那一个人也得给他三分钟以上来选好票下单,甚至因为很多人不熟悉操作,得给更长时间。
如果这队伍前进得比火车售票厅还慢,而网上排队的人又必然比售票厅还多,那这队你排一天都未必轮得到你,还不可能每个人都坐在电脑前死等,网站排队变成了笑柄。
25、我虽然也喷一次12306, 其是因为填了几个小时的验证码, 有点火,
但是心里还是佩服的, 至少如此高的页面访问量 , 访问人数量, 网站然后还流畅的运行, 已经很不错了, (即使是说用户过多, 那只是后面系统处理不过来的原因)
因为人太多, 拼命加硬件也可以解决,前面查询与后面支付问题, 但是只是每年春节才用一次, 不值当的, 所以我有一个小小的想法来解决这个问题,
我说的这个问题特指: 目前网站前台接待工作做的很好, 但是把你带到后堂, 就没有人招呼了, 就跟我大学的时候,
吃饭的时间大家都拿水瓶去打水, 结果茶房挤破头, 但是呢最终都有水打, 只是要耗太多的时间在那等, 我就想大家都茶壶放在那,
然后有一个人专门来打水 , 大家都估计(根据送来水壶的时间)水打好了, 就直接到拿走就行了, 节省时间
所以我想到的方法是: 采用预订机制, (归根结底是因为没有排除)
1. 先查询出我要订哪些票(票的价格都有), 此时直接可以支付(注册时已经认证过身份证信息), 支付好了以后, 大家就该干嘛干嘛,
2. 系统根据预订队列, 按照系统所能使出最大能力, 生成车票, 如果可能可以短信提醒或邮件提醒订票成功与否, (如果有客户选择的时间范围,自动滚动到下一批的队列)
3. 最终还是没有票, 执行退款动作
我想这样做的好处, 大家不用无谓的去查询多张, 再订啊订啊, 增加无谓的服务器和带宽负担, 这样就可以把前台接客的资源节省(甚至支付资源)一大部分, 并用与支付环节
26、很多事情并不是技术可以解决的。
今天是因为网上购票系统扛不住了导致买票困难,导致骂声一片,而如果真的是系统很牛叉可以抵挡的了这样的访问,那么新的问题又来了:在放票的一瞬间票就被秒杀光了。
结果会怎样?
全国人民再次抱怨买不到票,甚至怀疑购票系统。
并且这样会导致专门有人开发刷票软件。那样,再牛X的系统、再好的排队系统也无济于事的。。。因为网上到处充斥着刷票程序。这样,真正通过人工登录去购票的人还是买不了票。
我倒是想到了一个非技术的可行办法
简单的说就是把预售改成预约,任何人都可以实名制预约某个班次火车,然后截止预约时间后系统对旅客进行虚拟购票,第一批为分组1,虚拟售罄后再从1车厢1
号座位开始虚拟售票,为第2组,再售罄后就是第三组,以此类推。假如总共虚拟售出了5组,那么在通过一个公开的方式选出某一组为获得该车次的购票权(比如当天沪深指数之和对总组数的余数+1即为该组获得购买权),然后在规定的天数内付款买票即可。
这样,即不需要那么牛叉的并非系统,买票的人也不需要到3点去秒杀火车票了。
27、关于老系统的问题:
最简单的方式就是新系统分配到一定配额的车票,独立运行。其实这应该是最佳的方案,否则在正常情况下,网络的出票效率肯定是高于其他方式,如果不做配额,99%的票都会被网络拿走。
稍微复杂一点的就是加一个仲裁的服务器,每几分钟分别向老系统和新系统放票,新老系统也是独立运行。
最后,比较笨的方法新系统通过外挂的形式和老系统交互,系统串行化请求,加个流量控制之后向老系统请票,这样新系统出票速度不会很快,但至少不会让用户卡在用户交互界面上。
28、系统在内存里开一个大数组(32G 内存够排上亿人了吧),就是一循环队列。把这个 ticket id 放在队列尾。
用户现在拿着 ticket id 向网关发起请求。网关利用一次 hash 查询,在内存中的数组队列里查到它的位置,立刻返回给用户。用户的前端就可以看到,他在这个网关(售票大厅)前面还有多少人等着。
黄牛开始卖 tickid 了, 大量的刷,囤这个tickid, 到时候卖给别人就可以了。
这个设计不靠谱。
======================================================================
黑传说看法:
原文已经被我改造过,为方便理解,添加了注解(斜体字),用通俗的词汇替换了一些名词(一般会在括号里加注专用名词),并对一些说法做了通俗化的更改。
说到底三个问题:
1、火车本身的运力问题:这是根本的问题,其他都是辅助
2、网站硬件上可能需要继续投入,毕竟这是一个人口大国的高交互量、需要高稳健度的网站
3、业务模式可以稍作调整,这样不仅改善了用户体验,而且还节约了系统资源开销。比如排队,可以排到若干人再完成总交易,这样可以控制在自己的处理范围内。
转载 http://coolshell.cn/articles/6470.html