批量執行 Redis 命令的 4 種方式!
前言
在我們的印象中 Redis 命令好像都是一個個單條進行執行的,
如果有人問你如何批量執行 Redis 命令,你能回答的上嗎,或者說能答出幾種方式呢?
最容易想到的是 Redis 的一些批量命令,例如 MGET
今天小許就這個問題給大家總結一下!
Redis 命令執行過程
在瞭解批量執行有哪些方式之前,我們簡單回顧下 Redis 命令執行的過程:
爲什麼需要批量執行命令呢?
在瞭解批量執行命令有哪些方式之前,我們先簡單整理下【批量執行命令】比【執行多個單 Redis 命令】能帶來哪些好處!
通過批量執行命令好處如下:
-
• 提高命令執行效率:減少網絡延遲,提高 Redis 服務器的響應速度
-
• 簡化客戶端邏輯:將多個命令封裝成一個操作,簡化客戶端處理邏輯
-
• 提升事務性能:可以保證一組命令在同一時間內執行,提高事務的性能
你看單個執行命令每次都需要發送進行網絡傳輸,同樣多的執行,批量執行可以有效減小網絡開銷,減少 RTT(往返時間)。
批量執行命令的方式
有以下四種常見批量執行命令的方式:
-
- Redis 原生命令:例如 MSET、HMGET、HMSET、SADD
-
2. pipeline(管道)
-
3. Lua 腳本
-
4. Redis 事務
我們來給每種方式簡單舉個栗子,然後看看有什麼需要注意的地方!
原生批量命令
Redis 的原生命令就支持批量命令的操作,比如:HMSET、HMGET、SADD。
其實嚴格來說上述命令不屬於批量操作,而是在一個指令中處理多個 key,我們來看下具體該如何使用。
String 字符串
MSET:設置一個或多個指定 key 的值
MGET:從一個或多個指定的 key 中獲取值
MSET key value [key value ...]
MGET key [key ...]
Hash 哈希
操作哈希類型時,使用 HMSET 和 HMGET 命令分別設置和獲取多個字段及其值
HMSET:將一個或多個 field-value 對設置到指定哈希表中
HMGET:從指定指定哈希表中一個或者多個字段的值
HMSET key field value [field value ...]
HMGET key field [field ...]
Sorted Set 有序集合
SADD 可以將多個元素添加到有序集合
SADD key member [member ...]
📢 注意
🚩 Redis Cluster 中 MGET 操作可能無法保證原子性!
因爲在 Redis Cluster 中,MGET 操作涉及多個鍵的讀取操作,並且這些鍵無法保證所有的 key 都在同一個 hash slot(哈希槽)上。
而 Redis Cluster 的節點間可能會有網絡延遲和不同的負載情況,MGET 操作不能保證在同一時刻原子地獲取所有鍵的值。
不過相較於非批量操作,這些指令可以節省不少網絡傳輸次數,畢竟不用發送一次命令,服務器響應一次。
pipeline(管道)
Redis Pipeline(管道)命令是一種優化網絡通信的技術,可以將多個命令一次性發送給 Redis 服務器,可以減少客戶端與 Redis 服務器之間的網絡通信次數。
客戶端將多個命令發送到 Redis 服務器,Redis 服務器將這些命令緩存起來,然後一次性執行,最後將執行結果一次性返回給客戶端。
使用 Redis Pipeline 好處很明顯,可以避免在每個命令執行時都進行一次網絡通信,時間開銷變爲:
🚩 1 次 pipeline(n 條命令) = 1 次網絡時間 + 執行 n 條命令時間
使用
這裏用 Golang 語言看看如何使用 pipeline , 從代碼中可以看出需要服務端和客戶端的共同實現,不像原生批量命令一樣 Redis 直接支持實現。
package main
import (
"github.com/go-redis/redis"
)
func main() {
pipe := client.Pipeline()
defer pipe.Close()
// 封裝 pipeline待執行命令
set := pipe.Set("key", "value", 0)
get := pipe.Get("key")
// 執行 pipeline
_, err := pipe.Exec()
if err != nil {
panic(err)
}
// 獲取 pipeline執行結果
val, err := get.Result()
if err != nil {
panic(err)
}
}
📢 注意
🚩 1:Redis Cluster 中 Pipeline 命令操作可能無法保證原子性!
因爲 Redis Cluster 採用的分片機制,這些鍵無法保證所有的 key 都在同一區域 hash slot(哈希槽)上,所以不同的命令可能會發送到不同的節點上。
這意味着即使你使用 Pipeline,每個命令仍然在不同的節點上進行處理,可能會導致多個命令的執行不是在同一時刻進行的。
🚩 2:pipeline 能執行有依賴關係的命令嗎?
答案是不可以的,如果 pipeline 中後一個命令的執行需要依賴前一個命令的執行結果,就沒辦法滿足需求了。
🚩 3:pipeline 對發送的命令有數量限制嗎?
雖然命令可以一次性發給 Redis 服務端,但是考慮帶寬等情況,建議不多於 500 個命令,或者根據實際命令的數據類型定。
爲了保證更高的一致性和原子性,就需要考慮使用其他方式,比如 Lua 腳本、事務的方式了,我們繼續往下看!
Lua 腳本
我們知道 Redis 支持使用 Lua 腳本來執行自定義的複雜邏輯,因此使用 Lua 腳本,我們可以在 Redis 服務器端執行多個命令。
而且 Lua 腳本具有原子性,即腳本中的所有命令會在同一時間內執行,不會被其他命令打斷。
使用
在 Redis 中使用 EVAL 命令使用 Lua 解釋器執行腳本,語法如下:
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]
-
• script:要執行的 Lua 腳本
-
• numkeys:腳本中涉及到的鍵的數量
-
• key 和 arg:腳本中的鍵和參數
📢 注意
Redis Cluster 下 Lua 腳本的原子操作同樣無法操作,原因也是無法保證所有的 key 都在同一個 hash slot(哈希槽)上。
Redis 事務
Redis 事務(Transaction)通過將多個 Redis 操作封裝爲一個原子性的操作序列,確保在事務執行過程中,不會受到其他客戶端的干擾。
🚩 比起原生命令和 pipeline 批量執行方式,事務的執行具備原子性,即全部被執行或全部不執行,並且在持久化時也具備原子性。
使用
Redis 事務使用以下三個命令進行操作:
-
• MULTI:標記事務開始
-
• EXEC:執行所有在 MULTI 之後的命令
-
• DISCARD:取消事務
用過數據庫事務的對這幾個命令也很容易理解,MULTI 和 EXEC 之間的所有命令將作爲一個整體被執行。這些命令會被放入隊列中,等待 EXEC 命令的調用,一旦 EXEC 命令被調用,所有的命令將按照順序被執行。
📢 注意
Redis Cluster 支持 transaction,但是前提是 transaction 涉及的所有 key 都屬於同一 hash slot
所有需要被事務處理的鍵必須分佈在同一個節點上
👉 Redis Cluster 模式下該如何正確使用批量命令操作?
通過對上面四種方式的總結,可以發現在 Redis Cluster 模式下會存在 key 可能不屬於同一個節點的 hash slot(哈希槽)上,導致不能按實際想的方式去執行。
小許查了下也有一些解決方式,看下是否適合你。
✏️ hash-tag 方式:
Redis Cluster 模式一般都是支持 hash-tag 功能,它可以將多個 key 強制分配到一個節點上,它的操作時間 =1 次網絡時間 +n 次命令時間。
這種方式雖然性能高,可能會因爲不均衡問題導致 Redis Cluster 部分節點負載過高。
✏️ 維護 Hash Slot 映射關係:
因爲主要問題在於,不能讓所有的 key 在同一個節點上執行,那麼我們在客戶端維護一個 key 和 slot 的映射關係,是不是就讓 key 固定在了一個節點的 hash slot 執行了!
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/HflvMVCvA5LNpgIHF04WNw