Redis 實戰 —— 14. Redis 的 Lua 指令碼程式設計
阿新 • • 發佈:2021-02-04
#### 簡介
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`
我們可以在 [官方文件](https://redis.io/commands/eval#conversion-between-lua-and-redis-data-types) 中找到 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 指令碼實現。相關程式碼已在 [實現自動補全、分散式鎖和計數訊號量](08.%20實現自動補全、分散式鎖和計數訊號量.md) 中實現。
#### 移除 `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`
組成分片列表的每個分片都會被命