第一層:合法性限流
首先對(duì)于合法性要有一個(gè)認(rèn)識(shí)咒锻,哪些行為是合法的豁翎,哪些是非法的蝴猪,比如:用戶反復(fù)購買同一件商品行為(刷單)漓摩,或者是機(jī)器人參與秒殺
1.驗(yàn)證碼
? 針對(duì)不同的非法請(qǐng)求要用不同的方法來限制裙士,對(duì)于機(jī)器人,首先想到的應(yīng)該是增加驗(yàn)證碼功能管毙,驗(yàn)證碼還能使用戶的下單時(shí)長(zhǎng)增加腿椎,比如:原來的下單時(shí)長(zhǎng)是1秒,在增加驗(yàn)證碼之后夭咬,用戶的下單時(shí)間可能就會(huì)在1-3秒之間啃炸,這樣原來一秒鐘需要處理100萬請(qǐng)求,現(xiàn)在每秒就需要處理33萬卓舵,可以降低流量的峰值
2.IP黑名單
? 通過網(wǎng)絡(luò)監(jiān)控技術(shù)獲取到某個(gè)IP請(qǐng)求服務(wù)的時(shí)間是毫秒級(jí)別或者南用,多次下單同一個(gè)商品,那么這個(gè)IP很有可能就是機(jī)器人或者刷單行為掏湾,這樣就可以把這個(gè)IP加入黑名單中來減少不合法的請(qǐng)求
3.隱藏入口地址
? 比如:在秒殺開始之后才能打開秒殺頁面等
第二層:負(fù)載限流
理論基礎(chǔ):網(wǎng)絡(luò)七層模型
? 1)物理層:物理層
? 2)數(shù)據(jù)鏈路層:提供介質(zhì)訪問和鏈路管理
? 3)網(wǎng)絡(luò)層:IP選址和路由選擇
? 4)傳輸層:建立裹虫、管理和維護(hù)端到端的連接
? 5)會(huì)話層:建立、管理和維護(hù)對(duì)話
? 6)表示層:數(shù)據(jù)格式轉(zhuǎn)化和數(shù)據(jù)加密
? 7)應(yīng)用層:為應(yīng)用程序提供服務(wù)
方法:
? 1.通過Nginx進(jìn)行負(fù)載均衡融击,如果有3臺(tái)服務(wù)器筑公,那么每臺(tái)服務(wù)器就會(huì)承載三分之一的請(qǐng)求
? 2.在數(shù)據(jù)鏈路層,通過MAC地址進(jìn)行負(fù)載;在網(wǎng)絡(luò)層尊浪,通過IP進(jìn)行負(fù)載匣屡,在傳輸層封救,通過端口號(hào)進(jìn)行負(fù)載
? 3.級(jí)聯(lián)負(fù)載:也就是在第二層->第三層->第四層->第七層都進(jìn)行負(fù)載,但是這種方法不推薦捣作,因?yàn)槊窟M(jìn)行一次負(fù)載都會(huì)增加轉(zhuǎn)發(fā)路徑誉结,這樣會(huì)帶來網(wǎng)絡(luò)延遲的問題
注意:所以常見的負(fù)載限流是通過Nginx(應(yīng)用層)或者 Nginx+LVS(傳輸層,通過網(wǎng)絡(luò)端口進(jìn)行負(fù)載)來進(jìn)行負(fù)載虾宇,或者通過購買F5或者Array等硬件工具來進(jìn)行負(fù)載
第三層:服務(wù)限流(請(qǐng)求已經(jīng)抵達(dá)服務(wù)器)
方法:
? 1.通過算法來進(jìn)行限流:如漏桶算法和令牌桶算法(php可以配合redis來實(shí)現(xiàn))
? 漏桶算法理解:水(請(qǐng)求)先進(jìn)入到漏桶里,漏桶以一定的速度出水(接口有響應(yīng)速率),當(dāng)水流入速度過大會(huì)直接溢出(訪問頻率超過接口響應(yīng)速率),然后就拒絕請(qǐng)求,可以看出漏桶算法能強(qiáng)行限制數(shù)據(jù)的傳輸速率搓彻。
? 令牌桶算法理解:是和漏桶算法一樣但是方向相反的算法,設(shè)置一個(gè)桶的大小,每隔一定時(shí)間往桶里加入一個(gè)token(如10ms)嘱朽,桶滿了就不加了旭贬,每一個(gè)請(qǐng)求過來從桶里取一個(gè)token,如果沒有Token可拿了就阻塞或者拒絕服務(wù)
代碼示例:
<?php
namespace Api\Lib;
/**
* 限流控制
*/
class RateLimit
{
private $minNum = 60; //單個(gè)用戶每分訪問數(shù)
private $dayNum = 10000; //單個(gè)用戶每天總的訪問量
public function minLimit($uid)
{
$minNumKey = $uid . '_minNum';
$dayNumKey = $uid . '_dayNum';
$resMin = $this->getRedis($minNumKey, $this->minNum, 60);
$resDay = $this->getRedis($minNumKey, $this->minNum, 86400);
if (!$resMin['status'] || !$resDay['status']) {
exit($resMin['msg'] . $resDay['msg']);
}
}
public function getRedis($key, $initNum, $expire)
{
$nowtime = time();
$result = ['status' => true, 'msg' => ''];
$redisObj = $this->di->get('redis');
$redis->watch($key);
$limitVal = $redis->get($key);
if ($limitVal) {
$limitVal = json_decode($limitVal, true);
$newNum = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($nowtime - $limitVal['time'])));
if ($newNum > 0) {
$redisVal = json_encode(['num' => $newNum, 'time' => time()]);
} else {
return ['status' => false, 'msg' => '當(dāng)前時(shí)刻令牌消耗完搪泳!'];
}
} else {
$redisVal = json_encode(['num' => $initNum, 'time' => time()]);
}
$redis->multi();
$redis->set($key, $redisVal);
$rob_result = $redis->exec();
if (!$rob_result) {
$result = ['status' => false, 'msg' => '訪問頻次過多稀轨!'];
}
return $result;
}
}
2.通過消息隊(duì)列來進(jìn)行限流:
? 如:有三個(gè)子系統(tǒng)每秒分別能處理2萬,3萬岸军,5萬請(qǐng)求奋刽,如果不用消息隊(duì)列,那么每隔子系統(tǒng)就要處理3.3萬個(gè)請(qǐng)求艰赞,前兩個(gè)系統(tǒng)就會(huì)出現(xiàn)問題佣谐,如果使用消息隊(duì)列,那么這三個(gè)系統(tǒng)就可以針對(duì)自己的能力去消息隊(duì)列中拉取特定數(shù)量的請(qǐng)求進(jìn)行處理
3.緩存策略:
? 靜態(tài)緩存:如html和js代碼可以緩存到瀏覽器中方妖,如果是圖片的話可以緩存到nginx中或者通過nginx轉(zhuǎn)發(fā)到OSS中
? 動(dòng)態(tài)緩存:可以緩存的本地服務(wù)器中狭魂,如果本地緩存失效,可以緩存到遠(yuǎn)程的redis集群中