1. 程式人生 > >Redis中使用Lua指令碼

Redis中使用Lua指令碼

1.關於Lua

Lua [1] 是一個小巧的指令碼語言。是巴西里約熱內盧天主教大學(Pontifical Catholic University of Rio de Janeiro)裡的一個研究小組,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所組成並於1993年開發。 其設計目的是為了嵌入應用程式中,從而為應用程式提供靈活的擴充套件和定製功能。Lua由標準C編寫而成,幾乎在所有作業系統和平臺上都可以編譯,執行。Lua並沒有提供強大的庫,這是由它的定位決定的。所以Lua不適合作為開發獨立應用程式的語言。Lua 有一個同時進行的JIT專案,提供在特定平臺上的即時編譯功能。

簡單來說Lua就是一門指令碼語言,因為其簡潔的語法和小巧的核心,所以被廣泛應用在應用程式的嵌入中,比較著名的就是《WOW》《大話西遊2》等一些遊戲中,用到了這門指令碼語言。

Lua的語法相對來說比較簡單,有興趣的同學可以 Lua教程–菜鳥教程

基於Lua諸多的優點,redis中支援Lua指令碼的使用。

2.redis中使用Lua

   1、減少網路開銷:可以將多個請求通過指令碼的形式一次傳送,減少網路時延和請求次數。

   2、原子性的操作:Redis會將整個指令碼作為一個整體執行,中間不會被其他命令插入。因此在編寫指令碼的過程中無需擔心會出現競態條件,無需使用事務。

   3、程式碼複用:客戶端傳送的腳步會永久存在redis中,這樣,其他客戶端可以複用這一指令碼來完成相同的邏輯。

   4、速度快:見 與其它語言的效能比較, 還有一個 JIT編譯器可以顯著地提高多數任務的效能; 對於那些仍然對效能不滿意的人, 可以把關鍵部分使用C實現, 然後與其整合, 這樣還可以享受其它方面的好處。

   5、可以移植:只要是有ANSI C 編譯器的平臺都可以編譯,你可以看到它可以在幾乎所有的平臺上執行:從 Windows 到Linux,同樣Mac平臺也沒問題, 再到移動平臺、遊戲主機,甚至瀏覽器也可以完美使用 (翻譯成JavaScript).

   6、原始碼小巧:20000行C程式碼,可以編譯進182K的可執行檔案,載入快,執行快。

其實我自己在使用Redis中內嵌Lua指令碼的時候,感覺就是很像SQL型資料庫裡的儲存過程,因為本身Redis是沒有儲存過程這個概念的,不過根據上面提到的 2.原子性操作3.程式碼複用 相信你也和我感受一樣,這不就是和儲存過程一個道理嗎。

2.1 在shell中嘗試使用Lua指令碼

          $ redis-cli --eval path/to/redis.lua KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...
          --eval,告訴redis-cli讀取並執行後面的lua指令碼
           path/to/redis.lua,是lua指令碼的位置
           KEYS[1] KEYS[2],是要操作的鍵,可以指定多個,在lua指令碼中通過KEYS[1], KEYS[2]獲取
           ARGV[1] ARGV[2],引數,在lua指令碼中通過ARGV[1], ARGV[2]獲取。

在這裡插入圖片描述

很容易理解,EVAL命令後引數是script,**這裡指的就是Lua指令碼,**後面的引數,是指令碼中可以取的引數(就像Shell指令碼一樣)

在 Lua 指令碼中,可以使用兩個不同函式來執行 Redis 命令,它們分別是:

redis.call()

redis.pcall()

這兩個函式的唯一區別在於它們使用不同的方式處理執行命令所產生的錯誤。

在這裡插入圖片描述

這個例子> eval "return redis.call('set','foo','bar')" 0就是redis執行了set操作,讓一個String為‘bar’,之所以有return是為了返回操作的結果,就像我們平時操作redis一樣,redis會返回一個結果。

在這裡插入圖片描述 當然也可以直接呼叫函式,而不返回值,那麼看到的是nil

2.2 傳入引數的指令碼

在這裡插入圖片描述 上面這個例子中,eval "return redis.call('set',KEYS[1],'test')" 1 test_key,引數 1是傳入的key的數量,這裡我們傳入了一個key所以引數是1,而Lua指令碼中的KEYS[1] 相當於傳入引數的一個數組。

在這裡插入圖片描述

當然傳入多個引數,我們使用KEYS[index]就可以取相應下標的引數。

除了傳入key(鍵),也可以傳入arg(值),作為引數。 在這裡插入圖片描述

回頭再看一眼這個說明,numkeys是key的數量,後面跟了key陣列,再後面還有arg陣列。也就是說arg陣列可以有值也可以不傳值。

在這裡插入圖片描述

eval "return redis.call('set',KEYS[1],ARGV[2])" 1 test_3_key arg3 arg2 arg3

所以為什麼要指定numkeys也很顯而易見了,為了區分後面的arg,當然在Lua指令碼中,arg也會被作為一個數組,取值的方法也是ARGV[index]

當然,除了我這裡使用到Redis中的String資料型別,其他幾種資料型別的操作也可以直接寫,就和平時使用shell直接操作redis一樣,這裡我就不都展示出來了。

3.Python中使用Lua嵌入redis

其實真實使用過程中,Lua指令碼要比上面的複雜一些。

比如說下面這個指令碼,我們會先判斷傳入的引數ARGV[1],然後再進行插入到不同佇列的操作。

-- 一個簡單地判斷指令碼,內嵌進redis中可以提交執行速度
if (ARGV[1] == 'ok')
then
    return redis.call('lpush',KEYS[1],ARGV[2])
end
if (ARGV[1] == 'error') then
    return redis.call('lpush','ERROR_LIST',ARGV[2])
end

而在Python使用API可以說非常的簡單

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import redis

if __name__ == '__main__':
    r = redis.StrictRedis(host='127.0.0.1', port=6888, db=0)
    lua_script = """
    -- 一個簡單地判斷指令碼,內嵌進redis中可以提交執行速度
    if (ARGV[1] == 'ok')
    then
        return redis.call('lpush',KEYS[1],ARGV[2])
    end
    if (ARGV[1] == 'error') then
        return redis.call('lpush','ERROR_LIST',ARGV[2])
    end
    """
    lua_res = r.register_script(lua_script)
    lua_res(keys=['RIGHT_LIST'],args=['ok','test_arg'])
    lua_res(keys=['ANY_THING'],args=['error','error_arg'])

在這裡插入圖片描述

可以看到執行完Python指令碼以後的結果。當然在實際使用過程中,可能需要更復雜的邏輯,所以需要本身對於Lua指令碼有一定的掌握能力。