redis+lua指令碼實現介面限流

送东阳马生序發表於2024-04-19

寫在前面

在多執行緒的情況下對一個介面進行訪問,如果訪問次數過大,且沒有快取存在的情況下大量的請求打到資料庫可能會存在資料庫當機,從而造成服務的不可用性。往往我們需要對其進行限流操作用來保證服務的高可用性,以下介紹下redis限流如何使用。

lua指令碼

Lua 是一種輕量小巧的指令碼語言,用標準C語言編寫並以原始碼形式開放, 其設計目的是為了嵌入應用程式中,從而為應用程式提供靈活的擴充套件和定製功能。Lua 本身並沒有提供對於原子性的直接支援,它只是一種指令碼語言,通常是嵌入到其他宿主程式中執行,比如Redis。 在Redis中,執行Lua指令碼的原子性是指:整個Lua指令碼在執行期間,不會被其他客戶端的命令打斷。

特性

  • 輕量級:它用標準C語言編寫並以原始碼形式開放,編譯後僅僅一百餘K,可以很方便的嵌入別的程式裡。
  • 可擴充套件:Lua提供了非常易於使用的擴充套件介面和機制:由宿主語言(通常是C或C++)提供這些功能,Lua可以使用它們,就像是本來就內建的功能一樣。

lua語法教程

redis使用Lua指令碼

Redis 指令碼使用 Lua 直譯器來執行指令碼。 Redis 2.6 版本透過內嵌支援 Lua 環境。執行指令碼的常用命令為 EVAL。redis Eval 命令基本語法如下:

redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...] 

引數說明:

  • script: 引數是一段 Lua 5.1 指令碼程式。指令碼不必(也不應該)定義為一個 Lua 函式。
  • numkeys: 用於指定鍵名引數的個數。
  • key [key ...]: 從 EVAL 的第三個引數開始算起,表示在指令碼中所用到的那些 Redis 鍵(key),這些鍵名引數可以在 Lua 中透過全域性變數 KEYS 陣列,用 1 為基址的形式訪問( KEYS[1] , KEYS[2] ,以此類推)。
  • arg [arg ...]: 附加引數,在 Lua 中透過全域性變數 ARGV 陣列訪問,訪問的形式和 KEYS 變數類似( ARGV[1] 、 ARGV[2] ,諸如此類)。

Jedis實現介面限流

首先,我們定義一個lua指令碼:

local key = KEYS[1];

local times = ARGV[1];

local expire = ARGV[2];

local afterval = redis.call('incr',key);
if afterval ==1 then
    redis.call('expire',key,tonumber(expire) )
    return 1;
end;

if afterval > tonumber(times) then
    return 0;
end

return 1;

這個指令碼首先定義了三個成員變數用來獲取方法中傳入的值,redis.call()方法是redis的命令指令碼執行,即redis執行incr操作,對key中儲存的key值加1操作,如果afterval值等於1時,執行redis的expire,設定key的過期時間,tonumber是將引數值轉換為數值,返回,如果加一後的值大於我們傳入的規定值時,返回0,進行限流。

java程式碼實現限流

    public boolean acquire(String limitKey, int limit, int expire) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setResultType(Long.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("rateLimiter.lua")));
        Long result = (Long) jedis.eval(redisScript.getScriptAsString(), 1, limitKey, String.valueOf(limit), String.valueOf(expire));
        if (result == 0){
            return false;
        }else {
        return true;
        }
    }

controller層呼叫

     if (isAcquire.acquire("myKey",10,60)){
          // 介面放行
           return "success";
       }else {
         // 介面拒絕
           return "err";
       }

這個方法是傳入一個你的key,這個key下每一分鐘只能請求10次,如果超出10次,進行限流操作,等到上次請求介面的時間超出1分鐘後才可以進行放行操作。

END

相關文章