摘要:基本上查询都是直线式的,所以key-value就是很好的工具;因为出票可能需要找一下车次,座位,只能一一对应的查询就不好用;弄个redis带个列表结构(dict or zset ,哪个结构更合适?
关于12306网站和清华某院长的微博言论,我做了一个回复,说这玩意不难,2个人2周,40台服务器可以搞定。
下面详细解释一下大概的思路。免费share一下,看看靠谱不靠谱。
别人看到的是流量,我先看结构,这里的数据结构是相当简单的,主要满足的需求是
1.车次查询(最常见的是起点站,终点站查询 和车次直接输入查询)+余票显示
所谓的用户刷页面,绝大部分应该在这里。日均10亿pv(这个数字我先质疑一下,不过么关系,后面再说怎么处理),估计主要落在这个查询上。
2.注册,登陆。每天过千万人次是有的
3.下单,也就是日成交订单量,可能存在下单失败,约几百万次。
这里基本不涉及复杂的关系操作,不涉及推拉结构,和新浪微博,facebook这样的应用场景相比,在数据关系上简直毫无难度,这也才是我敢说大话的原因。
因为不涉及复杂的关系操作,不涉及个性展示(不同用户搜索同样的条件,结果一致),那么缓存化就是最佳途径。
1.存储key-value化, 推荐redis
基 本上查询都是直线式的,所以key-value就是很好的工具;因为出票可能需要找一下车次,座位,只能一一对应的查询就不好用;弄个redis带个列表 结构(dict or zset ,哪个结构更合适?问问新浪架构师杨卫华吧,这事估计对他太简单了)进去就可以了。春节放票总共多少张?又不是一次放出来,每张票对应一个key,一个 value,能吃多少内存?后面跟个数据库做同步,这点数据量对于现在的服务器来说根本不是问题。
注册登陆也可以在 mysql基础上弄个redis挂在前头响应,这种查询速度,biu.
根据不同车次分几台服务器,响应速度根本不是问题。
2.将所有查询结果缓存化,静态化
首先明确一下查询的步骤,实际上主要查询分两步
第一步是查询符合要求的车次,第二步是查询余票。
缓存也就分两步做,起始地,目标地查询 - 常见查询目标(如北京到成都)全部预制缓存。非常见查询目标,基于第一次查询的结果缓存,这样查询车次基本上无压力。
查 询有票状态就更简单了,因为票数只有有票,无票两个状态,某日某车次作为一个key-value类型存储(仍用redis即可)。某类车票发生从有到无或 从无到有的变化,才通知缓存更新。更新是后台通知的,而非基于用户查询。比如某车次硬卧票售完,通知一次更新,硬座售完,通知一次更新,软座售完,通知一 次更新。以此类推,这样的缓存更新次数极少。而且可以给前端返回甚至静态结果(基于查询条件生成静态结果,是个Seoer都会的,后台在票数变化时通知更 新,这样结构上就与前端查询无关了,而且一样可以保持实时性)。
如果你较真说,其实一个车次在不同区间也存在有无票的不同,的确,不过按照同样思路,结构多做一层死不了人的。毕竟这只是概述。但是核心思路不变,缓存的变更次数远少于查询请求次数,这就够了。
3.前端缓存处理
很 多人被10亿请求数吓到了,其实这里水分很大,最多的是重复刷新和外挂工具,那么如果你做到基于2的查询结果缓存化,这一步就简单了;直接参见这个文章 http://blog.sina.com.cn/s/blog_466c66400100bi2y.html 大量的用户重复刷新根本不是问题。 想知道实际效果,看这里 http://blog.sina.com.cn/s/blog_466c66400100cfrj.html 1小时20亿的刷新都不怕,还怕你一天10亿刷新?
4.i/o优化
其实我甚至觉得用了 redis都不需要做i/o优化了,如果用户单据需要数据库保存,一天200万单嘛,搜一下 淘宝技术专家余锋分享的qcon讲座文档,顺便读一下他历来新浪微博分享的文字,这个需求简直就是小儿科了。 大不了狠狠心买几块ssd硬盘做raid1/0,对于我这样的穷架构师来说,都属于大手笔了,至于昂贵的fusion-io,我真觉得,这个场景用不着, 实在用不着。
这里关键点,是查询结果的静态化和前段缓存的利用
查询怎么可能静态化?
因为
1:重复查询的频度远远大于数据更新的频度(即便是票数的更新,也是500:1,更不用说是有无的变化)
2:静态化不代表不动态更新,在订票成功后,如果发生了票数状态的改变(是状态改变,而不是数字改变),服务端更新或删除该静态结果(下一次查询重新生成静态结果)
至于为什么说2人2周,别搞花的,别图好看,就把这些结构捋清楚,代码能有多少行?这玩意没什么工作量。
此外,有人说,你肯定没考虑神马神马神马神马;您说对了,我还真没考虑这么多,毕竟铁道部没给我1000多万,不过真要是给了我1000多万,我用三天时间考虑清楚,肯定比这不到1个小时整理的东西详细,您觉得呢,剩下一周半干活足够完工了。
------------------------------------------------------------------------
做个简要总结,该方案所适应场景
1:查询请求频次远大于数据更新频次。
2:所有人同一时刻查询同一条件返回结果一致。
在二者条件满足的情况下,查询结果可以静态化,静态化不代表不动态更新
更新通过服务端的数据变化触发,而非通过用户请求触发。这样就可以保证静态化发布和动态化更新。
静态化发布后,利用杨建的 前端优化技巧,设计输出header。
根据公开数据粗略估计,10亿pv请求,90%+甚至95%会落到前端缓存里,根本不会带来服务器负载!连cdn都省下了!
明白嘛?不明白的仔细去看杨建的博客。
至于订单系统,一天200万,数据库随便分一下库,还需要多少解释?看看余锋的微博和Qcon分享文档,200万请求算毛事情,不至于唧唧歪歪吧。
铁路订票系统的简单设计
摘要:大量的网络带宽,计算力浪费在了“维持次序”上。系统不稳定时,大量的只做了一半的无效的购票流程浪费掉了这些。要响应高并发的 HTTP 请求,关键就在于迅速反应,不要什么都想着从数据库绕一圈。排队的队伍维持就完全不需要使用数据库。
其实铁路订票系统面临的技术难点无非就是春运期间可能发生的海量并发业务请求。这个加上一个排队系统就可以轻易解决的。
本来我在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 连接保持在那里,就莫怪系统支持不住了。
另外,用户看到了不断在减少的队列前面的人数,他们也会安心等待。只要网站页面刷新流畅(只处理队列信息很容易保证),用户体验会很好。
最后补充几句废话:因为铁路购票系统很多年前就实现了内部网络化,有成熟系统支撑,运作多年。这次做互联网版本,一定不能放弃原有系统新来一套。不然实体购票点也在网页上刷不出票就崩溃了。
所以要做的仅仅是怎么做一个系统和原有系统对接。这样风险最小。两套系统可以分别优化处理能力。基于这个设计起点,所以我才不看好所有企图取代原有系统的方案。
转载于:https://blog.51cto.com/tianshili/1640097