Redis入門(6) - Lua腳本

来源:https://www.cnblogs.com/zhixin9001/archive/2020/06/01/13027420.html
-Advertisement-
Play Games

Lua基本語法 表類型 函數 Redis執行腳本 KEYS與ARGV 沙盒與隨機數 腳本相關命令 原子性和執行時間 Lua是一種高效的輕量級腳本語言,能夠方便地嵌入到其他語言中使用。在Redis中,藉助Lua腳本可以自定義擴展命令。 Lua基本語法 數據類型 空(nil),沒有賦值的變數或表的欄位值 ...


  • Lua基本語法
  • 表類型
  • 函數
  • Redis執行腳本
  • KEYS與ARGV
  • 沙盒與隨機數
  • 腳本相關命令
  • 原子性和執行時間

Lua是一種高效的輕量級腳本語言,能夠方便地嵌入到其他語言中使用。在Redis中,藉助Lua腳本可以自定義擴展命令。

Lua基本語法

數據類型

  • 空(nil),沒有賦值的變數或表的欄位值都是nil
  • 布爾(boolean)
  • 數字(number),整數或浮點數
  • 字元串(string),字元串可以用單引號或雙引號表示,可以包含轉義字元如\n \r等
  • 表(table),表類型是Lua語言中唯一的數據結構,既可以當數組又可以當字典,十分靈活
  • 函數(function),函數在Lua中是一等值(first-class-value),可以存儲在變數中、作為函數的參數或返回結果。

變數

Lua的變數分為全局變數和局部變數,全局變數無需聲明就可以直接使用,預設值是nil。
全局變數:

a=1 -- 為全局變數a賦值
print(b) -- 無需聲明即可使用,預設值是nil

局部變數:

local c -- 聲明一個局部變數c,預設值是nil
local d=1 -- 聲明一個局部變數d並賦值為1
local e,f -- 可以同時聲明多個局部變數

但在Redis中,為了防止腳本之間相互影響,只允許使用局部變數。

賦值

Lua支持多重賦值,如:

local a,b=1,2 --a的值是1,b的值是2
local c,d=1,2,3 --c的值是1,d的值是2,3被捨棄了
local e,f =1 --e的值是1,f的值是nil

操作符

  1. 數學操作符,包括常見的+ - * \ %(取模) -(一元操作符,取負)和冪運算符號^。

  2. 比較操作符,包括== ~=(不等於) > < >= <=。
    比較操作符不會對兩邊的操作數進行自動類型轉換:

pring(1=='1') --結果為false
print({'a'}=={'a'}) -false,表類型比較的是二者的引用
  1. 邏輯操作符
    包括下麵三個:
    not,根據操作數的真和假相應地返回false和true;
    and,a and b中如果a是真則返回b,否則返回a;
    or,a or b中,如果a是真則返回a,否則返回b。
    這些根據操作符短路的原理可以推斷出。
print(1 and 5)  --5
print(1 or 5)  --1
print(not 0)  --false
print('' or 1)  --''

只要操作數不是nil或false,邏輯操作符就認為操作數是真,否則是假。而且即使是0或空字元串也被當作真,所以上面的代碼中print(not 0)的結果為false,print('' or 1)的結果為''。

  1. 連接操作符
    Lua中的連接操作符為'..',用來連接兩個字元串。

  2. 取長度操作符

 print(#'hello')  --5

if語句

Lua中if語句的格式為

if condition then
    ...
else if condition then
    ...
else
    ...
end

由於Lua中只有nil和false才認為是假,這裡也需要註意避坑,比如Redis中EXISTS命令返回1和0分別表示存在或不存在,類似下麵的寫法if條件將始終為true:

if redis.call('EXISTS','key1') then
    ...

所以需要寫成:

if redis.call('EXISTS','key1')==1 then
    ...

迴圈語句

Lua中的迴圈語句有四種形式:

while condition do
    ...
end
repeat
    ...
until condition
for i=初值, 終值, 步長 do
    ...
end

其中步長為1時可以省略。

for 變數1,變數2,...,變數N in 迭代器 do
    ...
end

表類型

表是Lua中唯一的數據結構,可以理解為關聯數組,除nil之外的任何類型的值都可以作為表的索引。

表的定義和賦值

-- 表的定義
a={} --將變數a賦值為一個空表
-- 表的賦值
a['field']='value' --將field欄位賦值為value
print(a.field) --a['field']可以簡化為a.field

-- 定義的同時賦值
b={
    name='bom',
    age=7
}
-- 取值
print(b['age'])
print(b.age)

當索引為整數的時候表和傳統的數組一樣,但需要註意的是Lua的索引是從1開始的。

a={}
a[1]='bob'
a[2]='daffy'

上面的定義和賦值的過程可以直接簡化為:

a={'bob','daffy'}

取值:

print(a[1])

表的遍歷

之前介紹的這種類型的for迴圈可以用於表的遍歷:

for 變數1,變數2,...,變數N in 迭代器 do
    ...
end
a={'bob','daffy'}

for index,value in ipairs(a) do
    print(index) 
    print(value) 
end

ipairs用於數組的遍歷,index和value分別為元素的索引和值,變數名不是必須為index和value,可以自定義。
或者:

for i=1, #a do
    print(i)
    print(a[i])
end

通過#a可以去到數組a的長度。

對於非數組的遍歷,可以使用pairs

b={
    name='bom',
    age=7
}

for key,value in pairs(b) do
    print(key) 
    print(value) 
end

變數名不是必須為key和value,可以自定義。

函數

函數的定義為:

function(參數列表)
    ...
end

實際使用中可以將其賦值給一個局部變數,如:

local square=function(num)
    return num * num
end

還可以簡化為:

local function square(num)
    return num * num
end

如果實參的個數小於形參的個數,則沒有匹配到的形參的值為nil;如果實參的個數大於形參的個數,則多出的實參會被忽略。如果希望參數可變,可以用...表示形參。

在腳本中調用Redis命令

在腳本中使用redis.call可以調用Redis命令

redis.call('SET','foo','bar')

redis.call的返回值就是Redis命令的執行結果。針對Redis的不同返回類型,redis.call會將其轉換為對應的Lua的數據類型,兩者的對應關係為:

Redis返回類型 Lua數據類型
整數回覆 數字類型
字元串回覆 字元串類型
多行字元串回覆 表類型(數組形式)
狀態回覆 表類型(只有一個ok欄位存儲狀態信息)
錯誤回覆 表類型(只有一個err欄位存儲錯誤信息)

Redis的nil回覆會被轉換為false。

Lua腳本執行完畢後可以通過return將結果返回給Redis客戶端,這是又會將Lua的數據類型轉換為Redis的返回類型,過程與上面的表格相反。

redis.pcall函數與redis.call的功能相同,但redis.pcall在執行出錯時會記錄錯誤並繼續執行,而redis.call則會中斷執行。

Redis執行腳本

EVAL

在Redis客戶端通過EVAL命令可以調用腳本,其格式為:

EVAL 腳本內容 key參數的數量 [key...] [arg...]

例如用腳本來設置鍵的值,就是這樣的:

EVAL "return redis.call('SET',KEYS[1],ARGV[1])" 1 foo bar

通過key和arg這兩類參數向腳本傳遞數據,它們的值可以在腳本中分別使用KEYS和ARGV兩個表類型的全局變數訪問。key參數的數量是必須指定的,沒有key參數時必須設為0,EVAL會依據這個數值將傳入的參數分別存入KEYS和ARGV兩個表類型的全局變數。

EVALSHA

如果腳本比較長,每次調用腳本都將整個腳本傳給Redis會占用較多的帶寬。而使用EVALSHA命令可以腳本內容的SHA1摘要來執行腳本,該命令的用法和EVAL一樣,只不過是將腳本內容替換成腳本內容的SHA1摘要。Redis在執行EVAL命令時會計算腳本的SHA1摘要並記錄在腳本緩存中,執行EVALSHA命令時Redis會根據提供的摘要從腳本緩存中查找對應的腳本內容,如果找到了則執行腳本,否則會返回錯誤:“NOSCRIPT No matching script. Please use EVAL.”。

具體使用時,可以先計算腳本的SHA1摘要,並用EVALSHA命令執行腳本,如果返回NOSCRIPT錯誤,就用EVAL重新執行腳本。

KEYS與ARGV

前面提到過向腳本傳遞的參數分為KEYS和ARGV兩類,前者表示要操作的鍵名,後者表示非鍵名參數。但這一要求並不輸強制的,比如設置鍵值的腳本:

EVAL "return redis.call('SET',KEYS[1],ARGV[1])" 1 foo bar

也可以寫成:

EVAL "return redis.call('SET',ARGV[1],ARGV[2])" 0 foo bar

雖然規則不是強制的,但不遵守這樣的規則可能會為後續帶來不必要的麻煩。比如Redis 3.0之後支持集群功能,開啟集群後會將鍵發佈到不同的節點上,所以在腳本執行前就需要知道腳本會操作哪些鍵以便找到對應的節點,而如果腳本中的鍵名沒有使用KEYS參數傳遞則無法相容集群。

沙盒與隨機數

Redis限制腳本只能在沙盒中運行,只允許腳本對Redis的數據進行處理,而禁止使用Lua標準庫中與文件或系統調用相關的函數,Redis還通過禁用腳本的全局變數的方式保證每個腳本都是相對隔離、不會互相干擾的。

使用沙盒一方面可保證伺服器的安全性,還可確保可以重現(腳本執行的結果只和腳本本身以及傳遞的參數有關)。

Redis還替換了math.random和math.randomseed函數,使得每次執行腳本時生成的隨機數列都相同。如果希望獲得不同的隨機數序列,可以採用提前生成隨機數並通過參數傳遞給腳本,或者提前生成隨機數種子的方式。

集合類型和散列類型的欄位是無序的,所以SMEMBERS和HKEYS命令原本會返回隨機結果,但在腳本中調用這些命令時,Redis會對結果按照字典順序排序。

對於會產生隨機結果但無法排序的命令,比如SPOP,SRANDMEMBER, RANDOMKEY, TIME,Redis會在這類命令執行後將該腳本狀態標記為lua_random_dirty,此後只允許調用只讀命令,不允許修改資料庫的值,否則會返回錯誤:“Write commands not allowed after non deterministic commands.”

腳本相關命令

SCRIPT LOAD

EVAL命令會執行腳本,並將腳本計算SHA1、加入到腳本緩存中,如果只是希望緩存腳本而不執行,就可以使用SCRIPT LOAD,返回值是腳本的SHA1結果:

> SCRIPT LOAD "return redis.call('SET',KEYS[1],ARGV[1])"
"cf63a54c34e159e75e5a3fe4794bb2ea636ee005"

SCRIPT EXISTS

通過SHA1查詢某個腳本是否被緩存,可以查詢多個SHA1。參數必須是完整的SHA1,而不能像docker只輸前幾位。返回結果1表示存在。

SCRIPT FLUSH

Redis將腳本加入到緩存後會永久保留,如果要清空緩存可以使用SCRIPT FLUSH。

SCRIPT KILL

用於終止正在執行的腳本

原子性和執行時間

Redis的腳本執行是原子的,腳本執行期間其他命令不會被執行,必須等待上一個腳本執行完成。

但為了防止某個腳本執行時間過長導致Redis無法提供服務(比如陷入死迴圈),Redis提供了lua-time-limit參數限制腳本的最長運行時間,預設為5秒鐘。當腳本運行時間超過這一限制後,Redis將開始接受其他命令,但為了確保腳本的原子性,新的腳本仍然不會執行,而是會返回“BUSY”錯誤。

可以打開兩個redis-cli實例A和B來驗證,首先在A執行一個死迴圈腳本:

EVAL "while true do end" 0

這時在實例B執行GET key1會返回:
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

如果按照錯誤提示,在B執行SCRIPT KILL,這時在實例A的腳本會被終止,並返回:
(error) ERR Error running script (call to f_694a5fe1ddb97a4c6a1bf299d9537c7d3d0f84e7): @user_script:1: Script killed by user with SCRIPT KILL...

但如果A已經對Redis的數據做了修改,則SCRIPT KILL無法將其終止,A執行:

EVAL "redis.call('SET','foo','bar') while true do end" 0

如果在B嘗試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.

這時就只能通過SHUTDOWN NOSAVE命令強行終止Redis。SHUTDOWN NOSAVE與SHUTDOWN命令的區別在於,SHUTDOWN NOSAVE將不會進行持久化操作,所有發生在上一次快照後的資料庫修改都會丟失!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 在開發編程中,我們經常會遇到功能非常相似的功能模塊,只是他們的處理的數據不一樣,所以我們會分別採用多個方法來處理不同的數據類型。但是這個時候,我們就會想一個問題,有沒有辦法實現利用同一個方法來傳遞不同種類型的參數呢? 這個時候,泛型也就因運而生,專門來解決這個問題的。 泛型是在C 2.0就推出 ...
  • 最近一個朋友有個關於素數的小東西要寫一下,素數是什麼呢?除了1和他本身不能被其他數整除,那麼這個數就是素數,1除外哦。我們知道概念那就很簡單了,直接代碼擼起。 ...
  • 一:背景 1. 講故事 曾今在項目中發現有同事自定義結構體的時候,居然沒有重寫Equals方法,比如下麵這段代碼: static void Main(string[] args) { var list = Enumerable.Range(0, 1000).Select(m => new Point ...
  • 一 歸檔命令:tar(既可以歸檔,又可以歸檔後壓縮文件) 歸檔:tar -cvf 文件名.tar *.txt 解檔:tar -xvf 文件名.tar -C 指定文件 不寫預設當前文件(同下) 二 壓縮命令: 1 gzip 壓縮:gzip -r test.tar test.tar.gz 解壓:gzip ...
  • 大家好,我叫良許,本來是一名寫代碼的博主,萬萬沒想到,我居然我會放下臉皮在B站當UP主,還全程露臉不戴口罩! 到目前為止,已經擁有 1.1 萬粉絲啦~ 我是從去年12月中旬開始正式進駐B站,但是,因為疫情的影響,當中有 2 個月沒有更新視頻。到目前,已經更新了13個視頻,總播放量 31 萬,8388 ...
  • 這裡簡單總結一下Linux平臺Zabbix Agent的安裝配置,實驗測試的Zabbix版本比較老了(Zabbix 3.0.9),不過版本雖然有點老舊,但是新舊版本的安裝步驟、流程基本差別不大。這裡的總結僅僅當成一個操作手冊,後續會更新或添加部分內容。 Linux版本眾多,下麵文檔僅僅在RHEL、C... ...
  • 1、NAT模式拓撲及工作原理在一組伺服器前有一個調度器,它們是通過Switch/HUB相連接的。這些伺服器提供相同的網路服務、相同的內容,即不管請求被髮送到哪一臺伺服器,執行結果是一樣的。服務的內容可以複製到每台伺服器的本地硬碟上,可以通過網路文件系統(如NFS)共用,也 可以通過一個分散式文件系統... ...
  • 近年來,片上存儲器發展迅速,根據國際半導體技術路線圖(ITRS),隨著超深亞微米製造工藝的成熟和納米工藝的發展,晶體管特征尺寸進一步縮小,半導體存儲器在片上存儲器上所占的面積比例也越來越高。接下來宇芯電子介紹SRAM的工作原理以及工作過程。 SRAM 寫操作。寫操作就是把數據寫入指定的SRAM 存儲 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...