12306 已经成为每年春节绕不开的热点。在猴年的春运之际,InfoQ 再次重拾这个话题,与各位一起探索这个影响亿万人的公共服务。正如小道君所说的那样,今年朋友圈里、微博上抱怨12306 的少了。不得不说,这是一个很大的进步,唯有进步值得颂扬。希望明年我们不必再跟踪这个热点。
吐槽奇葩验证码
相信无数人已经见识过今年 12306 各种神奇的验证码了,吐槽归吐槽,我们来看看验证码到底是怎么回事?验证码的学名叫做 CAPTCHA,即“图灵测试”(看过电影《机械姬》的同学因该对此并不陌生)。验证码通常是由计算机生成一个对人类而言很容易而对电脑而言非常困难的问题,能回答者被判定为人,但验证码测试其实不是标准的图灵测试。验证码的目的是阻止人类恶意利用计算机做受限的事情。早期的验证码大多用扭曲的文字来实现,用以避开 OCR 的机器自动识别。后来,验证码的复杂有时让真正的人类也难以识别。毫无疑问,12306 对验证码的使用已发挥到了极致。
图像识别方法可以分为两大类,模型的方法和搜索的方法。模型的方法是在业界研究和使用最多的方法,基于搜索的方法直到大数据时代才出现。2012 年前后,深度卷积神经网络在图像识别领域开始应用,此后,深度学习的方法击败了所有传统的方法,使得图像识别的准确率向前迈了很大一步。最新的研究结果使得识别率达到了 96.3%。阿里巴巴研究员 / 资深总监华先胜先生表示,只要验证码系统不断快速地增加和迭代系统的数据,验证码的破解基本上是不可能的任务。
折翼的前端
对于广大购票者来说,刷票软件并不陌生。浏览器厂商、黄牛党的刷票技能与 12306 道高一尺魔高一丈的对决,一度成为媒体争相报道的对象。我联系了著名刷票软件开发者木鱼并与之谈及了相关的话题,他源于个人使用而开发的刷票插件,曾一度导致 GitHub 遭受了 DDoS 攻击。
- 导致对 GitHub 那次 DDoS 攻击的主要原因如下:
- Chrome 在某次更新后增加了同源限制策略(大概是在 2012 年),HTTPS 无法加载 HTTP 的资源。
- 木鱼没有 HTTPS 服务器,也没有相关证书。
- 订票助手需要自动更新检查,这个是刚需。
- 并没有找到比较简便的做法能绕过同源限制。
- 那时候并不清楚 GitHub 内部的区别和限制。
- GitHub 在流量略高的时候会返回错误(403)。
- 经过调整的检测策略并没有对重试操作增加延迟和次数限制(虽然默认间隔并不短,但始终的高频率 403 导致请求越攒越多)。
- 后期改用新的检测方式不依赖于 Github 和 HTTPS(iframe 代理模式),但之前的客户端因为 GitHub 不返回正确信息导致无法更新,也就无法降低请求量。
- 综上所述,在 GitHub 越来越限制的时候,其承受的流量是逐渐增加的。只要能正确返回数据,流量是会直线降低的。可以说情况也始料未及,对 GitHub 认识不足导致其躺枪,实非木鱼本人所愿。后来的解决方案分了好几个阶段。
代码托管:
随后木鱼回忆了代码托管的几个阶段。
- SAE 托管:尝试将检测更新的信息文件放在 SAE 上。然而 SAE 是个不靠谱的家伙,在 2013 年 12306 闹事之后强制直接关闭并删除了木鱼的托管(发信息说是因为公安部的文件要求)。不过这些影响并不大,因为此时 SAE 上的信息文件只是过渡,最新版的更新代码已经改为 iframe 内嵌框架代理了。所以 SAE 的流量在短暂的高峰后,迅速下降。
- iframe 代理模式:虽然 Chrome 有 HTTPS 同源限制策略,但并不影响内嵌的 iframe 来自于 HTTP。所以后期(也就是临近 2013 年春节的时候)的更新已经全部更新为 iframe 模式。换句话说,就是内嵌一个隐藏的 iframe,由它去加载检测更新的页面,然后通过参数传递版本信息并判断版本。这个操作很灵活,但有个限制,就是提示必须在 iframe 里,对外层的窗口由于跨域也无法操作,所以提示方法很有限,并且无法保证正确检测更新。
- 扩展检测更新模式:之前的订票助手一直使用 UserScript 模式,可以兼容 Firefox、Chrome 等。但由于 2013 年春节 12306 做的手脚比较多,纯粹的 UserScript 能做的事情很少但限制很大,所以开始放弃 UserScript 模式,转而全面使用 Chrome 扩展模式。此时,更新代码完全可以放到扩展的后台页面中,通过前台页面触发更新。
刷票软件技术演进:
木鱼强调:验证码识别绝对不是技术进步,而是技术的滥用。因为购票需要秩序,你可以在守秩序的范围内为用户出谋划策,教他购票为他免去繁琐无用的环节,但你不能公然无视购票秩序越过 12306 设置的种种规则。验证码识别就是其中一个典型的技术滥用,可以说是对 12306 信息安全的一种攻击。如此下三滥,估计也只有国内的这些公司加不明真相的群众才能做得出来了。而木鱼在开发刷票软件过程中主要经历了如下几个阶段:
- 最早的 UserScript 时代:其实最早这些东西是在 Firefox 上依托 GreaseMonkey/Scriptish 实现的,然后恰好 Chrome 也支持 UserScript,所以可以跨浏览器使用。彼时各大浏览器厂商和 IT 厂商并没有意识到这个市场,甚至连黄牛软件都没有,12306 除了卡慢也没有各种暗坑和意外状况,所以这是个很美好的时代。基本上只要你肯刷,刷到票都是你的,根本用不到自动识别验证码之流。所以很多早期的同学会怀念这个时代,觉得这个时代的插件很牛,看到票绝不失手,其实不是插件牛,而是那会儿的环境太纯洁了。
- Chrome 扩展时代:后期由于几大互联网公司的浏览器介入,市场的推波助澜,导致 12306 意识到问题的严重性,开始着手封堵。UserScript 工作于页面级别,能做的事情非常有限,针对 12306 的限制(比如 12306 曾经依据 User Agent 封堵访问、判断 Refer 来源等)几乎无事可做,所以后来渐渐放弃了 UserScript 模式,转而全面使用 Chrome 扩展模式。在 Chrome 扩展的后台页中,可以实现修改请求信息、长驻留任务等,可以做的事情比较多。这个模式一直发展到 2014 年春节,基本上达到了登峰造极的程度,功能和实用性最为丰富。但可惜的是,2014 年春节 12306 出来的新版,让这个工作模式开发难度陡增(因为新版的 12306 大量使用了闭包和第三方库封装,代码实在太臃肿了),并且 12306 在高峰时会因为自己的原因甚至直接卡死浏览器,导致用户体验极差(甚至会有不少人觉得这是插件的问题),所以在这之后,Chrome 扩展也渐渐取消了 UI 界面。
- 独立页面购票时代 V1:早在 2014 年春节的时候,某数字就搞出了独立页面订票。在这之前,其实我也是考虑过这种模式的,但是当时在独立页面和内嵌 12306 两种模式之间衡量的时候,觉得内嵌 12306 更为稳妥,然而事实证明我的观点是错误的,因为难度极大不说,12306 掉的链子会带上我一起掉链子,并且还光荣背锅。所以在 2014 年春节后,木鱼也走上了全面独立页面的道路。独立页面其实更容易实现,用户体验和功能都容易做得比较好。这个模式一直延续到现在。简单地说,就是通过独立的页面来实现对接 12306 接口,实现订票。
- 独立页面购票时代 V2:在实现独立页面购票后,由于是独立页面,所以可以做的事情是比较多的。此时我开始考虑做一些原来想做却没法做的事情,或有一些新的情况(如限售)是否可以通过技术手段降低些影响。在 2014 年中到 2015 年,我设计并实现的这些功能包括:
- 跨站推荐:如果一趟车因为长途因素限售,自动检测并监控临近的车站,尽可能提高发现车票的概率。
- 中转推荐:通过服务器后端算法找出可以中转的线路并提供相关信息。
- 辅助信息提示:如当前车站以及始发站起售时间提醒、购票难度提醒等等。
- 聊天室:刷票路上做个伴。
以上这些功能可以说订票助手都是首推并做得最好的。后来其中的一些功能也为其它的浏览器厂商所借鉴,但并不是他们的重点。很多的浏览器厂商(如数字、某狗、黄易等等)把精力放在了全自动上面(如验证码识别)。
对于国内各厂商的抢票版产品,木鱼评价如下:
- 开源在国内意味着可以免费用没限制想改就改、想抄就抄;
- 反正吹牛不上税,我说我第一就是我第一(简称不要脸);
- 天下大一统,你好我也好。
总的来说,和 12306 对接的接口没那么复杂也并不是什么机密,所以在这之后的各大独立 APP、软件等,其实可以说是大一统的场景,并没有基于谁的版本。木鱼认为在他之后,其实并没有让人感觉眼前一亮的新功能设计,可以说超越其设计的功能的产品(无论是独立软件还是 APP 还是独立购票页面)并没有出现。
前端技术的发展:
接着,我跟木鱼谈了一些前端技术的现状,木鱼感慨道:
- 新技术新标准(如 HTML5,WebGL,NodeJS 乃至 Reactive 本地化等)推行的越来越快,受众越来越广,是很值得高兴的事情。
- 以前觉得搞 C++ 的都是大牛,因为 C++ 实在是太高深了。现在觉得搞前端的都是折翼的天使,因为前端水太深了。
- 除此之外,前端和移动结合也越来越紧密,比如淘宝的 APP 很多地方都是内嵌的 HTML5 页面。但木鱼本人认为这些都有点过度膨胀了,他本人是很不喜欢这些 APP 的,因为它们共同的特点是卡、费流量、慢,交互复杂而诡异。
抢票软件与公平:
不可避免地,我跟木鱼再次提及了抢票软件与公平的话题。木鱼的主要观点如下:
- 抢票软件会做成什么样子,取决于作者的素质。车票作为一种稀缺资源(相对于需求来说),是不可能每个人都惠及的。因此无论你怎么做,总有人拿不到票。当一种资源无法普及给每个人并且依赖个人去自己争取的时候,那就是没有平等可言的。我们只能去构造一个良好的秩序去保证每个人购票的机会和权利。因此,在购票这件事情上,我们只能要求不要有明显可控却放任不管以及有意而为之导致的不公平和秩序错乱。
- 同时,作为 IT 公司、软件开发者,也是具有一定的社会责任的。作为购票软件的开发者,是不是使用了特殊的、异于常规的技术手段,以一种常人所不能及的速度和功能去进行操作,决定了它是不是会导致不公平。
- 作为市场的受体,用户往往是盲目的,因为他们的目标就是买一张票,有票就行别的什么都不会讲究。在缺乏监管的情况下,不少软件作者和企业为了吸引用户获得市场,会利用用户的无感知以及无监管的特点,做着看起来为人谋福利实则破坏秩序的行为。这些行为的典型代表就是离线抢票和验证码识别。它们利用自己所拥有的常人不具备的硬件设备资源、服务器资源以及机器手段,肆无忌惮地破坏购票秩序对 12306 设置的购票秩序肆意践踏,然后标榜自己是在为自己的用户抢票,然而这些手段都极大地损害了正常用户的购票过程,乃至对 12306 正常的经营活动造成强大的干扰。
- 与此同时,不在市面上流通或为一小部分人使用的黄牛软件,也会无视种种秩序,竭尽所能占用 12306 服务资源,在此同时对其他正常用户造成极大的干扰,对秩序也会有极大的破坏。
所以说,是不是会造成人为不公平,要看软件具体怎么做。
鸟瞰混合云架构
先前有文章报道了 12306 采用 Pivotal GemFire 分布式内存计算平台进行技术改造的过程,重点阐述了 12306 如何解决网络阻塞和集群性能水平扩展的问题。
笔者针对 12306 业务的特点,与京东云首席架构师杨海明作了一番简单的交流,主要内容如下:
核心的瓶颈:
12306 购票业务与电商秒杀系统有诸多相似的地方。通常的秒杀系统包含数据展示及数据处理两个层面的能力:数据展示层面一般是通过 CDN 在全国范围内部署,实现各地访问的加速,因此访问层面的压力不大,核心瓶颈实际上在后台请求响应方面。后端必须同时能够支持高并发请求,并返回用户请求结果足够快。实际瓶颈不在 CPU 的处理能力,而在于 I/O 的吞吐速度以及数据一致性方面。在通常的电商秒杀系统里为了实现尽可能快一点,后端存储使用内存级别的操作,然后在慢速磁盘及高速内存中增加闪存卡,在磁盘阵列中增加 SSD。虽然进行了磁盘层面的增强,但各地秒杀访问依然会带来数据一致性的问题,为了让全国各地的用户有平等的权利秒杀商品(比如新疆的客户与北京的客户)就需要部署多数据中心,同时实现多中心交易。
架构的选择:
分布式内存计算平台 (Pivotal GemFire) 对于秒杀及 12306 之类的业务是一个正确的选择,但不是唯一的选择。在市面上还有其它各种分布式内存计算平台,在这里就不一一赘述了。但内存计算平台对于提升秒杀系统的处理能力毋庸置疑。此类业务的基本特性在于商品数目有限制,但在短时间内消耗光,查多买少;设计系统时,需要考虑读多写少的特性,尽量将压力往前端移,降低对数据层面的冲击。12306 及秒杀业务的一个重要特点就是业务总量固定,比如 12306 对于一段时间的火车票是一个确定的数据量大小,秒杀业务在一个特定时间,商品的库存及商品的品类也是一个固定的数据量。因此,对于内存数据库层面,并不需要非常大的容量;相反,用户的请求如何合理的分散到各个负载服务器上,并汇总到这个数据后端,是秒杀平台设计优化的重点。
分库操作:
原则上,需要对 12306 的业务及数据格式充分了解,才能把握其瓶颈及拆分点。12306 涉及的是国家的铁路命脉,可以通过铁路的形态进行拆分,比如高铁、动车、快车及普通车;也可以通过地域进行拆分,比如北京中心、上海中心、广州中心、西安中心;再或者根据以往购票查询热度进行划分。原则是将数据分到多个地方,降低中心的压力。
小型机与 X86 优劣:
虽然外界报道 12306 业务是 x86 取代小机,但京东云首席架构师杨海明认为小机不会完全被 x86 架构取代。在所有 12306 的报道中,所解决的是查询的问题,而不是改变数据库的问题,小机 +Oracle 或者小机 +db2 的传统结构在确保数据一致性方面依然有绝对的优势。我们只是看到 12306 的查询库使用了分布式内存计算平台。也就是说,假设一个 x86 服务器出现问题,并不会造成铁路秩序的混乱;相反,增加 x86 的前端,非常适应之前描述的将压力往前端迁移的原则,将用户的访问压力用更廉价的 x86 来扛住,而把数据一致性和可靠性的任务交给更稳定更可靠的系统平台。在 x86 及小机的架构选择中,京东云首席架构师杨海明认为性能的瓶颈并不是绝对的问题。小机在 RISC 架构上的优势,在内存访问上的优势,及软硬一体优化的优势依然明显;相反,x86 的廉价、生态的丰富、越来越强的处理能力及易用性方面,也越来越吸引大量的用户。在现在对于系统的选择、成本的控制,以及出现问题的责任影响,成为决策者考虑的因素。举个例子,如果 12306 的后台票务核心系统在春节期间出现 2 小时无法购票,那造成的将不仅仅是经济损失,甚至可能引起各种不安定因素;相反,如果某电商的秒杀系统无法完成交易,那损失是可以控制的。
CDN 缓存:
CDN 缓存解决的是用户访问端的问题,12306 及秒杀场景下这些用户访问所带来的网络带宽是因为秒杀活动新增的,超过网站平时使用的带宽,这些带宽必须和运营商临时购买。为了减轻网站服务器的压力,更好地利用 CDN、反向代理等性能优化手段,需要将秒杀商品页面以静态页面的形式缓存在 CDN 上。秒杀开始,用户刷新页面时,用户请求不会到达数据服务器,而是通过缓存直接提供给终端用户。用户体验的好坏,就是看 CDN 内容更新的效果。
更多关于 12306 的内容
至此,我们从前到后、从用户到开发迅速地遍历了 12306 的全貌。我们也有理由相信,随着时代的不断发展,这一关系国计民生、牵涉亿万人的公共服务会越来越好。随着春节假期结束,2016 年的征程已然开始。愿 InfoQ 的读者在猴年里一切顺利。
InfoQ 上还有几篇关于 12306 的内容,感兴趣的读者可以延伸阅读如下:
给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。
评论