我們知道當網站的訪問量突然很大的時候肯定會對服務器造成影響,甚至無法訪問,如果是正常的訪問那么很好說明業務量增大可以考慮系統的擴展,但是如果是搜索引擎爬蟲頻繁訪問或是一些惡意訪問,那這時候我們就應該限制這些訪問的訪問次數。redis剛好可以解決這個問題
一、方式一
限制每個用戶每分鍾最多只能訪問100個頁面。實現思路:key使用有"rate.limiting:IP",value使用數值,用戶每次訪問將value的值通過INCR命令自增1.如果自增后的值是1同時設置過期時間為1分鍾。這樣用戶每次訪問的時候都讀取該鍵的值,如果超過了100就表明該用戶的訪問頻率超過了限制,需要提示用戶稍后訪問。且該鍵每分鍾會自動被刪除。所以下一分鍾又會重新計算,也就達到了限制訪問頻率的目的。

代碼:
String key = "rage.limiting:"+ip; // 判斷key是否存在
int flag = exists(key);// key rate.limiting:192.168.88.60
if(flag == 1){ // key 存在 自增1
int count = incr(key); if(count > 100){ // 超過限制
log.info("訪問頻率超過了限制,請稍后重試"); return ; } }else{ // key 不存在
multi(); // 開啟事務
incr(key); // key不存在自增1 值為1
expire(key,60); // 設置過期時間
exec(); // 提交事務
}
二、方式二
實現方式一其實還有個問題,比如如果用戶第一分鍾的訪問了99次,前面58秒訪問了9次,后面1秒訪問了90次,然后用戶后一秒也訪問了99次,而后一分鍾的第一秒訪問了90次,后面的58秒訪問了9次,這樣按照上面的算法是沒有問題的,但是這種極端情況大家還是可以發現問題的。

解決方法:先將上面案例中的100次調整為10次便於在次場景中描述,要精確的保證同一個用戶每分鍾最多訪問10次,需要記錄下來用戶每次訪問的時間。因此對每個用戶我們使用一個List列表類型的鍵來記錄他最近10次訪問的時間,一旦鍵中的元素超過10個,就判斷最早的元素距離現在的時間是否小於1分鍾。如果是表示用戶最近1分鍾訪問次數超過了10次,如果不是就將現在的時間加入到隊列中,同時把最早的元素刪除。


代碼
String key = "rate.limiting:"+IP; int listLength = llen(key); if(listLength < 10){ lpush(key,new()); }else{ long time = lindex(key,-1); if(now()-time < 60){ log.info("訪問頻率超過了限制,請稍后再試"); }else{ lpush(key,now); ltrim(key,0,9); } }
PHP代碼(存在不足,結合上面案例更改)
$redis = new Redis(); $redis->connect('127.0.0.1', 6379); //獲取客戶端真實ip地址
function get_real_ip(){ static $realip; if(isset($_SERVER)){ if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){ $realip=$_SERVER['HTTP_X_FORWARDED_FOR']; }else if(isset($_SERVER['HTTP_CLIENT_IP'])){ $realip=$_SERVER['HTTP_CLIENT_IP']; }else{ $realip=$_SERVER['REMOTE_ADDR']; } }else{ if(getenv('HTTP_X_FORWARDED_FOR')){ $realip=getenv('HTTP_X_FORWARDED_FOR'); }else if(getenv('HTTP_CLIENT_IP')){ $realip=getenv('HTTP_CLIENT_IP'); }else{ $realip=getenv('REMOTE_ADDR'); } } return $realip; } //這個key記錄該ip的訪問次數 也可改成用戶id
$key = get_client_ip(); //該Key記錄訪問的次數,目前是以IP為例,也可以把用戶id作為key,如userid_123456 //限制次數為3次。
$limit = 3; $check = $redis->exists($key); if($check){ $redis->incr($key); $count = $redis->get($key); if($count > 3){ exit('已經超出了限制次數'); } }else{ $redis->incr($key); //限制時間為60秒
$redis->expire($key,60); } $count = $redis->get($key); echo '第 '.$count.' 次請求';
