這裏使用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