--- tags: Redis --- # Ch11. Scripting Redis with Lua ── Redis 的 LUA 腳本編程 - Overview - 不寫 C 的情形下添加新功能 - 使用 Lua 重寫鎖和信號量 (ch6) - 移除 WATCH/ MULTI/ EXEC 事務 (ch4, ch6) - 使用 Lua 對列表進行分片 (ch9, ch10) ## Lua - 輕量腳本語言、用 C 語言編寫。 - 可嵌入應用程式中,為應用程式提供擴展性 & 訂製功能。 - 應用 - 遊戲開發:將 Lua 腳本嵌入其他程序中運行,遊戲升級時可直接升級腳本,不用重新安裝遊戲。 - 資料庫插件:Redis 本身有很多命令,但在某些特定領域需要擴充若干指令時,用戶可向 server 發送 Lua 腳本執行自定義動作。 - 減少網路延遲:多個請求透過一個腳本一次性執行。 - 原子操作:整個腳本一次性執行,過程中無競態條件 (無需使用事務)。 - 重複使用:客戶端發送的腳本永久存在 Redis,其他客戶端可使用。 ## 11.1 不寫 C 的情形下添加新功能 - 避免在商業環境中修改 Redis 源代碼,可使用 Lua 對 Redis 進行腳本編程。 ### 11.1.1 將 Lua 腳本載入 Redis - Redis 指令 - `SCRIPT LOAD` - 輸入 = 字符串格式的 Lua 腳本 - 輸出 = 被儲存腳本的 SHA1 hash - 目標:腳本會被先儲存起來 - `EVALSHA` - 輸入 = 腳本的 SHA1 hash & 腳本所需所有參數 - 目標:調用之前儲存的腳本 - `EVAL` - 輸入 = 腳本字串符 & 腳本所需所有參數 - 目標:直接執行指定的腳本,並將執行的腳本緩存到 Redis Server - KEYS AND ARGUMENTS TO LUA SCRIPTS: - Redis 連線 - a list of keys:當使用 ch10 提到的 multiserver sharding 技術時,會需要驗證傳入的 key 值都在同一個 shard 上。 - a list of arguments to the function:保存要在 Redis 上調用的資料。 - 由於 Lua 的資料輸入輸出限制,Lua 和 Redis 需要進行互相轉換。  ※ 由於腳本回傳多種不同資料型態的結果可能 ambiguous,所以盡可能回傳 strings。 ### 11.1.2 建立新的狀態消息 (status message) - 案例:status message 的發送功能 (ch8) - Lua 腳本和單一 Redis 命令以及 `MULTI`/`EXEC` 一樣是「原子操作 (atomic)」 → 執行過程不會被其他命令干擾 - 如果已經對結構 (structure) 修改的 Lua 腳本就無法再被中斷,但如果撰寫的 Lua 腳本 never return,則其他客戶端就無法正常執行命令,這時候可使用 Redis 方法停止運行的腳本: - 只有「read」的腳本:運行超過 `lua-time-limit`,就執行`SCRIPT KILL` 終止腳本 - 包含「write」的腳本:為避免終止導致 Redis 不一致,可使用 `SHUTDOWN NOSAVE`,但會遺失自上一次快照後的所有改動 ## 11.2 使用 Lua 重寫鎖和信號量 - 為何使用 Lua 重寫? 1. 執行 Lua 腳本指令的參數有包含需讀/ 寫的 key 值 (11.1 提到的 a list of keys),因此可以在 Redis cluster 啟動時,去驗證是否可在指定分片上對鍵值讀寫。* 2. 有多次讀取要被快取的資料可能會帶來額外消耗,甚至導致新資料被舊資料覆蓋。 - Lua 腳本限制 - 如果事先不知道哪個 key 要被讀寫,就不要用 Lua 腳本,以避免腳本對未被記錄在 `KEYS` 中的參數做讀寫,導致在程序在遷移至 Redis clusters 時不相容。* - 改寫案例 (ch6) - 取得鎖:直接使用 `SET` - 釋放鎖:可使用 Lua 腳本實現釋放自己取得的鎖。(核心程式從 Python 遷移到 Lua,可參考 ch6) ## 11.3 移除 WATCH/ MULTI/ EXEC 事務 - 為何使用 Lua 重寫? 1. 當多個客戶端要對被 `WATCH` 監視的資料進行修改,因為要進行數次通信往返、衝突發生機率較高、網路延遲較大,客戶端可能要多次重士才能完成操作。 → Lua 腳本保證客戶端嘗試的執行都成功、無通信往返,所以執行速度更快。 - Lua 腳本限制 - 運行在 Redis 內部的 Lua 腳本只能訪問 Lua 腳本內,或是 Redis 之內的資料。 - 改寫案例 (ch4, ch6) ## 11.4 使用 Lua 對列表進行分片 - 分片列表的構成 - `<listname>:first` 和 `<listname>:last` 儲存首尾分片 ID,當兩個 ID 相同代表分片列表為空。 - 分片列表中每個分片的命名為 `<listname>:<shardid>`,並按照順序進行分配。 - 頭尾分片可能是空的,但中間一定是被填滿的, - 將元素推入分片列表 - Lua 腳本依據 `LPUSH`/`RPUSH` 找出首/尾分片。 - 將元素 push 進分片對應的列表中。假設該分片列表已經塞滿,就會自動產新一個新的分片繼續將元素推入 & 更新首/尾分片 ID。 - 最終 return 被推入的元素數量。 - 從分片列表中彈出元素 - Lua 腳本依據 `LPOP`/`RPOP` 找出首/尾分片。 - 當分片非空就 pop 出元素。假設該分片列表已經為空,但整個列表不是都空,就要更新首/尾分片 ID。 - 對分片列表執行阻塞彈出操作 - 避免產生不正確的資料。 ## 參考資源 - Redis中使用Lua脚本(一) https://zhuanlan.zhihu.com/p/77484377
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up