亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

directory search
Cluster cluster addslots(集群 槽位) cluster count failure reports(集群計數(shù)失敗報告) cluster countkeysinslot cluster delslots cluster failover cluster forget cluster getkeysinslot cluster info cluster keyslot(集群鍵槽) cluster meet cluster nodes(集群節(jié)點) cluster replicate(集群復制) cluster reset(集群重置) cluster saveconfig cluster set config epoch cluster setslot cluster slaves cluster slots readonly readwrite Connection auth echo ping quit select swapdb Geo geoadd geodist geohash geopos georadius georadiusbymember Hashes hdel hexists hget hgetall hincrby hincrbyfloat hkeys hlen hmget hmset hscan hset hsetnx hstrlen hvals HyperLogLog pfadd pfcount pfmerge Keys del dump exists expire expireat keys migrate move object persist pexpire pexpireat pttl randomkey rename renamenx restore scan sort touch ttl type unlink wait Lists blpop brpop brpoplpush lindex linsert llen lpop lpush lpushx lrange lrem lset ltrim rpop rpoplpush rpush rpushx Pub/Sub psubscribe publish pubsub punsubscribe subscribe unsubscribe Scripting eval evalsha script debug script exists script flush script kill script load Server bgrewriteaof bgsave client getname client kill client list client pause client reply client setname command command count command getkeys command info config get config resetstat config rewrite config set dbsize debug object debug segfault flushall flushdb info lastsave monitor role save shutdown slaveof slowlog time Sets sadd scard sdiff sdiffstore sinter sinterstore sismember smembers smove spop srandmember srem sscan sunion sunionstore Sorted Sets zadd zcard zcount zincrby zinterstore zlexcount zrange zrangebylex zrangebyscore zrank zrem zremrangebylex zremrangebyrank zremrangebyscore zrevrange zrevrangebylex zrevrangebyscore zrevrank zscan zscore zunionstore Strings append bitcount bitfield bitop bitpos decr decrby get getbit getrange getset incr incrby incrbyfloat mget mset msetnx psetex set setbit setex setnx setrange strlen Transactions discard exec multi unwatch watch
characters

EVAL script numkeys key [key ...] arg [arg ...]

自2.6.0起可用。

時間復雜度:取決于執(zhí)行的腳本。

EVAL簡介

EVAL 和 EVALSHA 用于從版本2.6.0開始使用內(nèi)置在 Redis 中的 Lua 解釋器來評估腳本。

EVAL 的第一個參數(shù)是一個 Lua 5.1 腳本。腳本不需要定義一個 Lua 函數(shù)(不應該)。這只是一個 Lua 程序,它將在 Redis 服務器的上下文中運行。

EVAL 的第二個參數(shù)是腳本后面的參數(shù)個數(shù)(從第三個參數(shù)開始)代表 Redis 鍵名稱。參數(shù)可以通過 Lua 中使用來訪問KEYS全局變量在基于一個陣列(這樣的形式KEYS[1],KEYS[2]...)。

所有其他參數(shù)不應該代表鍵名稱,可以通過 Lua 的使用來訪問ARGV全局變量,非常相似與什么發(fā)生了鍵(所以ARGV[1],ARGV[2]...)。

以下示例應該闡明上述內(nèi)容:

> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second1) "key1"2) "key2"3) "first"4) "second"

注意:正如你所看到的,Lua 數(shù)組是以 Redis 多重批量回復的形式返回的,這是一種 Redis 返回類型,客戶端庫可能會將其轉換為編程語言中的 Array 類型。

可以使用兩個不同的 Lua 函數(shù)從 Lua 腳本調(diào)用 Redis 命令:

  • redis.call()

  • redis.pcall()

redis.call()redis.pcall()類似,唯一的區(qū)別是,如果 Redis 命令調(diào)用會導致錯誤,redis.call()將引發(fā) Lua 錯誤,反過來會強制 EVAL 向命令調(diào)用方返回錯誤,同時redis.pcall將陷阱錯誤并返回 Lua 表代表錯誤。

函數(shù)redis.call()redis.pcall()函數(shù)的參數(shù)都是格式良好的 Redis 命令的所有參數(shù):

> eval "return redis.call('set','foo','bar')" 0OK

上面的腳本將鍵foo設置為字符串bar。然而,它違反了 EVAL 命令的語義,因為腳本使用的所有鍵都應該使用KEYS數(shù)組傳遞:

> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK

在執(zhí)行之前,必須分析所有 Redis 命令,以確定命令將在哪些鍵上運行。為了使 EVAL 成為真,密鑰必須明確傳遞。這在很多方面都很有用,但特別要確保 Redis 群集可以將您的請求轉發(fā)到適當?shù)娜杭?jié)點。

請注意,此規(guī)則未實施,以便為用戶提供濫用 Redis 單實例配置的機會,代價是編寫與 Redis 群集不兼容的腳本。

Lua 腳本可以使用一組轉換規(guī)則返回從 Lua 類型轉換為 Redis 協(xié)議的值。

Lua 和 Redis 數(shù)據(jù)類型之間的轉換

當 Lua 使用call()或調(diào)用 Redis 命令時,Redis 返回值將轉換為 Lua 數(shù)據(jù)類型pcall()。同樣,在調(diào)用 Redis 命令和 Lua 腳本返回值時,Lua 數(shù)據(jù)類型將轉換為 Redis 協(xié)議,以便腳本可以控制 EVAL 返回給客戶端的內(nèi)容。

數(shù)據(jù)類型之間的這種轉換的設計方式是,如果將 Redis 類型轉換為 Lua 類型,然后將結果轉換回 Redis 類型,則結果與初始值相同。

換句話說,Lua 和 Redis 類型之間存在一對一的轉換。下表顯示了所有轉換規(guī)則:

Redis 轉換為 Lua 轉換表。

  • Redis 整數(shù)回復 -> Lua 編號

  • Redis 批量回復 -> Lua 字符串

  • Redis 多批量回復 -> Lua 表(可能嵌套有其他 Redis 數(shù)據(jù)類型)

  • Redis 狀態(tài)回復 -> ok包含狀態(tài)的單個字段的 Lua 表

  • Redis 錯誤回復 -> err包含錯誤的單個字段的 Lua 表

  • Redis Nil 批量回復和 Nil 多批量回復 -> Lua false 布爾類型

Lua Redis 轉換表。

  • Lua 數(shù)字 -> Redis 整數(shù)回復(將數(shù)字轉換為整數(shù))

  • Lua 字符串 -> Redis 批量回復

  • Lua 表(數(shù)組) -> Redis 多批量回復(如果有的話,截斷到 Lua 數(shù)組中的第一個零)

  • 具有單個ok字段的 Lua 表- > Redis 狀態(tài)回復

  • 具有單個err字段的 Lua 表 -> Redis 錯誤回復

  • Lua boolean 錯誤  -> Redis 零批量回復

還有一個額外的 Lua-to-Redis 轉換規(guī)則沒有對應的 Redis 到 Lua 轉換規(guī)則:

  • Lua boolean true  -> 值為1的 Redis 整數(shù)回復。還有兩個重要的規(guī)則需要注意:

  • Lua 有一個數(shù)字類型,Lua 數(shù)字。整數(shù)和浮點數(shù)之間沒有區(qū)別。所以我們總是將 Lua 數(shù)字轉換為整數(shù)回復,如果有的話刪除數(shù)字的小數(shù)部分。如果你想從 Lua 返回一個浮點數(shù),你應該像字符串一樣返回它,就像 Redis 本身一樣(參見例如 ZSCORE 命令)。

  • 有沒有簡單的方法有 lua 陣內(nèi)尼爾斯,這是的 Lua 表語義的結果,所以當 Redis 的一個 Lua 陣列轉換成 Redis 的協(xié)議如果遇到零的轉換停止。

以下是幾個轉換示例:

> eval "return 10" 0(integer) 10> eval "return {1,2,{3,'Hello World!'}}" 01) (integer) 12) (integer) 23) 1) (integer) 3   2) "Hello World!"> eval "return redis.call('get','foo')" 0"bar"

最后一個例子展示它是如何可能獲得的確切的返回值redis.call()或者redis.pcall()從 Lua,如果命令直接調(diào)用將被退回。

在下面的例子中,我們可以看到如何處理帶有 nils 的浮點數(shù)組和數(shù)組:

> eval "return {1,2,3.3333,'foo',nil,'bar'}" 01) (integer) 12) (integer) 23) (integer) 34) "foo"

正如你可以看到3.333轉化成3,永遠不會返回的 bar 字符串,是因為以前是零。

Helper 函數(shù)返回 Redis 類型

有兩個幫助函數(shù)可以從 Lua 返回 Redis 類型。

  • redis.error_reply(error_string)返回錯誤回復。這個函數(shù)只返回一個字段表,其中err字段為你設置的字段。

  • redis.status_reply(status_string)返回狀態(tài)回復。這個函數(shù)只返回一個字段表,其中ok字段為你設置的字段。

使用輔助函數(shù)或直接以指定格式返回表沒有區(qū)別,所以以下兩種形式是等價的:

return {err="My Error"}return redis.error_reply("My Error")

腳本的原子性

Redis 使用相同的 Lua 解釋器來運行所有命令。另外,Redis 保證以原子方式執(zhí)行腳本:執(zhí)行腳本時不會執(zhí)行其他腳本或 Redis 命令。這種語義與 MULTI / EXEC 相似。從所有其他客戶端的角度來看,腳本的影響要么仍不可見,要么已經(jīng)完成。

然而這也意味著執(zhí)行緩慢的腳本不是一個好主意。創(chuàng)建快速腳本并不難,因為腳本開銷非常低,但如果您要使用慢速腳本,您應該知道腳本運行時沒有其他客戶端可以執(zhí)行命令。

錯誤處理

如前所述,redis.call()導致 Redis 命令錯誤的調(diào)用將停止腳本的執(zhí)行并返回一個錯誤,這樣就很明顯錯誤是由腳本生成的:

> del foo
(integer) 1
> lpush foo a
(integer) 1
> eval "return redis.call('get','foo')" 0
(error) ERR Error running script (call to f_6b1bf486c81ceb7edf3c093f4c48582e38c0e791): ERR Operation against a key holding the wrong kind of value

使用redis.pcall()不會引發(fā)錯誤,但錯誤對象是在上述規(guī)定的格式返回(作為一個Lua表與err字段)。該腳本可以通過返回由返回的錯誤對象將確切的錯誤傳遞給用戶redis.pcall()。

帶寬和EVALSHA

EVAL命令強制您一次又一次發(fā)送腳本正文。Redis不需要每次重新編譯腳本,因為它使用內(nèi)部緩存機制,但是在許多情況下,支付額外帶寬的成本可能不是最佳的。

另一方面,使用特殊命令或通過定義命令redis.conf會是一個問題,原因如下:

  • 不同的實例可能有不同的命令實現(xiàn)。

  • 如果我們必須確保所有實例都包含給定命令,特別是在分布式環(huán)境中,則部署非常困難。

  • 閱讀應用程序代碼,完整的語義可能不清楚,因為應用程序調(diào)用命令定義的服務器端。

為避免這些問題,同時避免帶寬損失,Redis實施了EVALSHA命令。

EVALSHA的工作方式與EVAL完全相同,但不是將腳本作為第一個參數(shù),而是使用腳本的SHA1摘要。行為如下:

  • If the server still remembers a script with a matching SHA1 digest, the script is executed.

  • 如果服務器不記得具有此SHA1摘要的腳本,則會返回一個特殊錯誤,告訴客戶端使用EVAL。

例:

> set foo bar
OK> eval "return redis.call('get','foo')" 0"bar"> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0"bar"> evalsha ffffffffffffffffffffffffffffffffffffffff 0(error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).

即使客戶端實際調(diào)用EVAL,客戶端庫實現(xiàn)始終可以樂觀地發(fā)送EVALSHA,希望腳本已被服務器看到。如果NOSCRIPT錯誤返回,則將使用EVAL。

將鍵和參數(shù)作為附加EVAL參數(shù)傳遞在此上下文中也非常有用,因為腳本字符串保持不變并且可以由Redis高效緩存。

腳本緩存語義

執(zhí)行的腳本保證永遠在Redis實例的給定執(zhí)行的腳本緩存中。這意味著如果對Redis實例執(zhí)行EVAL,所有后續(xù)的EVALSHA調(diào)用都將成功。

腳本可以長時間緩存的原因是編寫良好的應用程序不可能有足夠的不同腳本來引起內(nèi)存問題。每一個腳本在概念上都像是執(zhí)行一個新的命令,甚至一個大型的應用程序可能只有幾百個。即使應用程序被多次修改并且腳本會改變,所使用的內(nèi)存也可以忽略不計。

刷新腳本緩存的唯一方法是明確調(diào)用SCRIPT FLUSH命令,該命令將徹底刷新腳本緩存,刪除到目前為止執(zhí)行的所有腳本。

這通常僅在實例將要為云環(huán)境中的另一個客戶或應用程序實例化時才需要。

另外,如前所述,重新啟動Redis實例會刷新腳本緩存,這不是持久性的。但是從客戶端的角度來看,只有兩種方法可以確保Redis實例在兩個不同的命令之間沒有重新啟動。

  • 我們與服務器的連接是持久的,并且從未關閉。

  • 客戶端顯式檢查runidINFO命令中的字段,以確保服務器未重新啟動并且仍然是相同的進程。

實際上,對于客戶端來說,簡單地假定在給定連接的上下文中,保證緩存腳本在那里,除非管理員顯式調(diào)用SCRIPT FLUSH命令。

用戶可以指望Redis不刪除腳本的事實在流水線上下文中在語義上很有用。

例如,與Redis持久連接的應用程序可以確保,如果腳本一旦仍在內(nèi)存中就被發(fā)送,那么EVALSHA可以用于管道中的這些腳本,而不會由于未知腳本而產(chǎn)生錯誤(我們稍后會詳細看到這個問題)。

一種常見的模式是調(diào)用SCRIPT LOAD加載將出現(xiàn)在管道中的所有腳本,然后直接在管道內(nèi)部使用EVALSHA,而不需要檢查由于未識別腳本哈希而導致的錯誤。

SCRIPT命令

Redis提供了一個可用于控制腳本子系統(tǒng)的SCRIPT命令。SCRIPT目前接受三種不同的命令:

  • SCRIPT FLUSH此命令是強制Redis刷新腳本緩存的唯一方法。在同一個實例可以重新分配給不同用戶的云環(huán)境中,它非常有用。測試客戶端庫的腳本功能實現(xiàn)也很有用。

  • SCRIPT EXISTS sha1 sha2 ... shaN

給定一個SHA1摘要列表作為參數(shù),這個命令返回一個1或0的數(shù)組,其中1表示特定的SHA1被識別為已存在于腳本緩存中的腳本,而0表示具有該SHA1的腳本以前從未見過或者在最新的SCRIPT FLUSH命令之后至少從未見過)。

  • SCRIPT LOAD script 該命令將指定的腳本注冊到Redis腳本緩存中。該命令在我們希望確保EVALSHA不會失敗的所有上下文中很有用(例如在管道或MULTI / EXEC操作期間),而無需實際執(zhí)行腳本。

  • 腳本殺手

此命令是中斷長時間運行的腳本的唯一方法,該腳本可達到配置的腳本最大執(zhí)行時間。SCRIPT KILL命令只能用于在執(zhí)行期間不修改數(shù)據(jù)集的腳本(因為停止只讀腳本不會違反腳本引擎的保證原子性)。有關長時間運行的腳本的更多信息,請參閱下一節(jié)。

腳本作為純粹的功能

腳本的一個非常重要的部分是編寫純函數(shù)的腳本。默認情況下,在Redis實例中執(zhí)行的腳本通過發(fā)送腳本本身而不是結果命令在副本上復制到AOF文件中。

原因是將腳本發(fā)送到另一個 Redis 實例通常比發(fā)送腳本生成的多個命令快得多,因此如果客戶端將大量腳本發(fā)送給主設備,則將腳本轉換為 slave / AOF 的單個命令會導致復制鏈接或僅追加文件的帶寬太多(并且由于調(diào)度通過網(wǎng)絡接收到的命令,因此與分派由 Lua 腳本調(diào)用的命令相比,Redis 需要更多的工作)。

通常情況下復制腳本而不是腳本的效果是合理的,但不是所有情況都是如此。因此,從 Redis 3.2 開始,腳本引擎能夠復制腳本執(zhí)行產(chǎn)生的寫入命令序列,而不是復制腳本本身。有關更多信息,請參閱下一節(jié)。在本節(jié)中,我們假設通過發(fā)送整個腳本來復制腳本。我們稱這種復制模式為整個腳本復制。

整個腳本復制方法的主要缺點是腳本需要具有以下屬性:

  • 腳本必須始終使用給定相同輸入數(shù)據(jù)集的相同參數(shù)來評估相同的 Redis 寫入命令。腳本執(zhí)行的操作不能依賴任何隱藏的(非顯式的)信息或狀態(tài),這些信息或狀態(tài)可能隨著腳本執(zhí)行的進行或腳本的不同執(zhí)行而改變,也不依賴于來自 I / O 設備的任何外部輸入。像使用系統(tǒng)時間,調(diào)用 Redis 隨機命令(如 RANDOMKEY )或使用 Lua 隨機數(shù)生成器,可能會導致腳本不會總是以相同的方式評估。為了在腳本中強制執(zhí)行此操作,Redis 將執(zhí)行以下操作:

  • Lua 不會導出命令來訪問系統(tǒng)時間或其他外部狀態(tài)。

  • 如果腳本調(diào)用 Redis 命令,Redis 命令可以 Redis 隨機命令(如 RANDOMKEY ,SRANDMEMBER ,TIME )更改數(shù)據(jù)集則 Redis 將阻止該腳本。這意味著如果腳本是只讀的并且不修改數(shù)據(jù)集,則可以自由調(diào)用這些命令。請注意,隨機命令不一定意味著使用隨機數(shù)的命令:任何非確定性命令都被視為隨機命令(這方面的最佳示例是 TIME 命令)。

  • 可能以隨機順序返回元素的 Redis 命令(如 SMEMBERS(因為 Redis 集合是無序的))在從 Lua 調(diào)用時具有不同的行為,并在將數(shù)據(jù)返回到 Lua 腳本之前經(jīng)歷無聲的詞典排序過濾器。因此,redis.call("smembers",KEYS[1])將始終以相同的順序返回 Set 元素,而從普通客戶端調(diào)用的相同命令可能會返回不同的結果,即使該關鍵字包含完全相同的元素。

  • Lua 偽隨機數(shù)生成函數(shù)math.randommath.randomseed進行修改,以便每次執(zhí)行新腳本時始終擁有相同的種子。這意味著math.random如果math.randomseed不使用腳本,每次執(zhí)行腳本時,調(diào)用將始終生成相同的數(shù)字序列。

但是,用戶仍然可以使用以下簡單的技巧編寫具有隨機行為的命令。想象一下,我想寫一個 Redis 腳本,它將用 N 個隨機整數(shù)填充一個列表。

我可以從這個小小的 Ruby 程序開始:

require 'rubygems'require 'redis'r = Redis.newRandomPushScript = <<EOF
    local i = tonumber(ARGV[1])
    local res    while (i > 0) do
        res = redis.call('lpush',KEYS[1],math.random())
        i = i-1
    end    return res
EOF

r.del(:mylist)puts r.eval(RandomPushScript,[:mylist],[10,rand(2**32)])

每次執(zhí)行該腳本時,結果列表都將具有以下元素:

> lrange mylist 0 -1 1) "0.74509509873814" 2) "0.87390407681181" 3) "0.36876626981831" 4) "0.6921941534114" 5) "0.7857992587545" 6) "0.57730350670279" 7) "0.87046522734243" 8) "0.09637165539729" 9) "0.74990198051087"10) "0.17082803611217"

為了使它成為一個純函數(shù),但仍然要確保每次調(diào)用腳本都會導致不同的隨機元素,我們可以簡單地向腳本添加一個額外的參數(shù),這個參數(shù)將用于播種 Lua 偽隨機數(shù)發(fā)電機。新腳本如下:

RandomPushScript = <<EOF
    local i = tonumber(ARGV[1])
    local res
    math.randomseed(tonumber(ARGV[2]))    while (i > 0) do
        res = redis.call('lpush',KEYS[1],math.random())
        i = i-1
    end    return res
EOF

r.del(:mylist)puts r.eval(RandomPushScript,1,:mylist,10,rand(2**32))

我們在這里做的是發(fā)送 PRNG 的種子作為論據(jù)之一。這樣,給定相同參數(shù)的腳本輸出將是相同的,但是我們正在改變每個調(diào)用中的一個參數(shù),生成隨機種子客戶端。種子將作為復制鏈接和僅追加文件中的參數(shù)之一傳播,以保證在重新加載 AOF 或從屬進程處理腳本時將生成相同的更改。

注意:此行為的一個重要部分是 Redis 實現(xiàn)的 PRNG ,math.randommath.randomseed保證具有相同的輸出,而不管運行 Redis 的系統(tǒng)的體系結構如何。32位,64位,大端和小端系統(tǒng)都會產(chǎn)生相同的輸出。

復制命令而不是腳本

從 Redis 3.2 開始,可以選擇另一種復制方法。我們可以復制腳本生成的單個寫入命令,而不是復制整個腳本。我們稱之為腳本特效復制

在這種復制模式下,當執(zhí)行 Lua 腳本時,Redis 將收集由 Lua 腳本引擎執(zhí)行的所有實際修改數(shù)據(jù)集的命令。腳本執(zhí)行完成后,腳本生成的命令序列將被包裝到 MULTI / EXEC 事務中,并發(fā)送到從屬和 AOF 。

根據(jù)用例,這在幾個方面很有用:

  • 當腳本計算速度慢時,但是這些效果可以通過幾條寫入命令進行匯總,但重新計算從屬腳本或重新加載 AOF 時的腳本是很可惜的。在這種情況下,復制腳本的效果要好得多。

  • 當啟用腳本效果復制時,關于非確定性功能的控件被禁用。例如,您可以在任意位置隨意使用腳本中的 TIME 或 SRANDMEMBER 命令。

  • 在這種模式下的 Lua PRNG 在每次通話時隨機播種。

為了啟用腳本特效復制,您需要在腳本進行任何寫操作之前發(fā)出以下 Lua 命令:

redis.replicate_commands()

如果啟用腳本效果復制,則該函數(shù)返回 true ;否則,如果在腳本已經(jīng)調(diào)用某個寫入命令后調(diào)用該函數(shù),則返回 false ,并使用正常的整個腳本復制。

命令的選擇性復制

選擇腳本特技復制后(請參閱上一節(jié)),可以更多地控制命令復制到從屬和 AOF 的方式。這是一個非常先進的功能,因為濫用可以通過違反主控,從屬和 AOF 都必須包含相同邏輯內(nèi)容的合同來造成破壞。

然而,這是一個有用的功能,因為有時候我們只需要在主服務器上執(zhí)行某些命令來創(chuàng)建中間值。

在我們執(zhí)行兩套交集的 Lua 腳本中考慮一下。選取五個隨機元素,并用這五個隨機元素創(chuàng)建一個新集合。最后,我們刪除表示兩個原始集之間交集的臨時密鑰。我們想要復制的只是創(chuàng)建具有五個元素的新集合。復制創(chuàng)建臨時密鑰的命令也沒有用。

因此,Redis 3.2 引入了一個新命令,該命令僅在腳本特效復制啟用時才有效,并且能夠控制腳本復制引擎。redis.set_repl()如果禁用腳本特技復制,則調(diào)用該命令并在調(diào)用時失敗。

該命令可以用四個不同的參數(shù)調(diào)用:

redis.set_repl(redis.REPL_ALL) -- Replicate to AOF and slaves.redis.set_repl(redis.REPL_AOF) -- Replicate only to AOF.redis.set_repl(redis.REPL_SLAVE) -- Replicate only to slaves.redis.set_repl(redis.REPL_NONE) -- Don't replicate at all.

默認情況下,腳本引擎始終設置為REPL_ALL。通過調(diào)用此函數(shù),用戶可以打開/關閉AOF和/或從屬復制,并稍后根據(jù)自己的意愿將其恢復。

一個簡單的例子如下:

redis.replicate_commands() -- Enable effects replication.redis.call('set','A','1')redis.set_repl(redis.REPL_NONE)redis.call('set','B','2')redis.set_repl(redis.REPL_ALL)redis.call('set','C','3')

在運行上面的腳本之后,結果是只有A和C鍵將在從屬和AOF上創(chuàng)建。

全局變量保護

Redis腳本不允許創(chuàng)建全局變量,以避免數(shù)據(jù)泄露到Lua狀態(tài)。如果腳本需要在調(diào)用之間保持狀態(tài)(非常罕見),應該使用Redis鍵。

當嘗試全局變量訪問時,腳本終止,EVAL返回錯誤:

redis 127.0.0.1:6379> eval 'a=10' 0(error) ERR Error running script (call to f_933044db579a2f8fd45d8065f04a8d0249383e57): user_script:1: Script attempted to create global variable 'a'

訪問一個不存在的全局變量會產(chǎn)生類似的錯誤。

使用Lua調(diào)試功能或其他方法(例如修改用于實現(xiàn)全局保護的元表來避免全局保護并不難)。然而,意外地做到這一點很困難。如果用戶使用Lua全局狀態(tài)混淆,AOF和復制的一致性不能保證:不要這樣做。

注意Lua新手:為了避免在腳本中使用全局變量,只需使用local關鍵字聲明要使用的每個變量。

在腳本中使用SELECT

可以像使用普通客戶端一樣在Lua腳本中調(diào)用SELECT。但是,行為的一個細微方面在Redis 2.8.11和Redis 2.8.12之間發(fā)生了變化。在2.8.12發(fā)行版之前,由Lua腳本選擇的數(shù)據(jù)庫作為當前數(shù)據(jù)庫傳輸到調(diào)用腳本。從Redis 2.8.12開始,由Lua腳本選擇的數(shù)據(jù)庫僅影響腳本本身的執(zhí)行,但不會修改調(diào)用腳本的客戶端選擇的數(shù)據(jù)庫。

修補程序級別版本之間的語義變化是必需的,因為舊的行為本身與Redis復制層不兼容,并且是錯誤的原因。

可用的庫

Redis Lua解釋器加載以下Lua庫:

  • base lib.

  • table lib.

  • string lib.

  • math lib.

  • struct lib.

  • cjson lib.

  • cmsgpack lib.

  • bitop lib.

  • redis.sha1hex function.

  • redis.breakpoint and redis.debug在Redis Lua調(diào)試器的上下文中起作用。

每個Redis實例都保證具有上述所有庫,因此您可以確保Redis腳本的環(huán)境始終如一。

struct,CJSON和cmsgpack是外部庫,所有其他庫都是標準的Lua庫。

struct

struct是一個用于在Lua中打包/解包結構的庫。

Valid formats:> - big endian< - little endian![num] - alignment
x - pading
b/B - signed/unsigned byte
h/H - signed/unsigned short
l/L - signed/unsigned long
T   - size_t
i/In - signed/unsigned integer with size `n' (default is size of int)
cn - sequence of `n' chars (from/to a string); when packing, n==0 means
     the whole string; when unpacking, n==0 means use the previous
     read number as the string length
s - zero-terminated string
f - float
d - double' ' - ignored

例:

127.0.0.1:6379> eval 'return struct.pack("HH", 1, 2)' 
0"\x01\x00\x02\x00"
127.0.0.1:6379> eval 'return {struct.unpack("HH", ARGV[1])}' 0 "\x01\x00\x02\x00"
1) (integer) 1
2) (integer) 2
3) (integer) 5
127.0.0.1:6379> eval 'return struct.size("HH")' 0
(integer) 4

CJSON

CJSON庫在Lua中提供極快的JSON操作。

例:

redis 127.0.0.1:6379> eval 'return cjson.encode({["foo"]= "bar"})' 0
"{\"foo\":\"bar\"}"
redis 127.0.0.1:6379> eval 'return cjson.decode(ARGV[1])["foo"]' 0 "{\"foo\":\"bar\"}"
"bar"

cmsgpack

cmsgpack庫在Lua中提供了簡單快速的MessagePack操作。

例:

127.0.0.1:6379> eval 'return cmsgpack.pack({"foo", "bar", "baz"})' 0
"\x93\xa3foo\xa3bar\xa3baz"
127.0.0.1:6379> eval 'return cmsgpack.unpack(ARGV[1])' 0 "\x93\xa3foo\xa3bar\xa3baz"
1) "foo"
2) "bar"
3) "baz"

bitop

Lua位操作模塊在數(shù)字上添加按位操作。自2.8.18版以來,它可用于Redis中的腳本。

例:

127.0.0.1:6379> eval 'return bit.tobit(1)' 0
(integer) 1
127.0.0.1:6379> eval 'return bit.bor(1,2,4,8,16,32,64,128)' 0
(integer) 255
127.0.0.1:6379> eval 'return bit.tohex(422342)' 0
"000671c6"

它支持其他幾個功能:bit.tobit,bit.tohexbit.bnot,bit.band,bit.bor,bit.bxor,bit.lshift,bit.rshift,bit.arshiftbit.rol,bit.ror,bit.bswap。所有可用的功能都記錄在Lua BitOp文檔中

redis.sha1hex

執(zhí)行輸入字符串的SHA1。

例:

127.0.0.1:6379> eval 'return redis.sha1hex(ARGV[1])' 0 "foo""0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"

從腳本發(fā)出Redis日志

可以使用redis.log函數(shù)從Lua腳本寫入Redis日志文件。

redis.log(loglevel,message)

loglevel 是其中之一:

  • redis.LOG_DEBUG

  • redis.LOG_VERBOSE

  • redis.LOG_NOTICE

  • redis.LOG_WARNING

它們直接對應于正常的Redis日志級別。只有使用等于或大于當前配置的Redis實例日志級別的日志級別通過腳本發(fā)出的日志才會被發(fā)出。

message參數(shù)是一個簡單的字符串。例:

redis.log(redis.LOG_WARNING,"Something is wrong with this script.")

將生成以下內(nèi)容:

[32343] 22 Mar 15:21:39 # Something is wrong with this script.

沙箱和最大執(zhí)行時間

腳本不應嘗試訪問外部系統(tǒng),如文件系統(tǒng)或任何其他系統(tǒng)調(diào)用。腳本只能在Redis數(shù)據(jù)上運行并傳遞參數(shù)。

腳本也受最大執(zhí)行時間(默認為5秒)的限制。這個默認的超時時間很長,因為腳本通常應該在毫秒之內(nèi)運行。限制主要是為了處理在開發(fā)過程中產(chǎn)生的意外無限循環(huán)。

可以通過redis.conf或使用CONFIG GET / CONFIG SET命令修改以毫秒級精度執(zhí)行腳本的最長時間。影響最大執(zhí)行時間的配置參數(shù)被調(diào)用lua-time-limit。

當腳本達到超時時,它不會由Redis自動終止,因為這違反了Redis與腳本引擎之間的合約,以確保腳本是原子性的。中斷腳本意味著可能會使數(shù)據(jù)集保留半寫數(shù)據(jù)。由于這個原因,當腳本執(zhí)行超過指定時間時,會發(fā)生以下情況:

  • Redis記錄腳本運行時間過長。

  • 它開始從其他客戶端再次接受命令,但會向發(fā)送正常命令的所有客戶端回復BUSY錯誤。在這種狀態(tài)下唯一被允許的命令是SCRIPT KILL和SHUTDOWN NOSAVE。

  • 可以使用SCRIPT KILL命令終止一個只執(zhí)行只讀命令的腳本。這不會違反腳本語義,因為腳本尚未將數(shù)據(jù)寫入數(shù)據(jù)集。

  • 如果腳本已經(jīng)調(diào)用了寫入命令,則唯一允許的命令變?yōu)?code>SHUTDOWN NOSAVE停止服務器而不保存磁盤上的當前數(shù)據(jù)集(基本上服務器已中止)。

EVALSHA在流水線上下文中

在流水線請求的上下文中執(zhí)行EVALSHA時應該小心,因為即使在流水線中,命令的執(zhí)行順序也必須得到保證。如果EVALSHA將返回NOSCRIPT錯誤,則該命令不能在以后重新發(fā)布,否則違反了執(zhí)行順序。

客戶端庫實現(xiàn)應采用以下方法之一:

  • 在管道環(huán)境中始終使用簡單的EVAL。

  • 累積所有要發(fā)送到管道中的命令,然后檢查EVAL命令并使用SCRIPT EXISTS命令檢查是否所有腳本都已定義。如果沒有,請根據(jù)需要在管道頂部添加SCRIPT LOAD命令,并對所有EVAL呼叫使用EVALSHA。

調(diào)試Lua腳本

從Redis 3.2開始,Redis支持原生Lua調(diào)試。Redis Lua調(diào)試器是一個遠程調(diào)試器,由一個服務器(Redis本身)和一個默認的客戶端組成redis-cli。

Lua調(diào)試器在Redis文檔的Lua腳本調(diào)試部分中進行了描述。

Previous article: Next article: