日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

Lua腳本規范與常見報錯

重要

本文中含有需要您注意的重要提示信息,忽略該信息可能對您的業務造成影響,請務必仔細閱讀。

云數據庫 Tair(兼容 Redis)實例支持Lua相關命令,通過Lua腳本可高效地處理CAS(compare-and-set)命令,進一步提升實例的性能,同時可以輕松實現以前較難實現或者不能高效實現的模式。本文介紹使用Lua腳本的基本語法與使用規范。

注意事項

數據管理服務DMS控制臺目前暫不支持使用Lua腳本等相關命令,請通過客戶端或redis-cli連接實例使用Lua腳本。

基本語法

命令

語法

說明

EVAL

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

執行給定的腳本和參數,并返回結果。

參數說明:

  • script:Lua腳本。

  • numkeys:指定KEYS[]參數的數量,非負整數。

  • KEYS[]:傳入的Redis鍵參數。

  • ARGV[]:傳入的腳本參數。KEYS[]與ARGV[]的索引均從1開始。

說明
  • 與SCRIPT LOAD命令一樣,EVAL命令也會將Lua腳本緩存至實例。

  • 混用或濫用KEYS[]ARGV[]可能會導致實例產生不符合預期的行為,尤其在集群模式下,詳情請參見集群架構中Lua腳本的限制

  • 推薦使用KEYS[]ARGV[]的方式傳遞參數。不推薦將參數編碼進腳本中,過多類似行為會導致LUA虛擬機內存使用量上升,且無法及時回收,極端情況下會導致實例主庫與備庫內存溢出(Out of Memory),造成數據丟失。

EVALSHA

EVALSHA sha1 numkeys key [key ...] arg [arg ...]

給定腳本的SHA1校驗和,實例將再次執行腳本。

使用EVALSHA命令時,若sha1值對應的腳本未緩存至Redis中,Redis會返回NOSCRIPT錯誤,請通過EVAL或SCRIPT LOAD命令將目標腳本緩存至Redis中后進行重試,詳情請參見處理NOSCRIPT錯誤

SCRIPT LOAD

SCRIPT LOAD script

將給定的script腳本緩存在實例中,并返回該腳本的SHA1校驗和。

SCRIPT EXISTS

SCRIPT EXISTS script [script ...]

給定一個(或多個)腳本的SHA1,返回每個SHA1對應的腳本是否已緩存在當前實例中。腳本已存在則返回1,不存在則返回0。

SCRIPT KILL

SCRIPT KILL

停止正在運行的Lua腳本。

SCRIPT FLUSH

SCRIPT FLUSH

清空當前實例中的所有Lua腳本緩存。

更多關于Redis命令的介紹,請參見Redis官網

以下為部分命令的示例,本文在執行以下命令前執行了SET foo value_test

  • EVAL命令示例:

    EVAL "return redis.call('GET', KEYS[1])" 1 foo

    返回示例:

    "value_test"
  • SCRIPT LOAD命令示例:

    SCRIPT LOAD "return redis.call('GET', KEYS[1])"

    返回示例:

    "620cd258c2c9c88c9d10db67812ccf663d96bdc6"
  • EVALSHA命令示例:

    EVALSHA 620cd258c2c9c88c9d10db67812ccf663d96bdc6 1 foo

    返回示例:

    "value_test"
  • SCRIPT EXISTS命令示例:

    SCRIPT EXISTS 620cd258c2c9c88c9d10db67812ccf663d96bdc6 ffffffffffffffffffffffffffffffffffffffff

    返回示例:

    1) (integer) 1
    2) (integer) 0
  • SCRIPT FLUSH命令示例:

    警告

    該命令會清空實例中的所有Lua腳本緩存,請提前備份Lua腳本。

    SCRIPT FLUSH

    返回示例:

    OK

優化內存、網絡開銷

現象:

在實例中緩存了大量功能重復的腳本,占用大量內存空間甚至引發內存溢出(Out of Memory),錯誤示例如下。

EVAL "return redis.call('set', 'k1', 'v1')" 0
EVAL "return redis.call('set', 'k2', 'v2')" 0

解決方案:

  • 請避免將參數作為常量寫在Lua腳本中,以減少內存空間的浪費。

    # 與錯誤示例實現相同功能但僅需緩存一次腳本。
    EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k1 v1
    EVAL "return redis.call('set', KEYS[1], ARGV[1])" 1 k2 v2
  • 更加建議采用如下寫法,在減少內存的同時,降低網絡開銷。

    SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])"    # 執行后,Redis將返回"55b22c0d0cedf3866879ce7c854970626dcef0c3"
    EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1
    EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2

清理Lua腳本的內存占用

現象:

由于Lua腳本緩存將計入實例的內存使用量中,并會導致used_memory升高,當實例的內存使用量接近甚至超過maxmemory時,可能引發內存溢出(Out Of Memory),報錯示例如下。

-OOM command not allowed when used memory > 'maxmemory'.

解決方案:

通過客戶端執行SCRIPT FLUSH命令清除Lua腳本緩存,但與FLUSHALL不同,SCRIPT FLUSH命令為同步操作。若實例緩存的Lua腳本過多,SCRIPT FLUSH命令會阻塞實例較長時間,可能導致實例不可用,請謹慎處理,建議在業務低峰期執行該操作。

說明

在控制臺上單擊清除數據只能清除數據,無法清除Lua腳本緩存。

同時,請避免編寫過大的Lua腳本,防止占用過多的內存;避免在Lua腳本中大批量寫入數據,否則會導致內存使用急劇升高,甚至造成實例OOM。在業務允許的情況下,建議開啟數據逐出(實例默認開啟,模式為volatile-lru)節省內存空間。但無論是否開啟數據逐出,實例均不會逐出Lua腳本緩存。

處理NOSCRIPT錯誤

現象:

使用EVALSHA命令時,若sha1值對應的腳本未緩存至實例中,實例會返回NOSCRIPT錯誤,報錯示例如下。

(error) NOSCRIPT No matching script. Please use EVAL.

解決方案:

請通過EVAL命令或SCRIPT LOAD命令將目標腳本緩存至實例中后進行重試。但由于實例不保證Lua腳本的持久化、復制能力,在部分場景下仍會清除Lua腳本緩存(例如實例遷移、變配等),這要求您的客戶端需具備處理該錯誤的能力,詳情請參見腳本緩存、持久化與復制

以下為一種處理NOSCRIPT錯誤的Python Demo示例,該demo利用Lua腳本實現了字符串prepend操作。

說明

您可以考慮通過Python的redis-py解決該類錯誤,redis-py提供了封裝Redis Lua的一些底層邏輯判斷(例如NOSCRIPT錯誤的catch)的Script類。

import redis
import hashlib

# strin是一個Lua腳本的字符串,函數以字符串的格式返回strin的sha1值。
def calcSha1(strin):
    sha1_obj = hashlib.sha1()
    sha1_obj.update(strin.encode('utf-8'))
    sha1_val = sha1_obj.hexdigest()
    return sha1_val

class MyRedis(redis.Redis):

    def __init__(self, host="localhost", port=6379, password=None, decode_responses=False):
        redis.Redis.__init__(self, host=host, port=port, password=password, decode_responses=decode_responses)

    def prepend_inLua(self, key, value):
        script_content = """\
        local suffix = redis.call("get", KEYS[1])
        local prefix = ARGV[1]
        local new_value = prefix..suffix
        return redis.call("set", KEYS[1], new_value)
        """
        script_sha1 = calcSha1(script_content)
        if self.script_exists(script_sha1)[0] == True:      # 檢查Redis是否已緩存該腳本。
            return self.evalsha(script_sha1, 1, key, value) # 如果已緩存,則用EVALSHA執行腳本
        else:
            return self.eval(script_content, 1, key, value) # 否則用EVAL執行腳本,注意EVAL有將腳本緩存到Redis的作用。這里也可以考慮采用SCRIPT LOAD與EVALSHA的方式。

r = MyRedis(host="r-******.redis.rds.aliyuncs.com", password="***:***", port=6379, decode_responses=True)

print(r.prepend_inLua("k", "v"))
print(r.get("k"))
            

處理Lua腳本超時

  • 現象:

    由于Lua腳本在實例中是原子執行的,Lua慢請求可能會導致實例阻塞。單個Lua腳本阻塞實例最多5秒,5秒后實例會給所有其他命令返回如下BUSY error報錯,直到腳本執行結束。

    BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

    解決方案:

    您可以通過SCRIPT KILL命令終止Lua腳本或等待Lua腳本執行結束。

    說明
    • SCRIPT KILL命令在執行慢Lua腳本的前5秒不會生效(阻塞中)。

    • 建議您編寫Lua腳本時預估腳本的執行時間,同時檢查死循環等問題,避免過長時間阻塞實例導致服務不可用,必要時請拆分Lua腳本。

  • 現象:

    若當前Lua腳本已執行寫命令,則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.

    解決方案:

    請在控制臺的實例列表中單擊對應實例重啟

腳本緩存、持久化與復制

現象:

在不重啟、不調用SCRIPT FLUSH命令的情況下,實例會一直緩存執行過的Lua腳本。但在部分情況下(例如實例遷移、變配、版本升級、切換等等),實例無法保證Lua腳本的持久化,也無法保證Lua腳本能夠被同步至其他節點。

解決方案:

由于實例不保證Lua腳本的持久化、復制能力,請您在本地存儲所有Lua腳本,在必要時通過EVAL或SCRIPT LOAD命令將Lua腳本重新緩存至實例中,避免實例重啟、HA切換等操作時實例中的Lua腳本被清空而帶來的NOSCRIPT錯誤。

集群架構中Lua腳本的限制

  • 為了保證Lua執行的原子性,Lua命令不可拆分,只能在集群架構的一個DB分片上執行。通常會根據Key來決定路由到哪個DB分片執行,所以在集群架構中執行Lua命令時至少需要指定一個Key。如果讀寫多個Key,則同一個Lua腳本中的Key必須屬于同一個Slot,否則會導致執行結果異常。對于KEYS、SCAN、FLUSHDB等無Key的命令,雖然能正常執行,但返回結果只包含單個分片的數據。上述限制由Redis Cluster架構導致。

  • 對單個節點執行SCRIPT LOAD命令時,不保證將該Lua腳本存入至其他節點中。

代理模式(Proxy)自定義的Lua錯誤碼及原因

Proxy會通過語法檢查來提前識別Key跨越多個Slot的情況,提前暴露異常,方便問題排查。Proxy檢查方法和Lua虛擬機存在差異,這導致了在Proxy中執行Lua命令會存在額外限制(例如不支持UNPACK命令、不支持在MULTI、EXEC事務中使用EVAL、EVALSHA、SCRIPT系列命令等)。您也可以通過關閉script_check_enable參數配置關閉Proxy對Lua語法的部分檢查。

說明

關閉script_check_enable參數配置對實例有什么影響?

  • 當實例為兼容Redis 5.0版本(小版本5.0.8以下)、4.0及以下版本,不推薦關閉,可能會導致腳本執行結果錯誤但返回正確。

  • 其他版本關閉后,Proxy將不再檢查Lua語法,但數據節點仍會正常檢查Lua語法。

同時,讀寫分離架構實例如果開啟了readonly_lua_route_ronode_enable配置,Proxy會檢查Lua是否只包含只讀命令并決定能否將Lua轉發到只讀節點,該檢查邏輯對Lua語法存在限制。

具體錯誤碼和原因請參見下表。

錯誤碼分類

錯誤碼

說明

Redis Cluster 架構限制

-ERR for redis cluster, eval/evalsha number of keys can't be negative or zero\r\n

執行Lua時必須帶有Key,Proxy會根據Key決定將Lua轉發到哪個DB分片上執行。

# 正確示例
EVAL "return redis.call('get', KEYS[1])" 1 fooeval

# 錯誤示例
EVAL "return redis.call('get', 'foo')" 0

-ERR 'xxx' command keys must in same slot

Lua腳本中的多個Key必須屬于同一個Slot。

# 正確示例:
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar

# 錯誤示例:
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo foobar

Proxy Lua語法檢查導致的額外限制

(關閉 script_check_enable 配置可以避免該檢查)

-ERR bad lua script for redis cluster, nested redis.call/redis.pcall

不支持Redis嵌套方式調用,您可以使用局部變量的方式進行調用。

# 正確示例
EVAL "local value = redis.call('GET', KEYS[1]); redis.call('SET', KEYS[2], value)" 2 foo bar

# 錯誤示例
EVAL "redis.call('SET', KEYS[1], redis.call('GET', KEYS[2]))" 2 foo bar

-ERR bad lua script for redis cluster, first parameter of redis.call/redis.pcall must be a single literal string

redis.call/pcall中調用的命令必須是字符串常量。

# 正確示例
eval "redis.call('GET', KEYS[1])" 1 foo

# 錯誤示例
eval "local cmd = 'GET'; redis.call(cmd, KEYS[1])" 1 foo

-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array\r\n

所有Key都應該由KEYS數組來傳遞,redis.call/pcall中調用的命令,Key的位置必須是KEYS array,且不能使用Lua變量替換KEYS,。

說明

Redis開源版 5.0版本(小版本5.0.8以下)、4.0及以下版本實例或Proxy代理版本較低(云原生版7.0.2以下 、經典版6.8.12以下)存在該限制。

如果實例版本、代理版本都符合要求但仍存在限制,請修改任意參數(例如query_cache_expire參數),等待1分鐘后重試。

# 正確示例:
EVAL "return redis.call('mget', KEYS[1], KEYS[2])" 2 foo {foo}bar

# 錯誤示例:
EVAL "return redis.call('mget', KEYS[1], '{foo}bar')" 1 foo                      # '{foo}bar'作為Key,應該使用KEYS數組進行傳遞。
EVAL "local i = 2 return redis.call('mget', KEYS[1], KEYS[i])" 2 foo {foo}bar    # 在代理模式(Proxy)不允許執行此腳本,因為KEYS數據的索引是變量,但在直連模式中無此限制。
EVAL "return redis.call('mget', KEYS[1], ARGV[1])" 1 foo {foo}bar                # 不應該使用ARGV[1]數據元素作為Key。

-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, include destination, and KEYS should not be in expression

ZUNIONSTORE、ZINTERSTORE命令的destination參數必須用KEYS傳遞。

說明

Redis開源版 5.0版本(小版本5.0.8以下)、4.0及以下版本實例或Proxy代理版本較低(云原生版7.0.2以下 、經典版6.8.12以下)存在該限制。

如果實例版本、代理版本都符合要求但仍存在限制,請修改任意參數(例如query_cache_expire參數),等待1分鐘后重試。

-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE numkeys parameter should be a single number and not expression

ZUNIONSTORE、ZINTERSTORE命令的numkeys參數不是常量。

說明

Redis開源版 5.0版本(小版本5.0.8以下)、4.0及以下版本實例或Proxy代理版本較低(云原生版7.0.2以下 、經典版6.8.12以下)存在該限制。

如果實例版本、代理版本都符合要求但仍存在限制,請修改任意參數(例如query_cache_expire參數),等待1分鐘后重試。

-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE numkeys value is not an integer or out of range

ZUNIONSTORE、ZINTERSTORE命令的numkeys參數不是數字。

說明

Redis開源版 5.0版本(小版本5.0.8以下)、4.0及以下版本實例或Proxy代理版本較低(云原生版7.0.2以下 、經典版6.8.12以下)存在該限制。

如果實例版本、代理版本都符合要求但仍存在限制,請修改任意參數(例如query_cache_expire參數),等待1分鐘后重試。

-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE all the keys that the script uses should be passed using the KEYS array

ZUNIONSTORE、ZINTERSTORE命令的所有Key必須通過KEYS傳遞。

說明

Redis開源版 5.0版本(小版本5.0.8以下)、4.0及以下版本實例或Proxy代理版本較低(云原生版7.0.2以下 、經典版6.8.12以下)存在該限制。

如果實例版本、代理版本都符合要求但仍存在限制,請修改任意參數(例如query_cache_expire參數),等待1分鐘后重試。

-ERR bad lua script for redis cluster, XREAD/XREADGROUP all the keys that the script uses should be passed using the KEYS array

XREAD、XREADGROUP命令的所有Key必須通過KEYS傳遞。

說明

Redis開源版 5.0版本(小版本5.0.8以下)、4.0及以下版本實例或Proxy代理版本較低(云原生版7.0.2以下 、經典版6.8.12以下)存在該限制。

如果實例版本、代理版本都符合要求但仍存在限制,請修改任意參數(例如query_cache_expire參數),等待1分鐘后重試。

-ERR bad lua script for redis cluster, all the keys that the script uses should be passed using the KEYS array, and KEYS should not be in expression, sort command store key does not meet the requirements

SORT命令的Key必須通過KEYS傳遞。

說明

Redis開源版 5.0版本(小版本5.0.8以下)、4.0及以下版本實例或Proxy代理版本較低(云原生版7.0.2以下 、經典版6.8.12以下)存在該限制。

如果實例版本、代理版本都符合要求但仍存在限制,請修改任意參數(例如query_cache_expire參數),等待1分鐘后重試。

讀寫權限問題

-ERR Write commands are not allowed from read-only scripts

通過EVAL_RO命令發送的Lua中不能包含寫命令。

-ERR bad write command in no write privilege

只讀賬號發送的Lua中不能包含寫命令。

命令未支持

-ERR script debug not support

Proxy當前不支持SCRIPT DEBUG命令。

-ERR bad lua script for redis cluster, redis.call/pcall unkown redis command xxx

Lua中包含Proxy不支持的命令。更多信息請參見集群架構與讀寫分離架構實例的命令限制

Lua 語法錯誤

  • -ERR bad lua script for redis cluster, redis.call/pcall expect '('

  • -ERR bad lua script for redis cluster, redis.call/redis.pcall definition is not complete, expect ')'

Lua語法錯誤,redis.call后面必須包含完整的()

-ERR bad lua script for redis cluster, at least 1 input key is needed for ZUNIONSTORE/ZINTERSTORE

ZUNIONSTORE、ZINTERSTORE命令的numkeys參數必須大于0。

-ERR bad lua script for redis cluster, ZUNIONSTORE/ZINTERSTORE key count < numkeys

ZUNIONSTORE、ZINTERSTORE命令的實際Key數量小于numkeys值。

-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error

XREAD、XREADGROUP命令的語法不對,請檢查參數個數。

-ERR bad lua script for redis cluster, xread/xreadgroup command syntax error, streams must be specified

XREAD、XREADGROUP命令必須需要有streams參數。

-ERR bad lua script for redis cluster, sort command syntax error

SORT命令的語法錯誤。