在咱們作社區的時候,常常會出現發水帖的同窗。對於這種惡意刷帖的,咱們的運營同窗非常頭疼,並且這種還不能在網關進行ip之類的過濾,只能基於單個單個用戶進行處理,咱們常常策略就是:每分鐘發帖次數不能超過2個,超事後就關小黑屋10分鐘。redis
對於這種「黑惡」請求,咱們必需要作到是關小黑屋,固然有的系統架構比較大的,在網關層面就已經進行關了,咱們這裏是會在業務層來作,由於咱業務不是很大,固然同窗們也能夠把這個移植到網關層,這樣不用穿透到咱們業務側,最少可以減小咱們機房內部網絡流量。bash
以咱們場景爲例子,使用Redis來作分佈式鎖和原子計數器網絡
這個方案,在不少人設計的時候,都會考慮,看起來也沒有太大問題,主要流程是:架構
//將咱們用戶請求量疊加1
$request_nums = Redis::incr('user:1:request:nums',1);
//第一次疊加,設置key的過時時間
if ($request_nums == 1){
Redis::expire('user:1:request:nums',300);
}
if($request_nums > 10){
//加入小黑屋,下次再進來就要鎖定判斷
}
...
複製代碼
問題:咋一看是沒有問題,每次計算都在個人區間內,可以保證一個區間內的請求量是沒問題的,並且仍是要咱們Redis的原子計數器,可是這裏有一個問題是,一個用戶兩個時間段內都沒有問題,可是跨時間段這個點是沒有考慮的。分佈式
那麼有辦法解決這個時間推移問題形成時間段計算量不精準的問題嗎?lua
答案是確定有,我接下來是使用了Redis的有序集合來作。spa
大體流程:設計
//將咱們時間戳寫入咱們redis的有序集合裏面
Redis::zadd('user:1:request:nums',1561456435,'1561456435.122');
//設置key的過時時間爲10分鐘
Redis::expire('user:1:request:nums',300);
//刪除咱們10分鐘之前的數據
Redis::ZREMRANGEBYSCORE('user:1:request:nums',0,1561456135);
//獲取裏面剩下請求個數
$request_nums=(int)Redis::zcard(self::TIMELINE_ELEVEL_KEY);
if($request_nums >= 10){
//加入小黑屋,下次再進來就要鎖定判斷
}
...
複製代碼
由於咱們不是單純記錄數值,而是會將請求時間記錄下來,那麼隨着時間推移,咱們的請求數統計是不會斷代的。code
謝謝你們閱讀!!!cdn