redis之lua的使用

1.lua的概述

https://www.runoob.com/lua/lua-tutorial.html

2.redis中eval調用lua的語法格式:eval lua-script key-num keys[],arg[]

eval lua-script key-num keys[],arg[]
1.lua-script:這個是我們lua寫的腳本命令
2.key-num:這個是我們後面keys參數的個數
3.keys[]:是我們key-num標明的參數個數明細,當key-num爲0時,這個可以省略
4.arg[]:是我們keys[]對應的value值,當key-num爲0時,這個可以省略

2.1.舉例:eval “return ‘hello gaoxinfu’” 0

參數個數爲0,所以後面keys[],args[] 都沒有

127.0.0.1:6379> eval "return 'hello gaoxinfu'" 0
"hello gaoxinfu"
127.0.0.1:6379> 

3.在lua腳本中調用redis命令(通過call方法):redis.call(command, key [param1, param2…])

redis.call(command, key [param1, param2…])

3.1.舉例:EVAL “redis.call(‘set’,KEYS[1],ARGV[1])” 1 ename frank

127.0.0.1:6379> EVAL "redis.call('set',KEYS[1],ARGV[1])" 1 ename frank
(nil)
127.0.0.1:6379> get ename
"frank"

備註說明

1.大家要注意下 KEYS和ARGV這兩個參數的名字是固定的,必須是大寫,這樣寫
2.KEYS[1],第一個key
3.ARGV[1],標示我們需要使用的value,這個有可能是我們setkey的時候使用,也有可能在腳本調用的時候使用
4.實際上相當於redis當中的set en
127.0.0.1:6379> EVAL "return redis.call('set',KEYS[1],ARGV[1])" 1 sex m
OK
127.0.0.1:6379> get sex
"m"
127.0.0.1:6379> 

4.將lua腳本放到文件中去,然後redis去調用:redis-cli –eval [lua 腳本] [key…]空格,空格[args…]

4.1.案例1

4.1.1.創建name.lua腳本

在這裏插入圖片描述
name.lua 文件內容

redis.call('set','ename','gaoxinfu')
return redis.call('get','ename')                                  

4.1.2.執行命令驗證:/usr/local/bin/redis-cli --eval name.lua

同樣最後面的0是參數個數

localhost:demo gaoxinfu$ /usr/local/bin/redis-cli --eval name.lua
"gaoxinfu"

4.2.案例2

4.2.1.創建ip-limit.lua腳本

-- ip_limit.lua
-- IP 限流,對某個IP 頻率進行限制 ,6 秒鐘訪問 10 次
其中KEYS[1]是ip
ARGV[1]:爲設置ip的有效時間
ARGV[2]:爲設置限制的訪問次數
local num=redis.call('incr',KEYS[1])
  if tonumber(num)==1 then
        redis.call('expire',KEYS[1],ARGV[1])
        return 1
  elseif tonumber(num)>tonumber(ARGV[2]) then
        return 0
  else
        return 1
  end      

4.2.2.執行命令

比如 6秒鐘之內只能訪問2次,很顯然,前面兩次成功了,後面就不成功了

localhost:demo gaoxinfu$ /usr/local/bin/redis-cli  --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 1
localhost:demo gaoxinfu$ /usr/local/bin/redis-cli  --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 1
localhost:demo gaoxinfu$ /usr/local/bin/redis-cli  --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 0
localhost:demo gaoxinfu$ /usr/local/bin/redis-cli  --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 0
localhost:demo gaoxinfu$ /usr/local/bin/redis-cli  --eval ip-limit.lua 127.0.0.1 , 6 2
(integer) 0
localhost:demo gaoxinfu$ 

5.將lua腳本緩存到redis服務端中

5.1.爲什麼要將lua腳本緩存到redis服務端

1.在執行redis的命令的時候,因爲lua腳本是命令或者文件的形式,每次都需要將lua腳本命令傳遞給redis服務端,
  每一次調用,會產生比較大的網絡開銷;

5.2.如何緩存lua腳本:SCRIPT load “return ‘hello world’”

5.2.1.案例1

1.爲了解決這個交互導致的網絡開銷問題,redis提供了一個evalsha命令;
127.0.0.1:6379> SCRIPT load "return 'hello world'"
"5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
127.0.0.1:6379> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0
"hello world"

5.3.如何調用緩存的lua腳本:EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0

5.3.1.案例1

1.爲了解決這個交互導致的網絡開銷問題,redis提供了一個evalsha命令;
127.0.0.1:6379> SCRIPT load "return 'hello world'"
"5332031c6b470dc5a0dd9b4bf2030dea6d65de91"
127.0.0.1:6379> EVALSHA 5332031c6b470dc5a0dd9b4bf2030dea6d65de91 0
"hello world"

5.2.2.案例2

同樣上面我們的4.2.案例2也可以 先緩存,然後調用
這裏需要注意下,之前在文件中,現在弄成一行,需要注意是否可以用;分號

127.0.0.1:6379> SCRIPT LOAD "local num=redis.call('incr',KEYS[1]);if tonumber(num)==1 then redis.call('expire',KEYS[1],ARGV[1]) return 1 elseif tonumber(num)>tonumber(ARGV[2]) then return 0 else return 1 end"
"766696820dc636809147aba60dcab8c488860ff5"
127.0.0.1:6379>

這裏調用的時候要注意下,格式,766696820dc636809147aba60dcab8c488860ff5後面第一個是參數key個數,後面的就是1個key2個值

127.0.0.1:6379> EVALSHA 766696820dc636809147aba60dcab8c488860ff5 1 127.0.0.1 6 2
(integer) 0
127.0.0.1:6379> 

6.使用lua實現redis的自乘(redis有自增自減,但是無乘)

將key[1]的值乘以ARGV[1]倍數

local curVal = redis.call("get", KEYS[1]); 
if curVal == false 
   then curVal = 0 
   else curVal = tonumber(curVal) 
end;
curVal = curVal * tonumber(ARGV[1]); 
redis.call("set", KEYS[1], curVal);
return curVal;

執行驗證,上面的lua腳本去掉換行

127.0.0.1:6379> SCRIPT LOAD 'local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end;curVal = curVal * tonumber(ARGV[1]);redis.call("set",KEYS[1], curVal);return curVal;'
"3ffdd17da7e77aa3249ac0d0b05d6e8fc348e8f3"
127.0.0.1:6379> 

設置年齡10歲,然後通過調用lua緩存命令乘以3

127.0.0.1:6379> SCRIPT LOAD 'local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end;curVal = curVal * tonumber(ARGV[1]);redis.call("set",KEYS[1], curVal);return curVal;'
"3ffdd17da7e77aa3249ac0d0b05d6e8fc348e8f3"
127.0.0.1:6379> set gaoxinfu_age 10
OK
127.0.0.1:6379> EVALSHA 3ffdd17da7e77aa3249ac0d0b05d6e8fc348e8f3 1 gaoxinfu_age 3
(integer) 30
127.0.0.1:6379> 

7.lua腳本超時問題

7.1.lua腳本默認超時時間:默認5s

1.當腳本運行時間超過這一限制後,Redis 將開始接受其他命令但不會執行(以確保腳本的原子性,因爲此時腳本並沒有被終止),而是會返回“BUSY”錯誤。
localhost:redis-5.0.5 gaoxinfu$ pwd
/Users/gaoxinfu/using/redis-5.0.5
localhost:redis-5.0.5 gaoxinfu$ ls -la|grep redis.conf
-rw-r--r--@   1 gaoxinfu  staff   61798  3 17 21:10 redis.conf
-rw-r--r--@   1 gaoxinfu  staff   61797  3 17 21:09 redis.conf-bak

在這裏插入圖片描述

7.2.如何手動停止lua腳本命令的執行:script kill

7.2.1.案例1:殺死正在執行lua腳本

127.0.0.1:6379> EVAL "while(true) do end" 0
127.0.0.1:6379> SCRIPT KILL
OK
127.0.0.1:6379> 
127.0.0.1:6379> EVAL "while(true) do end" 0
(error) ERR Error running script (call to f_eec1f08dafc6bfdf256e3820d971514a3a24267e): @user_script:1: Script killed by user with SCRIPT KILL... 
(10.16s)
127.0.0.1:6379> 

在這裏插入圖片描述

7.2.2.案例2:

執行下面的命令,第一個set是成功的,第二個進入了死循環

127.0.0.1:6379> eval "redis.call('set','gupao','666') while true do end" 0

我們使用script kill去啥子執行lua的腳本進程,但是發現沒殺成功,原因是上面的命令是兩步,第一步是執行成功了,但是第二步
處理失敗了,只要有一步執行成功了,是無法殺死的lua進程的

127.0.0.1:6379> SCRIPT KILL
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.
127.0.0.1:6379> 

在這裏插入圖片描述

7.3.針對上面script kill無法殺死lua腳本進程的方案:shutdown nosave

shutdown nosave 和 shutdown 的區別在於 shutdown nosave 不會進行持久化操作,意味着發生在上一次快照後的數據庫修改都會丟失。
實際上就是把redis服務停掉,shutdown nosave不會進行持久化操作,一般不使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章