批量執(zhí)行Redis命令的四種方式!
前言
在我們的印象中Redis命令好像都是一個(gè)個(gè)單條進(jìn)行執(zhí)行的,
如果有人問你如何批量執(zhí)行Redis命令,你能回答的上嗎,或者說能答出幾種方式呢?
最容易想到的是Redis的一些批量命令,例如MGET
今天小許就這個(gè)問題給大家總結(jié)一下!
圖片
Redis命令執(zhí)行過程
在了解批量執(zhí)行有哪些方式之前,我們簡(jiǎn)單回顧下Redis命令執(zhí)行的過程:
圖片
為什么需要批量執(zhí)行命令呢?
在了解批量執(zhí)行命令有哪些方式之前,我們先簡(jiǎn)單整理下【批量執(zhí)行命令】比【執(zhí)行多個(gè)單Redis命令】能帶來(lái)哪些好處!
通過批量執(zhí)行命令好處如下:
- ? 提高命令執(zhí)行效率:減少網(wǎng)絡(luò)延遲,提高Redis服務(wù)器的響應(yīng)速度
- ? 簡(jiǎn)化客戶端邏輯:將多個(gè)命令封裝成一個(gè)操作,簡(jiǎn)化客戶端處理邏輯
- ? 提升事務(wù)性能:可以保證一組命令在同一時(shí)間內(nèi)執(zhí)行,提高事務(wù)的性能
圖片
你看單個(gè)執(zhí)行命令每次都需要發(fā)送進(jìn)行網(wǎng)絡(luò)傳輸,同樣多的執(zhí)行,批量執(zhí)行可以有效減小網(wǎng)絡(luò)開銷,減少 RTT(往返時(shí)間)。
批量執(zhí)行命令的方式
有以下四種常見批量執(zhí)行命令的方式:
1. Redis原生命令:例如 MSET、HMGET、HMSET、SADD
2. pipeline(管道)
3. Lua腳本
4. Redis事務(wù)
圖片
我們來(lái)給每種方式簡(jiǎn)單舉個(gè)栗子,然后看看有什么需要注意的地方!
原生批量命令
Redis的原生命令就支持批量命令的操作,比如:HMSET、HMGET、SADD。
其實(shí)嚴(yán)格來(lái)說上述命令不屬于批量操作,而是在一個(gè)指令中處理多個(gè)key,我們來(lái)看下具體該如何使用。
String字符串
MSET:設(shè)置一個(gè)或多個(gè)指定 key 的值
MGET:從一個(gè)或多個(gè)指定的key中獲取值
MSET key value [key value ...]
MGET key [key ...]
Hash哈希
操作哈希類型時(shí),使用HMSET和HMGET命令分別設(shè)置和獲取多個(gè)字段及其值
HMSET:將一個(gè)或多個(gè) field-value 對(duì)設(shè)置到指定哈希表中
HMGET:從指定指定哈希表中一個(gè)或者多個(gè)字段的值
HMSET key field value [field value ...]
HMGET key field [field ...]
Sorted Set 有序集合
SADD可以將多個(gè)元素添加到有序集合
SADD key member [member ...]
?? 注意
?? Redis Cluster中MGET操作可能無(wú)法保證原子性!
因?yàn)樵?Redis Cluster 中,MGET操作涉及多個(gè)鍵的讀取操作,并且這些鍵無(wú)法保證所有的 key 都在同一個(gè) hash slot(哈希槽)上。
而Redis Cluster 的節(jié)點(diǎn)間可能會(huì)有網(wǎng)絡(luò)延遲和不同的負(fù)載情況,MGET 操作不能保證在同一時(shí)刻原子地獲取所有鍵的值。
不過相較于非批量操作,這些指令可以節(jié)省不少網(wǎng)絡(luò)傳輸次數(shù),畢竟不用發(fā)送一次命令,服務(wù)器響應(yīng)一次。
pipeline(管道)
Redis Pipeline(管道)命令是一種優(yōu)化網(wǎng)絡(luò)通信的技術(shù),可以將多個(gè)命令一次性發(fā)送給Redis服務(wù)器,可以減少客戶端與Redis服務(wù)器之間的網(wǎng)絡(luò)通信次數(shù)。
圖片
客戶端將多個(gè)命令發(fā)送到Redis服務(wù)器,Redis服務(wù)器將這些命令緩存起來(lái),然后一次性執(zhí)行,最后將執(zhí)行結(jié)果一次性返回給客戶端。
使用Redis Pipeline好處很明顯,可以避免在每個(gè)命令執(zhí)行時(shí)都進(jìn)行一次網(wǎng)絡(luò)通信,時(shí)間開銷變?yōu)椋?/p>
?? 1 次 pipeline(n條命令) = 1 次網(wǎng)絡(luò)時(shí)間 + 執(zhí)行n 條命令時(shí)間
使用
這里用Golang語(yǔ)言看看如何使用pipeline , 從代碼中可以看出需要服務(wù)端和客戶端的共同實(shí)現(xiàn),不像原生批量命令一樣Redis直接支持實(shí)現(xiàn)。
package main
import (
"github.com/go-redis/redis"
)
func main() {
pipe := client.Pipeline()
defer pipe.Close()
// 封裝 pipeline待執(zhí)行命令
set := pipe.Set("key", "value", 0)
get := pipe.Get("key")
// 執(zhí)行 pipeline
_, err := pipe.Exec()
if err != nil {
panic(err)
}
// 獲取 pipeline執(zhí)行結(jié)果
val, err := get.Result()
if err != nil {
panic(err)
}
}
?? 注意
?? 1:Redis Cluster中Pipeline命令操作可能無(wú)法保證原子性!
因?yàn)?Redis Cluster 采用的分片機(jī)制,這些鍵無(wú)法保證所有的 key 都在同一區(qū)域hash slot(哈希槽)上,所以不同的命令可能會(huì)發(fā)送到不同的節(jié)點(diǎn)上。
這意味著即使你使用 Pipeline,每個(gè)命令仍然在不同的節(jié)點(diǎn)上進(jìn)行處理,可能會(huì)導(dǎo)致多個(gè)命令的執(zhí)行不是在同一時(shí)刻進(jìn)行的。
?? 2:pipeline 能執(zhí)行有依賴關(guān)系的命令嗎?
答案是不可以的,如果pipeline中后一個(gè)命令的執(zhí)行需要依賴前一個(gè)命令的執(zhí)行結(jié)果,就沒辦法滿足需求了。
?? 3:pipeline對(duì)發(fā)送的命令有數(shù)量限制嗎?
雖然命令可以一次性發(fā)給Redis服務(wù)端,但是考慮帶寬等情況,建議不多于500個(gè)命令,或者根據(jù)實(shí)際命令的數(shù)據(jù)類型定。
為了保證更高的一致性和原子性,就需要考慮使用其他方式,比如Lua腳本、事務(wù)的方式了,我們繼續(xù)往下看!
Lua腳本
我們知道Redis支持使用Lua腳本來(lái)執(zhí)行自定義的復(fù)雜邏輯,因此使用Lua腳本,我們可以在Redis服務(wù)器端執(zhí)行多個(gè)命令。
而且Lua腳本具有原子性,即腳本中的所有命令會(huì)在同一時(shí)間內(nèi)執(zhí)行,不會(huì)被其他命令打斷。
使用
在Redis中使用EVAL命令使用 Lua 解釋器執(zhí)行腳本,語(yǔ)法如下:
redis 127.0.0.1:6379> EVAL script numkeys key [key ...] arg [arg ...]
- ? script:要執(zhí)行的Lua腳本
- ? numkeys:腳本中涉及到的鍵的數(shù)量
- ? key和arg:腳本中的鍵和參數(shù)
?? 注意
Redis Cluster 下 Lua 腳本的原子操作同樣無(wú)法操作,原因也是無(wú)法保證所有的 key 都在同一個(gè) hash slot(哈希槽)上。
Redis事務(wù)
Redis事務(wù)(Transaction)通過將多個(gè)Redis操作封裝為一個(gè)原子性的操作序列,確保在事務(wù)執(zhí)行過程中,不會(huì)受到其他客戶端的干擾。
?? 比起原生命令和pipeline批量執(zhí)行方式,事務(wù)的執(zhí)行具備原子性,即全部被執(zhí)行或全部不執(zhí)行,并且在持久化時(shí)也具備原子性。
使用
Redis事務(wù)使用以下三個(gè)命令進(jìn)行操作:
- ? MULTI:標(biāo)記事務(wù)開始
- ? EXEC:執(zhí)行所有在MULTI之后的命令
- ? DISCARD:取消事務(wù)
用過數(shù)據(jù)庫(kù)事務(wù)的對(duì)這幾個(gè)命令也很容易理解,MULTI和EXEC之間的所有命令將作為一個(gè)整體被執(zhí)行。這些命令會(huì)被放入隊(duì)列中,等待EXEC命令的調(diào)用,一旦EXEC命令被調(diào)用,所有的命令將按照順序被執(zhí)行。
?? 注意
Redis Cluster支持transaction,但是前提是transaction涉及的所有key都屬于同一hash slot
所有需要被事務(wù)處理的鍵必須分布在同一個(gè)節(jié)點(diǎn)上
?? Redis Cluster模式下該如何正確使用批量命令操作?
通過對(duì)上面四種方式的總結(jié),可以發(fā)現(xiàn)在Redis Cluster模式下會(huì)存在key可能不屬于同一個(gè)節(jié)點(diǎn)的hash slot(哈希槽)上,導(dǎo)致不能按實(shí)際想的方式去執(zhí)行。
小許查了下也有一些解決方式,看下是否適合你。
?? hash-tag方式:
Redis Cluster模式一般都是支持 hash-tag 功能,它可以將多個(gè) key 強(qiáng)制分配到一個(gè)節(jié)點(diǎn)上,它的操作時(shí)間 =1 次網(wǎng)絡(luò)時(shí)間 +n 次命令時(shí)間。
這種方式雖然性能高,可能會(huì)因?yàn)椴痪鈫栴}導(dǎo)致Redis Cluster部分節(jié)點(diǎn)負(fù)載過高。
?? 維護(hù)Hash Slot映射關(guān)系:
因?yàn)橹饕獑栴}在于,不能讓所有的key在同一個(gè)節(jié)點(diǎn)上執(zhí)行,那么我們?cè)诳蛻舳司S護(hù)一個(gè)key和slot的映射關(guān)系,是不是就讓key固定在了一個(gè)節(jié)點(diǎn)的hash slot執(zhí)行了!
如有不對(duì)的地方還請(qǐng)朋友們指出!