簡介
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
,並用 PX
和 NX
選項即可在鍵不存在的時候設置帶過期時間的值。釋放鎖時為了保證釋放的時自己獲取的鎖,需要使用 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