記錄一起圖形驗證碼引起的線上事故

驗證碼,只要一點簡單的邏輯母市,就能避免泱泱腳本大軍的騷擾矾兜。但利劍往往是雙刃的损趋,并不是每個場景都適用患久,本文將通過記錄一起線上事故,來展示使用圖形驗證碼的代價浑槽,并討論如何應(yīng)對類似情況蒋失。

科普一個冷知識:驗證碼的英文是CAPTCHA,是Completely Automated Public Turing test to tell Computers and Humans Apart的縮寫桐玻,翻譯過來是“全自動區(qū)分計算機和人類的圖靈測試”(不是“雅木茶”)篙挽。

事故過程

一切都要從一個簡單的需求說起

公司新游戲即將上線,運營已經(jīng)激動地搓手手了镊靴,于是一次預(yù)約活動規(guī)劃了下來铣卡。就是那種輸入手機號和手機系統(tǒng)的預(yù)約活動链韭。這個需求太簡單了,開發(fā)也沒多想煮落,一梭子代碼就下去了敞峭。唯一一層防護是為了避免腳本刷接口,要求預(yù)約時要輸入一次圖形驗證碼蝉仇。

悲劇即將上演

倒計時3,2,1旋讹,活動開始。開始的時候一切順利轿衔,流量流入沉迹,預(yù)約數(shù)據(jù)也在有條不紊地入庫。不過一會兒害驹,客服就開始忙活了起來鞭呕,原來大量玩家反饋:本該顯示圖形驗證碼的地方現(xiàn)在正顯示著一個x。于是宛官,一句”你們公司用的是土豆服務(wù)器嗎“刷遍了微博貼吧琅拌。

開始思考
這是圖形驗證碼的一般做法
  • 在img標簽的src填入生成驗證碼地址,該接口會在內(nèi)存中生成一張圖片摘刑。嚴謹一些的話进宝,還會在圖片上面打上干擾線和噪點。

  • 將驗證碼存入Session后枷恕,返回圖片党晋。

  • 用戶提交數(shù)據(jù),比較參數(shù)和Session值徐块。

天底下沒有理所當然的事

這本來是最正常不過的操作未玻,但仔細過一遍可以發(fā)現(xiàn),生成一張圖形驗證碼的代價是很大的胡控。

  • 生成圖片扳剿,占用比普通操作更多的內(nèi)存

  • 生成隨機數(shù)、噪點昼激、干擾線庇绽,需要生成隨機數(shù)

  • 圖片傳輸,占用帶寬

  • 驗證碼讀取\寫入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);
  • 沒有使用框架瞧掺、組件,簡化加載過程凡傅。功能越強大辟狈,性能越受限。

  • 為了減少隨機數(shù)生成,驗證碼僅四位哼转,背景色明未、字體色直接固定,也沒有加噪點壹蔓,加干擾線亚隅。

  • 生成并輸出圖片后,銷毀資源庶溶。

  • 不保存圖片到磁盤煮纵。

寫一個方法,模擬用戶看到驗證碼偏螺,輸入驗證碼的過程:

$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' => '',
];
單次調(diào)用

獲取驗證碼用了184ms行疏。來看看機器指標,恩套像,沒什么波瀾酿联,系統(tǒng)1分鐘平均負載是0.3。

什么是系統(tǒng)1分鐘平均負載夺巩?

1分鐘內(nèi)贞让,占用全部CPU算力的比例。

舉個栗子柳譬,如果機器是2核的喳张,那么滿負載時,最大值就是2美澳。上面的負載是0.3销部,意味著程序只用到了1個CPU約三分之一的算力。

這次來試試1000次
go-stress-testing-linux -c 1000 -n 1 -u {your url}

總耗時5s制跟,全部成功(HTTP狀態(tài)碼200)舅桩,再來看看指標,系統(tǒng)1分鐘平均負載上升至1雨膨,還好還好擂涛。

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

總耗時36s聊记,成功5007撒妈,失敗4993(HTTP狀態(tài)碼509),看看指標:

1.png

硬件資源并沒有消耗完呀甥雕,怎么會有失敗請求呢踩身?

原因很簡單胀茵,上面已經(jīng)告訴你啦社露,帶寬占滿啦。代碼生成的驗證碼圖片一張約1.7k琼娘,比起其他類型的數(shù)據(jù)已經(jīng)大很多了峭弟。所謂聚少成多附鸽,聚沙成塔,一個人的力量可能不算什么瞒瘸,但千萬個人的力量就絕對不可忽視坷备。

各個HTTP狀態(tài)碼表示詳見(http://zhaomaomao.net/article/1/13)。

得想個辦法讓所有請求都進去

要達到這個目的情臭,需要一點點改造:

  • 把生成驗證碼的邏輯從controller移到Laravel的Command模塊省撑,這樣,就不會出現(xiàn)php的腳本超時啦俯在。

  • 寫一個sh腳本啟動這些CMD竟秫,這樣,就繞過了web服務(wù)器跷乐。

#!/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個驗證碼生成,已經(jīng)要花20多分鐘了愕提,單從這點看已經(jīng)不能拿上正式服了馒稍。由于沒有經(jīng)過Nginx處理,內(nèi)存倒沒有飆高浅侨。

分析一波

由上面幾組數(shù)據(jù)可以看出纽谒,自己生成圖形驗證碼消耗資源從多到少依次為:網(wǎng)絡(luò)帶寬 > CPU > 內(nèi)存 > 磁盤讀寫。

筆者的虛擬機是2核如输,4G佛舱,3M帶寬,在Nginx各項超時時間5分鐘的情況下(這個時間已經(jīng)很長了)挨决,每秒也只能正常處理約140個驗證碼請求请祖。

怎么優(yōu)化呢

有人可能會問,蛤脖祈?這有什么可優(yōu)化的肆捕?就像上面說的, 天底下真的沒有理所當然的事情盖高。筆者在此拋磚引玉慎陵,獻個丑啦。

限制前端的刷新頻率

筆者自己就是這樣喻奥,當看不清驗證碼心煩意亂的時候席纽,就會瘋狂點擊刷新。驗證碼生成本身就需要一定時間撞蚕,結(jié)果刷出新的還沒來得及顯示润梯,就又進入了下一次生成的循環(huán)。

3.png

用js稍稍控制一下,在img圖片load完成前禁止刷新纺铭;刷新后稍稍等個幾秒寇钉,就可以避免這類情況。

把驗證碼生成的服務(wù)與主要邏輯服務(wù)分離

這和靜態(tài)資源與網(wǎng)站其他資源分離是一個道理舶赔。既然驗證碼圖片占帶寬扫倡,那就不走主要邏輯服務(wù)器的流量,這樣就可以增大主要邏輯服務(wù)器的吞吐量竟纳。

使用沒有圖片的驗證碼

例如把方塊拖動到最右邊啦撵溃,完成拼圖啦(這種也需要圖片,但只需要加載一次)锥累,把圖片旋轉(zhuǎn)到正面啦之類的征懈。

使用第三方驗證碼

能用錢解決的,都不是問題揩悄。

就到這里吧

希望本文或多或少對你有些許幫助卖哎。謝謝你的閱讀。再見删性。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末亏娜,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蹬挺,更是在濱河造成了極大的恐慌维贺,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巴帮,死亡現(xiàn)場離奇詭異溯泣,居然都是意外死亡,警方通過查閱死者的電腦和手機榕茧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門垃沦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人用押,你說我怎么就攤上這事肢簿。” “怎么了蜻拨?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵池充,是天一觀的道長。 經(jīng)常有香客問我缎讼,道長收夸,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任血崭,我火速辦了婚禮卧惜,結(jié)果婚禮上厘灼,老公的妹妹穿的比我還像新娘。我一直安慰自己序苏,他們只是感情好手幢,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布捷凄。 她就那樣靜靜地躺著忱详,像睡著了一般。 火紅的嫁衣襯著肌膚如雪跺涤。 梳的紋絲不亂的頭發(fā)上匈睁,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音桶错,去河邊找鬼航唆。 笑死,一個胖子當著我的面吹牛院刁,可吹牛的內(nèi)容都是我干的糯钙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼退腥,長吁一口氣:“原來是場噩夢啊……” “哼任岸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起狡刘,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤享潜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嗅蔬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剑按,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年澜术,在試婚紗的時候發(fā)現(xiàn)自己被綠了艺蝴。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸟废,死狀恐怖吴趴,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情侮攀,我是刑警寧澤锣枝,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站兰英,受9級特大地震影響撇叁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜畦贸,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一陨闹、第九天 我趴在偏房一處隱蔽的房頂上張望楞捂。 院中可真熱鬧杆怕,春花似錦子刮、人聲如沸鸿吆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽座柱。三九已至榴鼎,卻和暖如春口叙,著一層夾襖步出監(jiān)牢的瞬間乡数,已是汗流浹背椭蹄。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留净赴,地道東北人绳矩。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像玖翅,于是被迫代替她去往敵國和親翼馆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348

推薦閱讀更多精彩內(nèi)容