从12306谈起验证码的架构

     最近和众屌丝一样,在12306上面刷着春节回家的票。与她大战无数个回合之后,终于抢到了一张回家的高铁票,不断感慨最近人品还不错。当前,在使用12306的过程中,充满很多的心酸,念叨了铁道部的亲人很多次(罪过),其中最让人纠结的一项即是:验证码。
     12306采用验证码, 无疑是一种很不错的措施,可以在一定程度上阻止了黄牛们的疯狂行为,不过也给正常使用验证码的童鞋带了个很头痛的问题,在选座提交订单的关键时候,竟然验证码图片拉取不下来又或者验证过程非常耗时。鉴于自己也是无数码农中的有这职业病的一员,为此也来谈谈关于验证码的优化方案。
      验证码通常一张静态的图片,但是12306使用的却是一张动态的图片(GIF格式),动态图片的验证码大大的提高了破解的难度,但无疑也具备比较高的生成成本。我们首先来看一下通常情况下的验证码校验流程:
从12306谈起验证码的架构

     1)浏览器向验证码服务器发起获取验证码图片的请求;
     2)验证码服务器返回验证码图片文件和图片文件对应的ID编号(ID编号一般下发到浏览器的Cookie中);
     3)浏览器提交用户输入的验证码和图片文件ID编号;
     4)Web服务器校验用户输入的验证码与图片中展示的验证码是否一致,根据校验结果来向用户展示不同的页面;

     下面我们来看一下系统的整体架构图:
从12306谈起验证码的架构
从12306谈起验证码的架构
    主要处理流程为:
    1、获取验证码
         1)从配置中心中获取当前可用的频率控制、验证码库、待验证库可用的服务地址列表
         2)频率控制,主要控制当前请求是否属于恶意攻击;
         3)验证码库,获取可用的验证码信息,即获取一个可用的验证码图片文件地址;
         4)将获取到的验证码信息写入到待验证库中,方便后续进行校验验证码;
     2、校验验证码
         1)根据从前端获取到的图片编号(获取验证码时,下发的对应编号),来从待验证库中获取验证信息;
         2)将验证结果发送到校验统计模块;

    在设计过程中需要考虑的点:
    1)恶意刷新
     来自某个IP频繁的请求验证码,大体上就可以判定验证码正在被刷中,需要采取措施进行一定的频率限制,降低继续被刷的请求量,这里我们可以采用比较简单限制来个某个IP请求次数,当然也可以根据业务特性,添加业务对应的频率控制逻辑;
     2)验证码有效期
     验证码可以生成一条Key—Value的数据存放到Memcache中(即待验证库),Value为:验证码图片文件ID、验证码Code、生成时间,每个请求验证码图片的请求,均在缓存中插入一条记录,每发送一个验证请求,即将缓存中的这条记录删除失效。
     3)图片文件与图片编号
     两者之间不可以建立一一对应的关系,否则,坏人只需要保存图片编号,下次就可以重复提交对应的验证码;但是可以建立一对多的关系,也就是一张图片对应多个图片编号,但是一图片编号只能唯一对应图片;
      4)验证码生成
     验证码图片文件的生成相比而言是比较慢的操作,所以需要采用离线计算成功的方案,避免浏览器获取图片验证码文件耗时比较久,影响用户的体验;这里存放一个问题,什么时机启动验证码生成的流程,有以下几种策略:
       a)每天定时生成更新,策略简单实现;
       b)定时检测验证码可用量,来生成一批新的验证码文件;
     总之,验证码图片文件的生成必须是离线进行的,同时在此时需要为图片文件生成一个或者多个编号;服务部署的个数,完全取决于验证码的消耗量;
     5)验证码库
     验证码库,应该采用那种数据结构?Mysql还是其他的数据结构。在这里,可以尝试采用Redis的list结构来当作消息队列来使用或者其他的可用的消息队列。需要获取验证码时,从消息队列中Pop出一个值即可。每个记录中至少需要存储的字段为:图片编号、图片地址、验证码等信息。消息队列中的记录 < 50%时,可以出发验证码生成逻辑来定时插入新的验证码。面对更并发的验证码请求量,可以在集群中多部署几套Redis消息队列以及验证码生成系统来应对;
     6)图片编号的生成
     验证码图片文件需要支持在多台机器上面部署多个服务,同时也要支持在单机上面部署多个服务,这里就需要解决如何来生成唯一验证码图片编号的问题。可以通过根据机器IP和服务编号来解决这个问题,每台机器上面的服务均存在一个服务编号(保证服务编号在单机上是唯一的),计算公式可以为:ID = MD5(IP+服务编号+时间戳+自动序列号);

其实,上面写的都是错的,我压根没有做过类似的服务。

从12306谈起验证码的架构

上一篇:UNIX/Linux进程间通信IPC系列(二)管道


下一篇:Linux基本命令