1 背景
每年的春节是中国人的传统节日,大多数中国人都会在这一天选择回家团聚。为了方便用户进行购票,2012 年春节,铁道部推出 12306 网站,进行网络实名购票。然而,历年春节假期,巨大的访问请求都让中国铁路客户服务中心网站(www.12306.cn)陷入“万劫不复”。根据新浪的调查,在 2013 年春节,有近 90%的网友表示 12306 网站缓慢、页面崩溃,严重影响正常购票。
世界级的人口迁徙带来了一个世界级的难题: 要如何通过网络,把火车票及时卖给有需要的人?
12306 网站所面临的问题分析:
铁道部在线车票发售网站 12306 基本不存在大量图片、视频这些占带宽资源的东西,所面临的主要问题就是数据库的高并发量——用中国的人口基数来算,这是一个极为恐怖的并发量,在车票发售的高峰时间点,向 12306 发起的并发请求数量大得就像一场国家规模的 DDOS 攻击。
中国铁路客户服务中心网站(www.12306.cn)是世界规模最大的实时交易系统之一,媲美 Amazon.com,节假日尤其是春节的访问高峰,网站压力巨大。据统计,在 2012 年初的春运高峰期间,每天有 2000 万人访问该网站,日点击量最高达到 14 亿。
所以 12306 所面临的难题本质上也是属于 高并发访问问题,类似与一些电商网站所搞的"秒杀"活动一样。通过对 12306 的深度优化,2015 年 12306网站顺利过关,没有“瘫痪”,是值得庆祝的。而我们本次主要就是来探究一下如何对 12306 网站做深度优化来抵御高并发访问。
2 高并发访问
那么 12306 做了什么样的优化,才解决了高并发访问呢?
12306 技术部主任单杏花在接受一次记者采访的时候有说到:我们研发了 分布式的内存计算的余票计算技术,让余票计算变得非常高效。与此同时单杏花及其团队还研发了 异步交易排队系统,这种系统采用 售取分离、读写分离的核心系统架构等多种技术,为 12306 售票系统提供技术支撑。
其实通过她的描述,我们可以得出一些处理高并发访问方式:
1、采用内存计算(使用缓存系统)
2、异步处理请求(进行流量消峰)
3、数据库进行读写分离操作
常见的分布式缓存系统:MongoDB , Redis,MemCache
2.1 流量消峰的方案
1、要对流量进行削峰,最容易想到的解决方案就是 用消息队列来缓冲瞬时流量,把同步的直接调用转换成异步的间接推送,中间通过一个队列在
一端承接瞬时的流量洪峰,在另一端平滑地将消息推送出去。在这里,消息队列就像“水库”一样,拦蓄上游的洪水,削减进入下游河道的洪峰
流量,从而达到减免洪水灾害的目的。
2.2 进行人为验证答题
你是否还记得,最早期的秒杀只是纯粹地刷新页面和点击购买按钮,它是后来才增加了答题功能的。那么,为什么要增加答题功能呢?这主要是为了增加购买的复杂度,从而达到两个目的。
第一个目的是防止部分买家使用秒杀器在参加秒杀时作弊。2011 年秒杀非常火的时候,秒杀器也比较猖獗,因而没有达到全民参与和营销的目的,所以系统增加了答题来限制秒杀器。增加答题后,下单的时间基本控制在 2s 后,秒杀器的下单比例也大大下降。答题页面如下图所示。
第二个目的其实就是延缓请求,起到对请求流量进行削峰的作用,从而让系统能够更好地支持瞬时的流量高峰。这个重要的功能就是把峰值的下单请求拉长,从以前的 1s 之内延长到 2s~10s。
这样一来,请求峰值基于时间分片了。这个时间的分片对服务端处理并发非常重要,会大大减轻压力。而且,由于请求具有先后顺序,靠后的请求到来时自然也就没有库存了,因此根本到不了最后的下单步骤,所以真正的并发写就非常有限了。
2.3 分时间段进行产品上架处理
其实处理高并发访问还有两种常见手段: 静态化、集群
静态化: 分布式缓存是为了解决数据库服务器和 Web 服务器之间的瓶颈,如果一个网站流量很大这个瓶颈将会非常明显,每次数据库查询耗费的时间将不容乐观。对于更新速度不是很快的站点,可以采用静态化来避免过多的数据查询,可使用 Freemaker 或 Velocity 来实现页面静态化。
集群: 使用多台服务器去处理并发请求
3 系统架构介绍
基于以上几点高并发的处理方案,我们本次所设计的 12306 后端系统架构如下所示。
3.1 数据同步架构
系统管理员通过后台管理系统基于一些基础数据(座位数据,列车车次数据,乘车计划数据)生成指定日期的乘车计划数据。然后我们通过 logstash
将生成的数据同步到 ES 和 Redis 中。
Logstash 常见的数据获取方式 拉,推。上述架构给大家展示的是拉的模式,但是这种方式我们当前这个系统环境中不太适合,原因是因为我们使用了 MyCat 进行分库分表的处理,而 Logstash 在进行拉取数据的时候如果数据量较大我们就需要进行分页拉取,那么此时 Logstash 就会生成类
似这样的一条 sql 语句:select count(*) as count from …来查询满足条件总条数,但是这个 count 别名使用了反引号,而这个 反引号在 MyCat 中无法使用,因此就会产生异常。
因此本次我们在进行数据同步的时候使用的是 Logstash 的推模式进行数据同步,如下所示:
3.2 数据搜索架构
数据同步完毕以后,用户就可以搜索相关的乘车计划数据了。具体的搜索架构如下所示:
3.3 用户下单架构
通常订票系统要处理 生成订单、减扣库存、用户支付这三个基本的阶段,我们系统要做的事情是要保证火车票订单不超卖、不少卖,每张售卖的车票都必须支付才有效,还要保证系统承受极高的并发。
这三个阶段的先后顺序改怎么分配才更加合理呢?
3.3.1 方案一:先扣库存在支付
当用户并发请求到达服务端时,首先创建订单,然后扣除库存,等待用户支付。这种顺序是我们一般人首先会想到的解决方案,这种情况下也能保证订单不会超卖,因为创建订单之后就会减库存,这是一个原子操作。
存在的问题:
1、就是在极限并发情况下,任何一个内存操作的细节都至关影响性能,尤其像创建订单这种逻辑,一般都需要存储到磁盘数据库的,对数据库的压力是可想而知的;
2、存在如果用户存在恶意下单的情况,只下单不支付这样库存就会变少,会少卖很多订单。
3.3.2 方案二:支付完减库存
如果等待用户支付了订单在减库存,第一感觉就是不会少卖。但是这是并发架构的大忌,因为在极限并发情况下,用户可能会创建很多订单,当库存减为零的时候很多用户发现抢到的订单支付不了了,这也就是所谓的" 超卖", 并且这种方案也不能避免并发操作数据库磁盘 IO。
3.3.3 方案三:引入消息队列
从上边两种方案的考虑,我们可以得出结论:数据库只要创建订单,就要频繁操作数据库 IO。那么有没有一种不需要直接操作数据库 IO 的方案呢,这就是预扣库存。先扣除了库存,保证不超卖,然后异步生成用户订单,这样响应给用户的速度就会快很多;那么怎么保证不少卖呢?用户拿到了订单,不支付怎么办?我们都知道现在订单都有有效期,比如说用户五分钟内不支付,订单就失效了,订单一旦失效,就会加入新的库存,这也是现在很多网上零售企业保证商品不少卖采用的方案。订单的生成是异步的,一般都会放到 MQ(消费队列)中处理,订单量比较少的情况下,生成订单非常快,用户几乎不用排队。如下图所示:
这种方案也就是单杏花主任所提出的异步交易排队系统。当然 12306 网站的还有一个改造的关键技术 建立可伸缩扩展的云应用平台 台。根据互联网上的新闻,中国铁道科学研究院电子计算技术研究所副所长,12306 网站技术负责人朱建生说,为了应对 2015 年春运售票高峰,该网站采取 5 项措施:
一、利用外部云计算资源分担系统查询业务,可根据高峰期业务量的增长按需及时扩充。
二、对系统的互联网接入带宽进行扩容,并可根据流量情况快速调整,保证高峰时段旅客顺畅访问网站。
三、防范恶意抢票,通过技术手段屏蔽抢票软件产生的恶意流量,保证网站健康运行,维护互联网售票秩序。
四、制定了多套应急预案,以应对突发情况。
4 下单流程
MQ异步生成订单数据到mysql,redis是主要控制库存的方式,如果订单失效就会退回到redis库存