Redis 實戰 —— 14. Redis 的 Lua 腳本編程


簡介

Redis 從 2.6 版本開始引入使用 Lua 編程語言進行的服務器端腳本編程功能,這個功能可以讓用戶直接在 Redis 內部執行各種操作,從而達到簡化代碼並提高性能的作用。 P248

在不編寫 C 代碼的情況下添加新功能 P248

通過使用 Lua 對 Redis 進行腳本編程,我們可以避免一些減慢開發速度或者導致性能下降對常見陷阱。 P248

將 Lua 腳本載入 Redis P249
  • SCRIPT LOAD 命令可以將腳本載入 Redis ,這個命令接受一個字符串格式的 Lua 腳本為參數,它會把腳本存儲起來等待之后使用,然后返回被存儲腳本的 SHA1 校驗和
  • EVALSHA 命令可以調用之前存儲的腳本,這個命令接收腳本的 SHA1 校驗和以及腳本所需的全部參數
  • EVAL 命令可以直接執行指定的腳本,這個命令接收腳本字符串以及腳本所需的全部參數。這個命令除了會執行腳本之外,還會將被執行的腳本緩存到 Redis 服務器里面

由於 Lua 的數據傳入和傳出限制, Lua 與 Redis 需要進行相互轉換。因為腳本在返回各種不同類型的數據時可能會產生含糊不清的結果,所以我們應該盡量顯式的返回字符串。 P250

我們可以在 官方文檔 中找到 Redis 和 Lua 不同類型之間的轉換表:

Redis 類型轉換至 Lua 類型

Redis Lua
integer reply number
bulk reply string
multi bulk reply table (may have other Redis data types nested)
status reply table with a single ok field containing the status
error reply table with a single err field containing the error
Nil bulk reply false boolean type
Nil multi bulk reply false boolean type

Lua 類型轉換至 Redis 類型

Lua Redis
number integer reply (the number is converted into an integer)
string bulk reply
table (array) multi bulk reply (truncated to the first nil inside the Lua array if any)
table with a single ok field status reply
table with a single err field error reply
boolean false Nil bulk reply
boolean true integer reply with value of 1
創建新的狀態消息 P251
  • Lua 腳本跟單個 Redis 命令以及 MULTI/EXEC 事務一樣,都是原子操作
  • 已經對結構進行了修改的 Lua 腳本將無法被中斷
    • 不執行任何寫命令對只讀腳本:可以在腳本對運行時間超過 lua-time-limit 選項指定的時間之后,執行 SCRIPT KILL 命令殺死正在運行對腳本
    • 有寫命令的腳本:殺死腳本將導致 Redis 存儲的數據進入一種不一致的狀態。在這種情況下

使用 Lua 重寫鎖和信號量 P254

如果我們事先不知道哪些鍵會被讀取和寫入,那么就應該使用 WATCH/MULTI/EXEC 事務或者鎖,而不是 Lua 腳本。因此,在腳本里面對未被記錄到 KEYS 參數中的鍵進行讀取或者寫入,可能會在程序遷移至 Redis 集群的時候出現不兼容或者故障。 P254

獲取鎖在目前已不需要使用 Lua 腳本實現,可以直接使用 SET ,並用 PXNX 選項即可在鍵不存在的時候設置帶過期時間的值。釋放鎖時為了保證釋放的時自己獲取的鎖,需要使用 Lua 腳本實現。相關代碼已在 實現自動補全、分布式鎖和計數信號量 中實現。

移除 WATCH/MULTI/EXEC 事務 P258

一般來說,如果只有少數幾個客戶端嘗試對被 WATCH 命令監視對數據進行修改,那么事務通常可以在不發生明顯沖突或重試的情況下完成。但是,如果操作需要進行好幾次通信往返,或者操作發生沖突的概率較高,又或者網絡延遲較大,那么客戶端可能需要重試很多次才能完成操作。 P258

使用 Lua 腳本替代事務不僅可以保證客戶端嘗試的執行都可以成功,還能降低通信開銷,大幅提高 TPS 。同時由於 Lua 腳本沒有多次通信往返,所以執行速度也會明顯快於細粒度鎖的版本。

Lua 腳本可以提供巨大的性能優勢,並且能在一些情況下大幅地簡化代碼,但運行在 Redis 內部但 Lua 腳本只能訪問位於 Lua 腳本之內或者 Redis 數據庫之內的數據,而鎖或者 WATCH/MULTI/EXEC 事務並沒有這一限制。 P263

使用 Lua 對列表進行分片 P263

分片列表的構成 P263

為了能夠對分片列表的兩端執行推入操作和彈出操作,在構建分片列表時除了需要存儲組成列表的各個分片之外,還需要記錄列表第一個分片的 ID 以及最后一個分片的 ID 。當分片列表為空時,這兩個字符串存儲的分片 ID 將是相同的。 P263

組成分片列表的每個分片都會被命名為 <listname>:<shardid> ,並按照順序進行分配。具體來說,如果程序總是從左端彈出元素,並從右端推入元素,那么最后一個分配的索引就會逐漸增大,並且新分片的 ID 也會變得越來越大。如果程序總是從右端彈出元素,並從左端推入元素,那么第一個分片的索引就會逐漸減少,並且新分片的 ID 也會變得越來越小。 P264

當分片列表包含多個列表時,位於分片兩端的列表可能是被填滿的,但位於兩端之間的其他列表總是被填滿的。 P264

將元素推入分片列表 P265

Lua 腳本根據命令 LPUSH/RPUSH 找到列表的第一個分片或者最后一個分片,然后將元素推入分片對應的列表中,若分片已達個數上限(可以取配置中的 list-max-ziplist-entries 的值 - 1 作為上限),則會自動產生一個新的分片,繼續推入,並更新第一個分片或者最后一個分片的分片 ID 。當推入操作執行完畢后,它會返回被推入元素的數量。 P265

從分片里面彈出元素 P266

Lua 腳本根據命令 LPOP/RPOP 找到列表的第一個分片或者最后一個分片,然后在分片非空的情況下,從分片里面彈出一個元素,如果列表在執行彈出操作之后不再包含任何元素,那么程序就對記錄着列表兩端分片信息的字符串鍵進行修改(注意只有列表端分片為空時才修改對應的字符串鍵,而整個列表為空時,不做調整) P267

對分片列表執行阻塞彈出操作 P267

這一段書上講得看不懂,也不知道為什么需要書中的花式操作才能完成。

個人覺得分片列表的阻塞彈出其實並不需要列表自身的阻塞彈出,我們可以不斷執行上述 Lua 腳本實現的彈出元素的操作,若彈出成功,則直接返回,若彈出失敗,則睡 1 ms 后繼續執行彈出操作,直至彈出成功或者達到超時時間。這樣我們對 Redis 對操作只在 Lua 腳本中,原子性保證了一定會彈出分片列表兩端的元素。

本文首發於公眾號:滿賦諸機(點擊查看原文) 開源在 GitHub :reading-notes/redis-in-action


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM