记录一起图形验证码引起的线上事故

验证码,只要一点简单的逻辑,就能避免泱泱脚本大军的骚扰。但利剑往往是双刃的,并不是每个场景都适用,本文将通过记录一起线上事故,来展示使用图形验证码的代价,并讨论如何应对类似情况。

科普一个冷知识:验证码的英文是CAPTCHA,是Completely Automated Public Turing test to tell Computers and Humans Apart的缩写,翻译过来是“全自动区分计算机和人类的图灵测试”(不是“雅木茶”)。

事故过程

一切都要从一个简单的需求说起

公司新游戏即将上线,运营已经激动地搓手手了,于是一次预约活动规划了下来。就是那种输入手机号和手机系统的预约活动。这个需求太简单了,开发也没多想,一梭子代码就下去了。唯一一层防护是为了避免脚本刷接口,要求预约时要输入一次图形验证码。

悲剧即将上演

倒计时3,2,1,活动开始。开始的时候一切顺利,流量流入,预约数据也在有条不紊地入库。不过一会儿,客服就开始忙活了起来,原来大量玩家反馈:本该显示图形验证码的地方现在正显示着一个x。于是,一句”你们公司用的是土豆服务器吗“刷遍了微博贴吧。

开始思考
这是图形验证码的一般做法
  • 在img标签的src填入生成验证码地址,该接口会在内存中生成一张图片。严谨一些的话,还会在图片上面打上干扰线和噪点。

  • 将验证码存入Session后,返回图片。

  • 用户提交数据,比较参数和Session值。

天底下没有理所当然的事

这本来是最正常不过的操作,但仔细过一遍可以发现,生成一张图形验证码的代价是很大的。

  • 生成图片,占用比普通操作更多的内存

  • 生成随机数、噪点、干扰线,需要生成随机数

  • 图片传输,占用带宽

  • 验证码读取\写入Session,需要读写磁盘

实践一下
代码

写一个简单的验证码生成方法:

$str = "23456789abcdefghijkmnpqrstuvwxyzABCDEFGHIJKLMNPQRSTUVW";
$len = strlen($str) - 1;
$code = '';
for ($i = 0; $i < 4; $i++) {
    $code .= $str[mt_rand(0, $len)];
}

$img = imagecreatetruecolor(100, 30);
imagefilledrectangle($img, 0, 0, 100, 30, imagecolorallocate($img, 255, 255, 255));
imagefttext($img, 20, mt_rand(-5, 5), 10, 25, imagecolorallocate($img, 0, 0, 0), '{your font path}', $code);

$_SESSION['captcha'] = $code;

header("Cache-Control: no-cache");
header("Content-type: image/png;charset=utf-8");
imagepng($img);
imagedestroy($img);
  • 没有使用框架、组件,简化加载过程。功能越强大,性能越受限。

  • 为了减少随机数生成,验证码仅四位,背景色、字体色直接固定,也没有加噪点,加干扰线。

  • 生成并输出图片后,销毁资源。

  • 不保存图片到磁盘。

写一个方法,模拟用户看到验证码,输入验证码的过程:

$input = $_POST['verify'] ?? '';
if ($input === '') {
    return [
        'code' => 0,
        'msg' => 'verify empty',
    ];
}
$code = $_SESSION['captcha'] ?? '';
if ($code === '') {
    return [
        'code' => 0,
        'msg' => 'verify not exist',
    ];
}
if (0 !== strcasecmp($input, $code)) {
    return [
        'code' => 0,
        'msg' => 'verify failed',
    ];
}
unset($_SERVER['captcha']);
return [
    'code' => 1,
    'msg' => '',
];
单次调用

获取验证码用了184ms。来看看机器指标,恩,没什么波澜,系统1分钟平均负载是0.3。

什么是系统1分钟平均负载?

1分钟内,占用全部CPU算力的比例。

举个栗子,如果机器是2核的,那么满负载时,最大值就是2。上面的负载是0.3,意味着程序只用到了1个CPU约三分之一的算力。

这次来试试1000次
go-stress-testing-linux -c 1000 -n 1 -u {your url}

总耗时5s,全部成功(HTTP状态码200),再来看看指标,系统1分钟平均负载上升至1,还好还好。

康康你的极限在哪里?
go-stress-testing-linux -c 10000 -n 1 -u {your url}

总耗时36s,成功5007,失败4993(HTTP状态码509),看看指标:

1.png

硬件资源并没有消耗完呀,怎么会有失败请求呢?

原因很简单,上面已经告诉你啦,带宽占满啦。代码生成的验证码图片一张约1.7k,比起其他类型的数据已经大很多了。所谓聚少成多,聚沙成塔,一个人的力量可能不算什么,但千万个人的力量就绝对不可忽视。

各个HTTP状态码表示详见(http://zhaomaomao.net/article/1/13)。

得想个办法让所有请求都进去

要达到这个目的,需要一点点改造:

  • 把生成验证码的逻辑从controller移到Laravel的Command模块,这样,就不会出现php的脚本超时啦。

  • 写一个sh脚本启动这些CMD,这样,就绕过了web服务器。

#!/bin/bash
start_at=`date +%s`
for((i=1;i<=5000;i++));
do
php artisan {your command} > /dev/null;
done
end_at=`date +%s`
echo $[end_at-$start_at]

总耗时1219s,看看指标:

2.png

处理5k个验证码生成,已经要花20多分钟了,单从这点看已经不能拿上正式服了。由于没有经过Nginx处理,内存倒没有飙高。

分析一波

由上面几组数据可以看出,自己生成图形验证码消耗资源从多到少依次为:网络带宽 > CPU > 内存 > 磁盘读写。

笔者的虚拟机是2核,4G,3M带宽,在Nginx各项超时时间5分钟的情况下(这个时间已经很长了),每秒也只能正常处理约140个验证码请求。

怎么优化呢

有人可能会问,蛤?这有什么可优化的?就像上面说的, 天底下真的没有理所当然的事情。笔者在此抛砖引玉,献个丑啦。

限制前端的刷新频率

笔者自己就是这样,当看不清验证码心烦意乱的时候,就会疯狂点击刷新。验证码生成本身就需要一定时间,结果刷出新的还没来得及显示,就又进入了下一次生成的循环。

3.png

用js稍稍控制一下,在img图片load完成前禁止刷新;刷新后稍稍等个几秒,就可以避免这类情况。

把验证码生成的服务与主要逻辑服务分离

这和静态资源与网站其他资源分离是一个道理。既然验证码图片占带宽,那就不走主要逻辑服务器的流量,这样就可以增大主要逻辑服务器的吞吐量。

使用没有图片的验证码

例如把方块拖动到最右边啦,完成拼图啦(这种也需要图片,但只需要加载一次),把图片旋转到正面啦之类的。

使用第三方验证码

能用钱解决的,都不是问题。

就到这里吧

希望本文或多或少对你有些许帮助。谢谢你的阅读。再见。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容