redis使用lua腳本

Lua是什麼:
Lua由標準C編寫而成,幾乎在所有操作系統和平臺上都可以編譯,運行。其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。

爲什麼使用:
(1) 減少網絡開銷: 在Redis操作需求需要向Redis發送5次請求,而使用腳本功能完成同樣的操作只需要發送一個請求即可,減少了網絡往返時延。

(2) 原子操作: Redis會將整個腳本作爲一個整體執行,中間不會被其他命令插入。換句話說在編寫腳本的過程中無需擔心會出現競態條件,也就無需使用事務。事務可以完成的所有功能都可以用腳本來實現。

(3) 複用: 客戶端發送的腳本會永久存儲在Redis中,這就意味着其他客戶端(可以是其他語言開發的項目)可以複用這一腳本而不需要使用代碼完成同樣的邏輯。

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


Redis+Lua:
Redis 2.6.0 版本開始內置的 Lua 解釋器來支持lua腳本,redis客戶端可以使用lua腳本,直接在服務器原子地執行多個redis命令。

怎麼使用:
1.調用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]獲取。
注意:KEYS和ARGV中間的 ',' 兩邊的空格,不能省略。

redis支持大部分Lua標準庫

庫名 說明
Base 提供一些基礎函數
String 提供用於字符串操作的函數
Table 提供用於表操作的函數
Math 提供數學計算函數
Debug 提供用於調試的函數
2.在腳本中調用redis命令
在腳本中可以使用redis.call函數調用Redis命令

redis.call('set', 'foo', 'bar')
local value=redis.call('get', 'foo') --value的值爲bar
redis.call函數的返回值就是Redis命令的執行結果
Redis命令的返回值有5種類型,redis.call函數會將這5種類型的回覆轉換成對應的Lua的數據類型,具體的對應規則如下(空結果比較特殊,其對應Lua的false)

redis返回值類型和Lua數據類型轉換規則

redis返回值類型 Lua數據類型
整數回覆 數字類型
字符串回覆 字符串類型
多行字符串回覆 table類型(數組形式)
狀態回覆 table類型(只有一個ok字段存儲狀態信息)
錯誤回覆 table類型(只有一個err字段存儲錯誤信息)
redis還提供了redis.pcall函數,功能與redis.call相同,唯一的區別是當命令執行出錯時,redis.pcall會記錄錯誤並繼續執行,而redis.call會直接返回錯誤,不會繼續執行。

在腳本中可以使用return語句將值返回給客戶端,如果沒有執行return語句則默認返回nil

Lua數據類型和redis返回值類型轉換規則

Lua數據類型 redis返回值類型
數字類型 整數回覆(Lua的數字類型會被自動轉換成整數)
字符串類型 字符串回覆
table類型(數組形式) 多行字符串回覆
table類型(只有一個ok字段存儲狀態信息) 狀態回覆
table類型(只有一個err字段存儲錯誤信息) 錯誤回覆
3.腳本相關命令
EVAL語法:

eval script numkeys key [key …] arg [arg …]

通過key和arg這兩類參數向腳本傳遞數據,它們的值在腳本中分別使用KEYS和ARGV兩個表類型的全局變量訪問。

script:是lua腳本

numkeys:表示有幾個key,分別是KEYS[1],KEYS[2]…,如果有值,從第numkeys+1個開始就是參數值,剩餘的參數就是ARGV[1],ARGV[2]…

注意:EVAL命令依據參數numkeys來將其後面的所有參數分別存入腳本中KEYS和ARGV兩個table類型的全局變量。當腳本不需要任何參數時,也不能省略這個參數(設爲0)192.168.127.128:6379>eval "return redis.call('set',KEYS[1],ARGV[1])" 1 name liulei
OK

192.168.127.128:6379>get name
"liulei"
4.SCRIPT LOAD SCRIPT

將腳本加入緩存,返回值就是SHA1摘要

5.EVALSHA命令
eval sha1id numkeys key [key …] arg [arg …]

在腳本比較長的情況下,如果每次調用腳本都需要將整個腳本傳給Redis會佔用較多的帶寬。

爲了解決這個問題,Redis提供了EVALSHA命令,允許開發者通過腳本內容的SHA1摘要來執行腳本,該命令的用法和EVAL一樣,只不過是將腳本內容替換成腳本內容的SHA1摘要。

Redis在執行EVAL命令時會計算腳本的SHA1摘要並記錄在腳本緩存中,執行EVALSHA命令時Redis會根據提供的摘要從腳本緩存中查找對應的腳本內容,如果找到了則執行腳本,否則會返回錯誤:"NOSCRIPT No matching script. Please use EVAL."

在程序中使用EVALSHA命令的一般流程如下。

1)先計算腳本的SHA1摘要,並使用EVALSHA命令執行腳本。

2)獲得返回值,如果返回“NOSCRIPT”錯誤則使用EVAL命令重新執行腳本。

雖然這一流程略顯麻煩,但值得慶幸的是很多編程語言的Redis客戶端都會代替開發者完成這一流程。執行EVAL命令時,先嚐試執行EVALSHA命令,如果失敗了纔會執行EVAL命令。

SCRIPTLOAD "lua-script" 將腳本加入緩存,但不執行, 返回:腳本的SHA1摘要
SCRIPT EXISTS lua-script-sha1 判斷腳本是否已被緩存

6.SCRIPT FLUSH(該命令不區分大小寫)
清空腳本緩存,redis將腳本的SHA1摘要加入到腳本緩存後會永久保留,不會刪除,但可以手動使用SCRIPT FLUSH命令情況腳本緩存。

192.168.127.128:6379>script flush
OK

192.168.127.128:6379>SCRIPT FLUSH
OK
7.SCRIPT KILL(該命令不區分大小寫)
強制終止當前腳本的執行。

但是,如果當前執行的腳步對redis的數據進行了寫操作,則SCRIPT KILL命令不會終止腳本的運行,以防止腳本只執行了一部分。腳本中的所有命令,要麼都執行,要麼都不執行。

192.168.127.128:6379>script kill
(error)NOTBUSY No scripts in execution right now

192.168.127.128:6379>SCRIPT KILL
(error)NOTBUSY No scripts in execution right now
//這是當前沒有腳本在執行,所以提示該錯誤

8.lua-time-limit 5000(redis.conf配置文件中)
爲了防止某個腳本執行時間過長導致Redis無法提供服務(比如陷入死循環),Redis提供了lua-time-limit參數限制腳本的最長運行時間,默認爲5秒鐘。當腳本運行時間超過這一限制後,Redis將開始接受其他命令但不會執行(以確保腳本的原子性,因爲此時腳本並沒有被終止),而是會返回“BUSY”錯誤。

安裝和使用Lua腳本
1.安裝Lua類庫環境
yum install -y readline

yum install -y readline-devel
2.下載Lua最新版本並安裝
去官網下載lua,可以直接通過wget下載,地址如下:http://www.lua.org/download.html

[root@lunux~]# wget http://www.lua.org/ftp/lua-5.3.4.tar.gz /root/software/download/lua/
通過ssh SSH Secure File Transfer Client工具,把軟件包上傳到Linux服務器上。

目錄是:/root/software/download/lua/

[root@linux~]# cd ./software/download/lua/

[root@linux lua]# tar zxvf lua-5.3.4.tar.gz
進入到已經解壓的目錄lua-5.3.4,準備安裝文件。

[root@linux lua]# ls

[root@linux lua]# lua-5.3.4 lua-5.3.4.tar.gz

[root@linux lua]# cd lua-5.3.4

[root@linux lua-5.3.4]#
準備安裝環境

使用make linux命令,當前也是需要gcc命令的支持,事先必須安裝

安裝gcc命令:yum install gcc。

[root@linux lua-5.3.4]# make linux
開始安裝lua軟件包

使用make install命令

[root@linux lua-5.3.4]# make install
最後進行測試,進到Linux的命令行,然後輸入lua命令,開始測試。

[root@linux lua-5.3.4]# lua

>print('lua')
lua

按Ctrl+C退出lua命令模式。

lua腳本文件名必須以.lua後綴名,如果在Linux命令行執行lua腳本,直接lua 腳本名稱。

[root@linux lua-5.3.4]# cd /root/application/program/ //執行文件都在這個目錄裏面

[root@linux program]# mkdir luascript //創建luaScript腳本目錄,存放lua腳本文件

[root@linux program]# cd luascript

[root@linux luascript]# lua 01.lua //執行01.lua腳本文件
redis與lua腳本結合使用,如果在lua腳本里使用了 redis.call命令來操作Redis,執行lua腳步如下面:

//redis-cli和lua腳本的路徑可以是相對路徑,也可以是絕對路徑

//以下代碼就是通過絕對地址來執行

//絕對地址:
[root@linux ~]# /root/application/program/redis-tool/redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/02.lua

//相對地址:
//當前目錄
192.168.127.128:6379>pwd
[root@linux redis-tool]/root/application/program/redis-tool/

[root@linux redis-tool]# redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/02.lua
Redis客戶端執行帶有參數的lua腳本,腳本文件的名稱是:03.lua。使用腳本文件執行時,用逗號分隔key和arg參數值

//當前redis 數據庫中只有name和age兩個key,其他數據已經清空。

//當前所在目錄
192.168.127.128:6379>keys *
1)"name"
2)"age"

192.168.127.128:6379>get name
"liulei"

192.168.127.128:6379>get age
"15"

//03.lua腳本代碼如下:

local name=redis.call("get",KEYS[1])

local age=redis.call("get",KEYS[2])

if name=="LLL" then

redis.call("set",KEYS[1],ARGV[1])

redis.call("incr",KEYS[2])
end

//執行改腳本的命令,必須在Linux的命令行,不是在Redis的命令行

[root@linux ~]# /root/application/program/redis-tool/redis-cli -h 192.168.127.128 -p 6379 --eval /root/application/program/luascript/03.lua name age , patrickLiu

//執行腳本命令後
192.168.127.128:6379>keys *
1)"name"
2)"age"

192.168.127.128:6379>get name
"patrickLiu"

192.168.127.128:6379>get age
"16"

//說明帶參數的執行Lua腳本成功

Redis客戶端執行有參數lua,並返回lua的表類型

//04.lua文件的源碼

local b1=redis.call("hgetall",KEYS[1])
return b1

//代碼很簡單,話不多說

//清空當前數據庫
192.168.127.128:6379>flushdb

192.168.127.128:6379>keys *
(empty list or set)

192.168.127.128:6379>hmset myhash name zhangsan sex nan address hebeibaoding school laiyuanyizhong
OK

192.168.127.128:6379>hmget myhash name sex address school
1)"zhangsan"
2)"nan"
3)"hebeibaoding"
4)"laiyuanyizhong"

//我們通過redis客戶端獲取myhash的結果,進入到redis客戶端的當前目錄

[root@linux redis-tool]# redis-cli -h 192.168.127.128 -p 6379 --eval ../luascript/04.lua myhash
1)"name"
2)"zhangsan"
3)"sex"
4)"nan"
5)"address"
6)"hebeibaoding"
7)"school"
8)"laiyuanyizhong"
//成功獲取myhash的列表

多key命令hashtag解決方案:
當如下操作時:
EVAL "redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 key1 key2 value1 value2
出現以下報錯:
ERR 'key1' and 'key2' not in the same slot
通過hashtag進行解決:
EVAL "redis.call('mset',KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 {user}key1 {user}key2 value1 value2
hashtag原理:
在設置了hashtag的情況下,集羣會根據hashtag決定key分配到的slot, 兩個key擁有相同的hashtag:{user}, 它們會被分配到同一個slot,詳細見Redis官方文檔:https://redis.io/topics/cluster-spec?spm=a2c4g.11186623.0.0.335e2e8bvr61sh
————————————————
 
參考:redis使用lua腳本

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章