我們常常使用滑動視窗實現限流操作,在單機時我們經常放在記憶體中實現,而在做全域性介面限流時,我們除了可以通過查詢介面呼叫記錄外,還可以通過依賴redis實現的滑動視窗進行,比如限制1分鐘可呼叫1000次,一小時可呼叫10000次。
1、乙個固定長度的迴圈佇列
2、每個時間片的時長,可以是按秒、分、時。。。
3、每個時間視窗長度,由多個時間片組成乙個時間視窗,也就是所需的一段時間
4、當前時間的所在時間片的索引
5、初始化迴圈佇列的方法
6、選擇當前時間所在時間片進行更新操作,增加呼叫次數
7、獲取當前時間視窗的呼叫總數(1小時的呼叫總數)
8、獲取當前時間片的呼叫總數(1分鐘的呼叫總數)
9、處理時間視窗內可能存在的跳躍的時間片(更新時用到)
1、定義基本要素:佇列長度、每個時間片長度、視窗長度、當前時間所在時間片索引
/**
* 每個時間片的時長 1min
*/private static final int time_millis_per_slice = 60000;
/** * 視窗長度 多個時間片組成乙個視窗,乙個小時
*/private static final int window_size = 3600000;
/** * 佇列總長度
*/private static volatile int queuesize = 0;
/** * 最後記錄的時間片索引
*/private static volatile int slideindex;
/** * list key
*/private static final string redis_key_for_slide_window = "slide_window:inte***ce_invoke_limit";
/** * 最後一次記錄時間片的索引,儲存到redis,系統重啟時可以拿到
*/private static final string redis_key_for_slide_index = "slide_window:slide_index";
1、定義初始化佇列方法,在新增元素的時候用到
/**
* 功能描述: 初始化佇列
* @author zcj
* @date 2019/7/24
*/private static void initqueue(redistemplateredistemplate)
queuesize = (window_size / time_millis_per_slice) * 2 + 1;
//啟動時從redis獲取最後記錄的時間片
string slideindexstr = redistemplate.opsforvalue().get(redis_key_for_slide_index);
if (stringutils.isnotblank(slideindexstr))
//判斷佇列是否已存在
long size = redistemplate.opsforlist().size(redis_key_for_slide_window);
if (size != null && size > 0)
//佇列未初始化,則初始化佇列,設定佇列長度為時間視窗的2被+1
listlist = new arraylist<>(queuesize);
for (int i = 0; i < queuesize; i++)
redistemplate.opsforlist().rightpushall(redis_key_for_slide_window, list);
}
2、介面呼叫時往當前時間片加1,因為這裡的redistemplate指定了value都是字串,所以入參返回值都做了型別轉換
增加呼叫數的方法:
/**
* 功能描述: 往當前時間所在時間片增加1
* @author zcj
* @date 2019/7/25
* @return 當前時間片的呼叫數
*/public static integer invokeincr(redistemplateredistemplate)
lua指令碼:
--傳入的key
local key = keys[1]
local slidekey = keys[2]
--傳入的引數陣列
local slideindex = tonumber(ar**[1])
local currentindex = tonumber(ar**[2])
local queuesize = tonumber(ar**[3])
local indexvalue = 0
local newvalue = 0
--如果上一次記錄的時間片與當前時間片相同
if(slideindex == currentindex)
then
indexvalue = redis.call("lindex", key, currentindex)
newvalue = indexvalue + 1
redis.call("lset", key, currentindex, newvalue)
else
--如果上次記錄的時間片與當前時間片不同,為當前時間片設定為1
newvalue = 1
redis.call("lset", key, currentindex, newvalue)
--遍歷設定跳躍時間片的值為0 index != currentindex
local index = (slideindex + 1) % queuesize
while(true)
do-- 遍歷到當前時間片即終止
if(index == currentindex)
then
break
endredis.call("lset", key, index, 0)
index = (index + 1) % queuesize
endend--記錄最後的時間片
redis.call("set", slidekey, currentindex)
return tostring(newvalue)
3、獲取當前時間視窗內的介面呼叫總數的方法
/**
* 功能描述: 返回當前時間視窗內呼叫總數
* @param redistemplate redistemplate
* @author zcj
* @date 2019/7/25
* @return 當前時間視窗的呼叫總數
*/public static int getcurrentwindowsum(redistemplateredistemplate)
//通過管道查詢
listresults = redistemplate.executepipelined((rediscallback) redisconnection -> );
//累加視窗內時間片的呼叫總和
int invokecount = 0;
if (!collectionutils.isempty(results))
} return invokecount;
}
4、獲取當前時間所在時間片的呼叫數方法
/**
* @description 返回當前所在時間片的呼叫數量
* @param redistemplate redistemplate
* @author zcj
* @date 2020/8/30 9:46
* @return 當前時間所在時間片的呼叫次數
*/public static int getcurrentslidevalue(redistemplateredistemplate)
總的來說,通過redis實現滑動視窗的原理並不難,主要的問題在lua指令碼中對跳躍時間片的迴圈處理,處理不好會導致redis進入死迴圈,可以在redis中配置lua指令碼執行的超時時間。 Redis Lua指令碼實現復合操作原子化
redis是高效能的key value資料庫,在很大程度克服了memcached這類key value儲存的不足,在部分場景下,是對關聯式資料庫的良好補充。得益於超高效能和豐富的資料結構,redis已成為當前架構設計中的首選key value儲存系統。雖然redis官網上提供了200多個命令,但做程...
REDIS LUA指令碼使用經驗分享
redis lua指令碼出現之前redis是沒有伺服器端運算能力的,主要是用來儲存,用做快取用,運算是在客戶端進行,這樣帶來了很大的頻寬流量。lua出現之後這一問題得到了充分的解決,非常棒!redis lua指令碼api介紹 eval 在redis伺服器端執行lur指令碼 evalsha 在redi...
Redis Lua指令碼編寫快速指南
您應該在系統上安裝redis才能執行本文中的例子。閱讀本文時對照redis命令參考可能會更有幫助。簡而言之 效能提公升。您在redis中執行的大多數任務都涉及許多步驟。您可以使用lua在redis內部進行操作,而不必使用應用程式語言來執行這些步驟。例如,我使用lua指令碼改變儲存在redis的jso...