使用Redis實現訪問頻率控制

這裏使用Redis遞減配合適當的過時策略來實現redis

# 初始設定rate上限
$ SET access_times 100
# 設置60秒過時
$ EXPIRE access_times 60

用戶每次調用API裏,都須要判斷access_times是否大於0併發

$ DECR access_times
(integer) 99

當返回數值小於等於0時,即不在容許用戶訪問,直到access_times從新初始化爲大於0的數值,因爲過時時間設置爲60秒,因此初始的訪問次數即爲每分鐘訪問次數,當access_times過時時,則從新初始化之。這樣就實現了每分鐘最大容許訪問XX次的需求。以上邏輯使用僞代碼表示爲網站

if exists key then
	return decr key
else
	set key 100
	expire key 60
	return 100
end

但在實際應用過程當中,發現一個嚴重問題。若是在判斷key過時時,key正處於即將過時狀態(未過時),按照上述邏輯應執行decr key,返回自減後的數值,但若是此時恰好key過時,因爲redis的機制,decr命令會生成一個新的key,並分配值爲0,返回遞減的值爲-1,而且因爲未設置過時時間,key將永不過時,致使程序始終返回負數。 爲解決這個問題,同事想到一個辦法,設置兩個key,分別用來計數和控制過時,用僞代碼表示code

# key1用於計數、key2用於控制過時
if exists key2 then
	return decr key1
else 
	set key1 100
	set key2 1
	expire key2 60
	return 100
end

控制邏輯,判斷表達式返回值小於等於0時,即不可訪問,並間歇輪邏(如100ms),直接從新獲取數值大於0,才經過訪問控制,另外爲了防止併發致使判斷和計數不在一個事務內,整個表達式使用Lua腳本實現事務

# KEYS[1]表示限制次數,由調用程序傳入
local key1 ,key2 = 'access:limit' ,'access:expire'
if redis.call('EXISTS' ,key2) > 0 then
	return redis.call('DECR' ,key1) ;
else 
	redis.call('SET' ,key2 ,1)
	redis.call('EXPIRE' ,key2 ,60)
	redis.call('SET' ,key1 ,KEYS[1])
	return KEYS[1]
end

我的網站同文連接:http://zlikun.com/redis_access_rate/get

相關文章
相關標籤/搜索