共計 3250 個字符,預計需要花費 9 分鐘才能閱讀完成。
這篇文章主要介紹 Redis 中如何限制操作頻率,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
場景
場景 1
留言功能限制,30 秒 內只能評論 10 次,超出次數不讓能再評論,并提示:過于頻繁
場景 2
點贊功能限制,10 秒 內只能點贊 10 次,超出次數后不能再點贊,并禁止操作 1 個小時,提示:過于頻繁,被禁止操作 1 小時
場景 3
上傳記錄功能,限制一天只能上傳 100 次,超出次數不讓能再上傳,并提示:超出今日上線
抽離本質
在業務開發的過程中,我們不斷的參與各種業務場景的方案設計,往往很容易碰到很類似的場景,只不過當前所屬的業務模塊不一樣,其實這些需求的本質是解決同一個問題,當遇到這種場景的時候,我們需要根據自己經驗分析抽離出需求的本質問題,實現一個通用的解決方案,讓自己的解決方案更有價值,這可能就是區別于你是有靈魂的工程師還是 cp(copy paste)最強王者吧。
分析上面 3 個業務場景,可以從中發現其中有相似的邏輯,稱它為同類的問題,現在我們就是要抽離這個問題,設計一個通用的解決方案,勾畫相同邏輯流程圖:
通過分析上面的需求場景,抽離出他們都需要的那些條件:
限制對象:用戶
限制操作(評論,點贊,記錄,…)
時間范圍 X 秒內
限制操作數 Y 次
超出后禁止操作時間 Z(秒 / 具體時間)
超出后不讓再操作,并提示
(最小時間單位用秒:天 / 小時 / 分鐘都可換算成秒,用秒可以解決更多的場景)
如果把功能抽離成一個通用函數是不是大概是這樣:
?php
* 頻率限制
* @param string $action 操作動作
* @param int $userId 發起操作的用戶 ID
* @param int $time 時間范圍 X 秒內
* @param int $number 限制操作數 Y 次
* @param array $expire 超出封印時間 Z [type = 1, ttl = 過期時間 / 秒] [type = 2, ttl = 具體過期時間戳] 二選一
* @return bool
* @throws \Exception
*/
public static function frequencyLimit(string $action, int $userId, int $time, int $number, $expire = [])
// todo 根據用戶操作動作時間范圍,進行頻率的控制和失效釋放
}
解決方案落地
功能中需要對用戶發起的操作和時間,以及累計次數進行存儲,并且需要失效過期的清理,如果這個時候我們依賴 mysql 做存儲,想想都覺的挺痛苦,這里主角:redis 終于登場了,基于 redis 特性,incr 的原子操作和 key 支持過期機制,內存存儲的效率優勢,可以相對簡單靈活并且又高效的完成目的。
這里簡單實現個通用功能的代碼:
?php
* 頻率限制
* @param string $action 操作動作
* @param int $userId 發起操作的用戶 ID
* @param int $time 時間范圍 X 秒內
* @param int $number 限制操作數 Y 次
* @param array $expire 超出封印時間 Z [type = 1, ttl = 過期時間 / 秒] [type = 2, ttl = 具體過期時間戳] 二選一
* @return bool
* @throws \Exception
*/
public function frequencyLimit(string $action, int $userId, int $time, int $number, $expire = [])
if (empty($action) || $userId = 0 || $time = 0 || $number = 0) {
throw new \Exception( 非法參數
}
$key = act:limit: . $action . : . $userId;
$r = RedisClient::connect();
// 獲取當前累計次數
$current = intval($r- get($key));
if ($current = $number) return false;
// 累計并返回最新值
$current = $r- incr($key);
// 第一次累加,設置控制操作頻率的有效時間
if ($current === 1) $r- expire($key, $time);
// 未超出限制次數先放過
if ($current $number) return true;
// 超出后根據需要重新設置過期失效時間 $current === $number 判斷保證只重新設置一次
$type = empty($expire[ type]) ? 0 : intval($expire[ type
$ttl = empty($expire[ ttl]) ? 0 : intval($expire[ ttl
if ($current === $number $ttl 0 in_array($type, [1, 2])) { if ($type === 1) $r- expire($key, $ttl);
if ($type === 2) $r- expireAt($key, $ttl);
}
return false;
// 場景 1
* 評論限制
* @param int $userId
* @return bool|string
*/
public function doComment(int $userId)
try { $pass = FrequencyLimit::doHandle( comment , $userId, 30, 10);
if (!$pass) return 過于頻繁
// todo 評論邏輯
return true;
} catch (\Exception $e) { return $e- getMessage();
}
// 場景 2
* 點贊限制
* @param int $userId
* @return bool|string
*/
public function doLike(int $userId)
try { $pass = FrequencyLimit::doHandle( like , $userId, 10, 10, [ type = 1, ttl = 1 * 60 * 60]);
if (!$pass) return 過于頻繁,被禁止操作 1 小時
// todo 點贊邏輯
return true;
} catch (\Exception $e) { return $e- getMessage();
}
// 場景 3
* 上傳限制
* @param int $userId
* @return bool|string
*/
public function doUpload(int $userId)
try { $expire = strtotime(date( Y-m-d , strtotime(+1 . days)));
$pass = FrequencyLimit::doHandle(upload , $userId, 1 * 24 * 60 * 60, 100, [ type = 2, ttl = $expire]);
if (!$pass) return 超出今日上線
// todo 上傳邏輯
return true;
} catch (\Exception $e) { return $e- getMessage();
}
// 場景 N
編碼上可以根據你設計這個通用方案的復雜度進行進一步抽象,如抽象成頻率限制的功能類 等
以上是“Redis 中如何限制操作頻率”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注丸趣 TV 行業資訊頻道!