當業務需要在一次資料庫互動中原子性地執行多個命令時,例如實現分布式鎖、限流器或進行條件更新,多次網路往返會增加延遲並引入競態條件風險。Orca(相容Redis協議)提供的Lua指令碼功能,允許您將多個Redis相容命令封裝在一個指令碼中,由資料庫原子化執行,從而有效解決此類問題,提升複雜操作的執行效率與資料一致性。
功能簡介
Orca(相容Redis協議)的Lua指令碼功能,允許您像在Redis中一樣執行Lua指令碼。其核心優勢在於原子性和高效能。
原子性:指令碼內的所有命令作為一個不可分割的單元執行。指令碼執行期間,不會有其他命令或指令碼插入,保證了操作的原子性,避免了部分執行導致的中間狀態。
高效能:
減少網路開銷:將多個命令打包在單個指令碼中一次性發送給資料庫,顯著減少了用戶端與伺服器之間的網路往返次數。
指令碼緩衝:通過
SCRIPT LOAD命令將指令碼預先載入到叢集記憶體中,並獲得一個SHA1校正和。後續可通過EVALSHA命令,僅發送此簡短的SHA1值來呼叫指令碼,進一步降低網路傳輸的資料量。
適用範圍
叢集版本:需為MySQL 8.0.2,且核心小版本需為8.0.2.2.33及以上版本。
串連地址:需使用读写模式為可读可写(自动读写分离)的Orca地址執行Lua指令碼命令。
不支援的命令:以下命令無法在Lua指令碼中通過
redis.call()或redis.pcall()執行,否則將返回錯誤。事務控制命令:
WATCH、UNWATCH、MULTI、EXEC、DISCARD阻塞命令:
BLPOP、BRPOPLua指令碼命令:
EVAL、EVAL_RO、EVALSHA、EVALSHA_RO、SCRIPT訂閱發布命令:
SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE、PUNSUBSCRIBE串連管理命令:
AUTH、HELLO、CLIENT
基本文法
以下是操作Lua指令碼的核心命令。更多資訊請參見Redis官網Lua 指令碼相關命令和Scripting with Lua。
命令 | 文法 | 說明 |
|
| 直接執行給定的Lua指令碼。這是最基礎的執行方式。參數說明:
|
|
| 唯讀模式下執行Lua指令碼。適用於讀寫分離情境,確保指令碼不會執行寫操作。若指令碼中包含寫命令,將返回錯誤。參數說明與 |
|
| 通過指令碼的SHA1校正和執行已緩衝的指令碼。如果指令碼未緩衝,將返回 |
|
| 唯讀模式下通過指令碼的SHA1校正和執行已緩衝的指令碼。適用於讀寫分離情境,確保指令碼不會執行寫操作。若指令碼中包含寫命令,將返回錯誤。參數說明與 |
|
| 將指令碼載入到緩衝中,並返回其SHA1校正和,以便後續通過 |
|
| 檢查一個或多個SHA1校正和對應的指令碼是否存在於緩衝中。存在返回1,不存在返回0。 |
|
| 終止當前正在執行的Lua指令碼。Orca支援終止任意指令碼(包括寫指令碼),並會自動復原該指令碼已產生的資料變更,保證資料一致性。 |
|
| 清空當前叢集中的所有Lua指令碼緩衝。
|
操作樣本
準備測試資料:
SET polardb orca測試命令:
EVAL
EVAL "return redis.call('GET', KEYS[1])" 1 polardb返回樣本:
"orca"EVAL_RO
EVAL_RO "return redis.call('GET', KEYS[1])" 1 polardb返回樣本:
"orca"SCRIPT LOAD
SCRIPT LOAD "return redis.call('GET', KEYS[1])"返回樣本:
"d3c21d0c2b9ca22f82737626a27bcaf5d288f99f"EVALSHA
EVALSHA d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 polardb返回樣本:
"orca"EVALSHA_R
EVALSHA_RO d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 polardb返回樣本:
"orca"SCRIPT EXISTS
SCRIPT EXISTS d3c21d0c2b9ca22f82737626a27bcaf5d288f99f ffffffffffffffffffffffffffffffffffffffff返回樣本:
1) (integer) 1 2) (integer) 0SCRIPT FLUSH
警告該命令會清空叢集中的所有Lua指令碼緩衝(
SCRIPT LOAD/EVAL產生的緩衝都會被移除)。清空後,所有依賴
EVALSHA/EVALSHA_RO的調用都可能返回NOSCRIPT錯誤,需要重新載入指令碼並重試。若緩衝指令碼較多,
SCRIPT FLUSH可能阻塞叢集較長時間,建議在業務低峰期執行。建議您在用戶端/應用側保留指令碼原始碼,並實現
NOSCRIPT的自動回復機制(重新SCRIPT LOAD或使用EVAL重新註冊)。
SCRIPT FLUSH返回樣本:
OK
常見使用情境
配置並執行一個原子計數器
避免並發INCR導致的競態,確保計數器嚴格遞增且可帶條件重設。
流程概述
編寫Lua指令碼。
載入至叢集緩衝。
通過
EVALSHA複用執行。
SCRIPT LOAD + EVALSHA減少網路傳輸量,提升效能與等冪性。指令碼內容不暴露於請求體。
操作步驟
編寫指令碼(本地儲存):
-- counter.lua:若 key 不存在則設為 0,再 +1;返回新值 if redis.call('EXISTS', KEYS[1]) == 0 then redis.call('SET', KEYS[1], 0) end return redis.call('INCR', KEYS[1])載入指令碼並擷取SHA1:
SCRIPT LOAD "if redis.call('EXISTS', KEYS[1]) == 0 then redis.call('SET', KEYS[1], 0) end return redis.call('INCR', KEYS[1])" -- 返回結果 "b547eabbcde73b25330442e4f4e4dc1783b91241"(推薦)複用執行:
EVALSHA b547eabbcde73b25330442e4f4e4dc1783b91241 1 my_counter -- 返回結果 (integer) 1 -- 再次執行返回結果 (integer) 2
執行純查詢指令碼
確認指令碼不含寫操作:檢查是否調用
SET、DEL、HSET、LPUSH等任意寫命令。若存在,禁止使用EVAL_RO。說明若指令碼含寫命令,將返回錯誤
(error) ERR Write commands are not allowed from read-only scripts.。使用
EVAL_RO執行:EVAL_RO "return {redis.call('SET', KEYS[1]), redis.call('TTL', KEYS[1])}" 1 polardb -- 返回結果 1) "orca" 2) (integer) -1
終止異常啟動並執行Lua指令碼
防止長耗時指令碼阻塞叢集。中斷執行,系統將自動復原全部變更,保持資料一致性。執行SCRIPT KILL:
僅能終止當前正在執行的指令碼,不支援指定SHA1終止。
SCRIPT KILL
-- 返回結果
OK效能最佳化實踐
為降低Lua指令碼對叢集的阻塞影響,並避免叢集緩衝大量功能重複的指令碼導致記憶體佔用過高,建議遵循以下實踐:
逾時限制:單個Lua指令碼的預設執行逾時時間為300秒。
避免編寫過大的Lua指令碼,防止佔用過多的記憶體。
避免在Lua指令碼中長時間、大批量寫入資料。
阻塞風險:Lua指令碼在叢集中以原子方式執行。在執行期間,會阻塞其他命令與指令碼,因此應控制寫入批量與執行時間長度,以避免長迴圈或大範圍遍曆。如有必要,應將複雜邏輯拆分為多個獨立指令碼進行執行。
指令碼緩衝非持久化:叢集運行時Lua指令碼緩衝不會被清除,叢集重啟、主備切換(HA)或執行
SCRIPT FLUSH命令後會清理Lua指令碼緩衝,需要重新註冊。指令碼編寫規範:使用
KEYS[]與ARGV[]傳遞參數,避免將參數寫入程式碼在指令碼中。減少網路流量:使用
SCRIPT LOAD + EVALSHA組合,獲得最佳效能並減少網路流量。
操作樣本
載入指令碼:在應用初始化或首次使用時,通過
SCRIPT LOAD將指令碼載入到叢集並擷取其SHA1值。您的應用程式應在本地儲存此SHA1值。# 載入一個設定索引值的指令碼 SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])" # 返回樣本 "55b22c0d0cedf3866879ce7c854970626dcef0c3"執行指令碼:後續通過
EVALSHA命令,使用上一步擷取的SHA1值來執行指令碼。# 使用緩衝的指令碼設定 k1 = v1 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1 # 使用同一個緩衝的指令碼設定 k2 = v2 EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v2處理
NOSCRIPT錯誤:如果EVALSHA返回NOSCRIPT錯誤,應用程式應捕獲此錯誤,然後自動切換回使用EVAL或SCRIPT LOAD命令執行一次指令碼將指令碼緩衝至叢集中。EVAL:在執行的同時會自動將指令碼重新緩衝。SCRIPT LOAD:將指令碼載入到叢集並擷取其SHA1值。
清理Lua指令碼的記憶體佔用:當您在不需要執行Lua指令碼時,可執行
SCRIPT FLUSH命令清除Lua指令碼緩衝。若叢集中緩衝的Lua指令碼過多,該命令可能會阻塞叢集較長時間,建議在業務低峰期執行。
常見問題
如何處理
NOSCRIPT No matching script. Please use EVAL.錯誤?問題原因:這個錯誤表示您嘗試使用
EVALSHA或EVALSHA_RO命令執行一個不存在於叢集緩衝中的指令碼。通常由叢集重啟、主備切換或執行了SCRIPT FLUSH導致。
解決方案:您的用戶端代碼需要具備錯誤處理邏輯。當捕獲到NOSCRIPT錯誤時,應改用EVAL或SCRIPT LOAD命令重新執行該指令碼將指令碼緩衝至叢集中,後續的EVALSHA調用即可恢複正常。Lua指令碼執行逾時怎麼辦?
指令碼預設逾時時間為300秒。逾時通常意味著指令碼邏輯過於複雜或處理的資料量過大。
解決方案:最佳化指令碼邏輯,避免在指令碼內執行低效的迴圈或大規模資料遍曆。
如果一個指令碼執行時間過長,可以通過
SCRIPT KILL命令終止它。PolarDB會復原該指令碼已做的所有修改。將複雜的商務邏輯拆分為多個更小、更快的指令碼分步執行。
為什麼使用
EVAL_RO執行指令碼會報錯ERR Write commands are not allowed from read-only scripts.?EVAL_RO和EVALSHA_RO是唯讀模式,嚴禁執行任何寫操作命令(如SET、HSET或DEL等)。此錯誤明確指出您的指令碼中包含了寫命令。
解決方案:檢查並移除指令碼中的所有寫命令。
如果您確實需要執行寫操作,請改用
EVAL或EVALSHA命令。