1. 程式人生 > 其它 >Redis 管道、事務、Lua 指令碼對比

Redis 管道、事務、Lua 指令碼對比

技術標籤:redis

概述

Redis 提供三種將客戶端多條命令打包傳送給服務端執行的方式: Pipelining(管道)Transactions(事務)Lua Scripts(Lua 指令碼)。本文不會過細的討論三種方式的基礎知識,將從這三種方式的 優勢侷限性原子性 方面展開討論

Pipelining(管道)

Redis 管道是三者之中最簡單的,當客戶端需要執行多條 redis 命令時,可以通過管道一次性將要執行的多條命令傳送給服務端,其作用是為了降低 RTT(Round Trip Time) 對效能的影響,比如我們使用 nc 命令將兩條指令傳送給 redis

服務端

$ printf "INCR x\r\nINCR x\r\n" | nc localhost 6379
:1
:2

可以看到,管道只是簡單的將多個命令拼接在一起,命令之間用換行符(/r/n)分割,並沒有在第一條命令前或最後一條命令後面新增開始/結束標誌位

redis 服務端接收到管道傳送過來的多條命令後,會一直執命令,並將命令的執行結果進行快取,直到最後一條命令執行完成,再所有命令的執行結果一次性返回給客戶端

Pipelining 的優勢

在效能方面, Pipelining 有下面兩個優勢:

  • 將多條命令打包一次性發送給服務端,減少了客戶端與服務端之間的網路呼叫次數,節省了 RTT
  • 避免了上下文切換,當客戶端/服務端需要從網路中讀寫資料時,都會產生一次系統呼叫,系統呼叫是非常耗時的操作,其中設計到程式由使用者態切換到核心態,再從核心態切換回使用者態的過程。當我們執行 10 條 redis 命令的時候,就會發生 10 次使用者態到核心態的上下文切換,但如果我們使用 Pipeining 將多條命令打包成一條一次性發送給服務端,就只會產生一次上下文切換

Pipelining 原子性

我們都知道, redis 執行命令的時候是單執行緒執行的,所以 redis 中的所有命令都具備原子性,這意味著 redis 並不會在執行某條命令的中途停止去執行另一條命令

但是 Pipelining 並不具備原子性,想象一下有兩個客戶端 client1

client2 同時向 redis 服務端傳送 Pipelining 命令,每條 Pipelining 包含 5 條 redis 命令。 redis 可以保證 client1 管道中的命令始終是順序執行的, client2 管道中的命令也是一樣,始終按照管道中傳入的順序執行命令

但是 redis 並不能保證等 client1 管道中的所有命令執行完成,再執行 client2 管道中的命令,因此,在服務端中的命令執行順序有可能是下面這種情況
![image.png](https://img-blog.csdnimg.cn/img_convert/29af8d8a442f82b89a9d26b0d1d72a15.png#align=left&display=inline&height=136&margin=[object Object]&name=image.png&originHeight=136&originWidth=369&size=5184&status=done&style=none&width=369)
這種行為顯示 Pipelining 在執行的時候並不會阻塞服務端。即使 client1 向客戶端傳送了包含多條指令的 Pipelining ,其他客戶端也不會被阻塞,因為他們傳送的指令可以插入到 Pipelining 中間執行

Pipelining 侷限性

只有在 Pipelining 內所有命令執行完後,服務端才會把執行結果通過陣列的方式返回給客戶端。在執行 Pipelining 內的命令的時候,如果某些指令執行失敗, Pipelining 仍會繼續執行

比如下面的例子

$ printf "SET name huangxy\r\nINCR name\r\nGET name\r\n" | nc localhost 6379
+OK
-ERR value is not an integer or out of range
$6
huangxy

Pipelining 中第二條指令執行失敗, Pipelining 並不會停止,而是會繼續執行,等所有命令都執行完的時候,再將結果返回給客戶端,其中第二條指令返回的是錯誤資訊

Pipelining 的這個特性會導致一個問題,就是當 Pipelining 中的指令需要讀取之前指令設定 key 的時候,需要額外小心,因為 key 的值有可能會被其他客戶端修改。此時 Pipelining 的執行結果往往就不是我們所預期的

Pipelining 使用場景

  • 對效能有要求
  • 需要傳送多個指令到服務端
  • 不需要上個命令的返回結果作為下個命令的輸入

Transactions(事務)

redis 中的事務,跟我們之前在學關係型資料庫的時候所瞭解到的事務概念有點區別。 redis 中的事務機制主要是用來對多個命令進行排隊,並在最後決定是否需要執行事務中的所有命令與否

與管道不同,事務使用特殊的命令來標記事務的開始和結束( MULTIEXECDISCARD )。伺服器還可以對事務中的命令進行排隊(這樣客戶端可以一次傳送一條命令)。除此之外,一些第三方庫還喜歡在客戶端中對事務的命令進行快取,然後通過在管道中傳送整個事務的方式對其進行優化

事務的優點

事務提供了 WATCH 命令,使我們可以實現 CAS 功能,比如通過事務,我們可以實現跟 INCR 命令一樣的功能

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

事務的原子性

redis 事務具備原子性,當一個事務正在執行時,服務端會阻塞其接收到的其他命令,只有在事務執行完成時,才會執行接下來的命令,因此事務具備原子性

事務的侷限性

Pipelining 一樣,只有在事務執行完成時,才會把事務中多個命令的結果一併返回給客戶端,因此客戶端在事務還沒有執行完的時候,無法獲取其命令的執行結果

如果事務中的其中一個命令發生錯誤,會有以下兩種可能性:

  • 當發生語法錯誤,在執行 EXEC 命令的時候,事務將會被丟棄,不會執行
  • 當發生執行時錯誤(操作了錯誤的資料型別)時, redis 會將報錯資訊快取起來,繼續執行後面的命令,並在最後將所有命令的執行結果返回給客戶端(報錯資訊也會返回)。這意味著 redis 事務中沒有回滾機制

事務使用場景

  • 需要原子地執行多個命令
  • 不需要事務中間命令的執行結果來編排後面的命令

Lua 指令碼

redis 從 2.6 版本開始引入對 Lua 指令碼的支援,通過在伺服器中嵌入 Lua 環境, redis 客戶端可以直接使用 Lua 指令碼,在服務端原子地執行多個 redis 命令

Lua 指令碼的優勢

Pipelining 和 事務不同的是,在指令碼內部,我們可以在指令碼中獲取中間命令的返回結果,然後根據結果值做相應的處理(如 if 判斷)

local key = KEYS[1]
local new = ARGV[1]

local current = redis.call('GET', key)
if (current == false) or (tonumber(new) < tonumber(current)) then
  redis.call('SET', key, new)
  return 1
else
  return 0
end

同時, redis 服務端還支援對 Lua 指令碼進行快取(使用 SCRIPT LOADEVAL 執行過的指令碼服務端都會對其進行快取),下次可以使用 EVALSHA 命令呼叫快取的指令碼,節省頻寬

Lua 指令碼的原子性

Lua 指令碼跟事務一樣具備原子性,當指令碼執行中時,服務端接收到的命令會被阻塞

Lua 指令碼的侷限性

Lua 指令碼在功能上沒有過多的限制,但要注意的一點是,Lua 指令碼在執行的時候,會阻塞其他命令的執行,所以不宜在指令碼中寫太耗時的處理邏輯

Lua 指令碼的使用場景

  • 需要原子性地執行多個命令
  • 需要中間值來組合後面的命令
  • 需要中間值來編排後面的命令
  • 常用於擴充套件 redis 功能,實現符合自己業務場景的命令

參考文件

掃碼關注我
一起學習,一起進步

關注公眾號:huangxy