伴随着十一长假的来临,大家对于铁道部12306的讨论又多了起来。这篇文章(原文)从12306网站延展到网站性能的诸多讨论,对于创业者与技术爱好者有很强的借鉴意义。本文作者陈皓(weibo)有14年软件开发相关工作经验,8年以上项目和团队管理经验。

  12306.cn网站挂了,被人民骂了。我这两天也在思考这个事,我想以这个事来粗略地和大家讨论一下网站性能的问题。因为仓促,而且完全基于本人有限的经验和了解。只讨论性能问题,不讨论那些UI,用户体验,或是是否把支付和购票下单环节分开的功能性的东西。

  业务

  任何技术都离不开业务需求,所以,要说明性能问题,首先还是想先说说业务问题。

  其一,有人可能把这个东西和QQ或是网游相比。但我觉得这两者是不一样的,网游和QQ在线或是登录时访问的更多的是用户自己的数据,而订票系统访问的是中心的票量数据,这是不一样的。不要觉得网游或是QQ能行你以为这是一样的。网游和QQ 的后端负载相对于电子商务的系统还是简单。

  其二,有人说春节期间订火车的这个事好像网站的活动。的确很相似,但是如果你的思考不在表面的话,你会发现这也有些不一样。火车票这个事,还有很多查询操作,查时间,查座位,查铺位,一个车次不 行,又查另一个车次,其伴随着大量的查询操作,下单的时候需要对数据库操作。而,直接杀好了。另外,关于,完全可以做成只接受前N个用户的请求(完全不操作后端的任何数据, 仅仅只是对用户的下单操作log),这种业务,只要把各个服务器的时间精确同步了可以了,无需在当时操作任何数据库。可以订单数够后,停止,然后批量写数据库。火车票这个岂止是那么简单。能不能买到票得当时告诉用户啊。

  其三,有人拿这个系统和奥运会的票务系统比较。我觉得还是不一样。虽然奥运会的票务系统当年也一上线废了。但是奥运会用的是抽奖的方式,也是说不存在先来先得的抢的方式,而且,是事后抽奖,事前只需要收信息,事前不需要保证数据一致性,没有锁,很容易水平扩展。

  其四,订票系统应该和电子商务的订单系统很相似,都是需要对库存进行:1)占住库存,2)支付(可选),3)扣除库存的操作。这个是需要有一致性的检查的,也是在并发时需要对数据加锁的。B2C的电商基本上都会把这个事干成异步的,也是说,你下的订单并不是马上处理的,而是延时处理的,只有成功处理了,系统才会给你一封确认邮件说是订单成功。我相信有很多朋友都收到认单不成功的邮件。这是说,数据一致性在并发下是一个瓶颈。

  其五,铁路的票务业务很变态,其采用的是突然放票,而有的票又远远不够大家分,所以,大家才会有抢票这种有中国特色的业务的做法。于是当票放出来的时候,会有几百万人甚至上千万人杀上去,查询,下单。几十分钟内,一个网站能接受几千万的访问量,这个是很恐怖的事情。据说12306的高峰访问是10亿PV,集中在早8点到10点,每秒PV在高峰时上千万。

  多说几句:

  库存是B2C的恶梦,库存管理相当的复杂。不信,你可以问问所有传统和电务零售业的企业,看看他们管理库存是多么难的一件事。不然,不会有那么多人在问凡客的库存问题了。(你还可以看看《乔布斯传》,你知道为什么Tim会接任Apple的CEO了,因为他搞定了苹果的库存问题)

  对于一个网站来说,浏览网页的高负载很容易搞定,查询的负载有一定的难度去处理,不过还是可以通过缓存查询结果来搞定,难的是下单的负载。因为要访问库存啊,对于下单,基本上是用异步来搞定的。去年双11节,淘宝的每小时的订单数大约在60万左右,京东也才能支持40万(居然比12306还差),亚马逊5年前一小时可支持70万订单量。可见,下订单的操作并没有我们相像的那么性能高。

  淘宝要比B2C的网站要简单得多,因为没有仓库,所以,不存在像B2C这样有N个仓库对同一商品库存更新和查询的操作。下单的时候,B2C的 网站要去找一个仓库,又要离用户近,又要有库存,这需要很多计算。试想,你在北京买了一本书,北京的仓库没货了,要从周边的仓库调,那要去看看沈阳或 是西安的仓库有没有货,如果没有,又得看看江苏的仓库,等等。淘宝的没有那么多事了,每个商户有自己的库存,库存分到商户头上了,反而有利于性能。

  数据一致性才是真正的性能瓶颈。有 人说nginx可以搞定每秒10万的静态请求,我不怀疑。但这只是静态请求,理论值,只要带宽、I/O够强,服务器计算能力够,并支持的并发连接数顶得住 10万TCP链接的建立 的话,那没有问题。但在数据一致性面前,这10万完完全全成了一个可望不可及的理论值了。

  我说那么多,我只是想从业务上告诉大家,我们需要从业务上真正了解春运铁路订票这样业务的变态之处。

  前端性能优化技术

  要解决性能的问题,有很多种常用的方法,我在下面列举一下,我相信12306这个网站使用下面的这些技术会让其性能有质的飞跃。

  一、前端负载均衡

  通过DNS的负载均衡器(一般在路由器上根据路由的负载重定向)可以把用户的访问均匀地分散在多个Web服务器上。这样可以减少Web服务器的请求负载。因为http的请求都是短作业,所以,可以通过很简单的负载均衡器来完成这一功能。好是有CDN网络让用户连接与其近的服务器(CDN通常伴随着分布式存储)。(关于负载均衡更为详细的说明见“后端的负载均衡”)

  二、减少前端链接数

  我看了一下12306.cn,打开主页需要建60多个HTTP连接,车票预订页面则有70多个HTTP请求,现在的浏览器都是并发请求的。所以,只要有100万个用户,会有6000万个链接,太多了。一个登录查询页面好了。把js打成一个文件,把css也打成一个文件,把图标也打成一个文件,用css分块展示。把链接数减到低。

  三、减少网页大小增加带宽

  这个世界不是哪个公司都敢做图片服务的,因为图片太耗带宽了。现在宽带时代很难有人能体会到当拨号时代做个图页都不敢用图片的情形(现在在手机端浏览也是这个情形)。我查看了一下12306 首页的需要下载的总文件大小大约在900KB左右,如果你访问过了,浏览器会帮你缓存很多,只需下载10K左右的文件。