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

簡介

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

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

經過使用 Lua 對 Redis 進行腳本編程,咱們能夠避免一些減慢開發速度或者致使性能降低對常見陷阱。 P248github

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

因爲 Lua 的數據傳入和傳出限制, Lua 與 Redis 須要進行相互轉換。由於腳本在返回各類不一樣類型的數據時可能會產生含糊不清的結果,因此咱們應該儘可能顯式的返回字符串。 P250redis

咱們能夠在 官方文檔 中找到 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

相關文章
相關標籤/搜索