居於redis lua指令碼實現的滑動視窗

2021-10-22 23:11:09 字數 4089 閱讀 4522

我們常常使用滑動視窗實現限流操作,在單機時我們經常放在記憶體中實現,而在做全域性介面限流時,我們除了可以通過查詢介面呼叫記錄外,還可以通過依賴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...