1. 管道 (Pipelining)
-
網絡延遲 (Round-Trip Time - RTT) 瓶頸。
-
在傳統模式下,客戶端發送一個命令 -> 等待 Redis 服務器處理并返回結果 -> 再發送下一個命令。如果客戶端需要執行大量命令(例如設置或獲取多個鍵),每個命令之間的網絡往返時間(RTT)會成為主要的性能瓶頸,即使每個命令本身執行很快。
-
-
原理:
-
客戶端一次性打包多個命令發送給 Redis 服務器。
-
Redis 服務器按順序連續執行收到的所有命令。
-
服務器執行完所有命令后,一次性將所有結果打包返回給客戶端。
-
-
關鍵特性:
-
性能優化:?核心目標是極大減少網絡 RTT 次數。將 N 次 RTT 減少為 1 次 RTT(發送請求包 + 接收響應包),顯著提升吞吐量,尤其適合批量寫入或讀取。
-
非原子性:?管道中的命令雖然是一起發送和返回的,但在執行過程中,命令之間并不是原子的。Redis 會按順序依次執行每個命令。其他客戶端的命令可能穿插在管道中某個命令執行的前后。
-
無回滾:?如果管道中某個命令執行出錯(例如語法錯誤、對錯誤類型操作),它不會影響其他命令的執行,也不會導致整個管道回滾。客戶端在收到所有結果后需要自行檢查每個命令的返回結果。
-
-
使用方式:
-
客戶端庫通常提供管道支持(如 Python 的?
redis-py
?使用?pipeline()
?方法)。 -
在 Redis CLI 中,可以手動將多個命令寫在一行(用分號?
;
?分隔)或使用?echo
?和?nc
?組合。
-
-
適用場景:
-
需要執行大量獨立、無依賴關系的命令,且對原子性沒有要求。
-
批量設置(
MSET
)、批量獲取(MGET
?雖然原生支持,但復雜鍵名時仍需管道)、批量刪除(DEL key1 key2 ...
)。 -
數據導入/導出。
-
需要最大化吞吐量的場景(如實時計數器批量更新)。
-
-
優點:
-
大幅提升吞吐量,減少網絡延遲影響。
-
使用相對簡單。
-
-
缺點:
-
不保證原子性:?命令可能被其他客戶端插入。
-
不提供隔離性:?管道執行過程中,其他客戶端可以修改數據。
-
錯誤處理:?需要客戶端在收到響應后逐一檢查每個命令的結果。
-
2. 事務 (Transactions)
-
簡單命令序列的原子性執行需求。
-
確保一組命令在?
EXEC
?時作為一個整體連續執行(執行原子性),但運行時錯誤不會回滾已執行的命令。
-
-
核心命令:
-
MULTI
: 標記事務開始。之后的命令不會立即執行,而是被放入一個隊列。 -
EXEC
: 執行事務隊列中的所有命令。Redis 會按順序、連續地、原子地執行隊列中的所有命令。在執行?EXEC
?期間,服務器不會被其他客戶端的命令打斷。 -
DISCARD
: 放棄事務,清空隊列。 -
WATCH key [key ...]
:?關鍵機制!?在?MULTI
?之前執行。監視一個或多個鍵。如果在?WATCH
?之后、EXEC
?之前,有任何被監視的鍵被其他客戶端修改,那么當該客戶端執行?EXEC
?時,整個事務將被取消(返回?nil
)。這是 Redis 實現 CAS(Compare-and-Set)樂觀鎖的基礎。
-
-
關鍵特性:
-
原子性:?
EXEC
?命令觸發時,隊列中的所有命令作為一個整體執行,不會被其他命令打斷。 -
隔離性:?通過?
WATCH
?實現樂觀鎖,可檢測并發修改,但事務本身無隔離性保證。 -
無回滾:?這是 Redis 事務的一個重要特點!?如果在事務執行過程中(
EXEC
?之后)某個命令運行時出錯(例如對字符串執行?HINCRBY
),只有出錯的命令不會生效,而隊列中其他命令依然會被執行!?Redis 不會回滾已經執行成功的命令。事務的錯誤通常發生在入隊時(命令語法錯誤,Redis 會拒絕入隊)或運行時(數據類型錯誤)。
-
-
執行流程:
-
WATCH key
?(可選,用于樂觀鎖) -
MULTI
-
發送要執行的命令 (這些命令被放入隊列,返回?
QUEUED
) -
EXEC
?或?DISCARD
-
如果執行?
EXEC
:-
檢查?
WATCH
?的鍵是否被修改過?是 -> 放棄執行,返回?(nil)
。 -
否 -> 按順序原子執行所有隊列命令,返回所有命令的結果數組。
-
-
-
-
適用場景:
-
需要保證一組命令原子執行的簡單場景(例如:轉賬?
A-100; B+100
)。 -
結合?
WATCH
?實現樂觀鎖,處理簡單的并發競爭(例如:庫存扣減、搶票)。
-
-
優點:
-
提供命令序列的原子性保證。
-
WATCH
?提供了基本的并發控制手段。
-
-
缺點:
-
運行時錯誤不回滾:?事務執行中部分命令失敗,其他命令仍然生效。需要客戶端精心設計命令或依賴?
WATCH
?重試。 -
無法獲取中間結果:?在事務中(
MULTI
?之后),無法直接獲取之前命令的執行結果來決定后續操作(所有命令在?EXEC
?時一次性執行)。命令是靜態入隊的。 -
性能:?雖然?
MULTI/EXEC
?本身開銷不大,但?WATCH
?失敗重試可能導致性能下降。EXEC
?執行期間會阻塞其他命令。 -
復雜性:?需要理解?
WATCH
?的機制和錯誤處理邏輯。
-
3. Lua 腳本 (Lua Scripting)
-
?復雜原子操作、需要中間邏輯判斷、事務的局限性。
-
事務無法在命令執行過程中根據中間結果做動態決策。
-
事務的錯誤回滾行為不符合某些預期。
-
需要執行更復雜的邏輯,這些邏輯無法簡單地拆分成一組 Redis 命令序列。
-
-
原理:
-
Redis 內嵌了?Lua 5.1?解釋器。
-
客戶端將一段?Lua 腳本發送給 Redis 服務器(使用?
EVAL
?或?EVALSHA
)。 -
Redis 服務器在單個線程中原子性地執行整個 Lua 腳本。
-
腳本執行期間,服務器不會執行任何其他命令或腳本(完全隔離)。
-
腳本可以訪問和操作 Redis 數據,可以包含復雜的邏輯(條件判斷、循環、計算等)。
-
腳本最后返回一個結果給客戶端。
-
-
關鍵特性:
-
原子性:?核心優勢!?整個腳本的執行是原子的、隔離的。腳本執行過程中不會有其他命令或腳本執行,腳本看到的數據視圖在執行開始時是確定的。
-
靈活性:?可以使用 Lua 語言的全部特性(變量、條件、循環、函數、表等)實現復雜的業務邏輯。
-
可獲取中間結果:?腳本內部可以執行多個 Redis 命令(通過?
redis.call()
?或?redis.pcall()
),并能立即獲取這些命令的返回值,用于后續的邏輯判斷和計算。這是超越事務的關鍵點。 -
redis.call()
: 執行 Redis 命令,如果命令出錯會拋出 Lua 錯誤,導致整個腳本停止執行(類似事務中的運行時錯誤)。 -
redis.pcall()
: 執行 Redis 命令,如果命令出錯會捕獲錯誤并以 Lua 表的形式返回錯誤信息,不會中斷腳本執行,腳本可以處理這個錯誤。 -
復用性:?腳本可以被緩存(使用?
SCRIPT LOAD
?返回 SHA1 摘要),后續通過?EVALSHA
?用摘要執行,減少網絡傳輸。
-
-
使用方式:
-
EVAL "lua_script" numkeys key [key ...] arg [arg ...]
-
lua_script
: Lua 腳本字符串。 -
numkeys
: 后面跟著的鍵名的個數。 -
key [key ...]
: 腳本中用到的 Redis 鍵名,通過?KEYS[1]
,?KEYS[2]
?訪問。 -
arg [arg ...]
: 傳遞給腳本的附加參數,通過?ARGV[1]
,?ARGV[2]
?訪問。
-
-
SCRIPT LOAD "lua_script"
: 加載腳本并返回 SHA1 摘要。 -
EVALSHA sha1 numkeys key [key ...] arg [arg ...]
: 使用 SHA1 摘要執行緩存的腳本。 -
SCRIPT EXISTS sha1 [sha1 ...]
: 檢查腳本是否已緩存。 -
SCRIPT FLUSH
: 清空腳本緩存。 -
SCRIPT KILL
: 終止當前正在執行的腳本(僅當腳本未執行任何寫操作時有效)。
-
-
適用場景:
-
需要真正原子性的復雜操作:?例如:檢查庫存、扣減庫存、記錄訂單;實現分布式鎖(Redlock 或更復雜的鎖);實現自定義的原子計數器/限流器。
-
需要基于中間結果做決策:?例如:如果?
HGET
?的值大于 X,則執行?ZADD
?和?PUBLISH
。 -
封裝復雜操作:?將多個 Redis 命令和業務邏輯封裝成一個原子操作,簡化客戶端代碼。
-
保證計算與操作原子性:?在腳本中進行計算并基于計算結果更新 Redis。
-
-
優點:
-
真正的原子性和隔離性:?腳本是執行 Redis 命令的最小單元。
-
強大的靈活性:?幾乎可以實現任何復雜的原子邏輯。
-
可獲取中間結果:?腳本內部可基于之前命令的返回值進行決策。
-
復用性:?
EVALSHA
?減少網絡開銷。 -
性能:?腳本在服務器端執行,避免了多次網絡 RTT(雖然單次?
EVAL
?請求可能比單個命令大,但比多次 RTT 好)。腳本執行很快(Redis 單線程高效)。
-
-
缺點/注意事項:
-
腳本編寫:?需要學習 Lua 語法和 Redis Lua API (
redis.call
,?redis.pcall
)。 -
調試:?Redis Lua 腳本調試相對困難。
-
阻塞風險:?非常重要!?一個運行緩慢的 Lua 腳本會阻塞整個 Redis 服務器,導致所有其他客戶端請求超時。必須確保腳本是高效的、執行時間可預測的。避免長循環、避免執行大量耗時的命令、避免使用?
KEYS
?命令。 -
可復制性:?Lua 腳本在復制和持久化時有一些特殊規則(通常腳本本身和效果都會被復制)。
-
資源管理:?腳本緩存需要管理(雖然 Redis 會管理,但?
SCRIPT FLUSH
?需謹慎)。 -
錯誤處理:?需要理解?
redis.call
?和?redis.pcall
?的錯誤處理差異。腳本中的邏輯錯誤可能導致整個操作失敗(原子性保證)。
-
總結對比表
特性 | 管道 (Pipeline) | 事務 (Transaction) | Lua 腳本 (Lua Scripting) |
---|---|---|---|
核心目標 | 優化性能 (減少 RTT) | 保證隔離性 (命令連續執行) | 原子性執行復雜邏輯 |
發送方式 | ? 批量發送 | ? 逐條發送(MULTI +命令) | ? 一次性發送(EVAL /EVALSHA ) |
原子性 | ? 不保證。命令逐個執行,可能被穿插。 | ???隔離性而非嚴格原子性。?EXEC ?期間連續執行,但運行時錯誤不回滾。 | ??強原子性。?整個腳本執行不可中斷,腳本內錯誤會中止執行(已執行效果保留)。 |
性能優化 | ??顯著減少 RTT?(主要優勢) | ?? 批量執行減少部分 RTT,但?WATCH ?失敗重試會降低效率。 | ? 減少復雜邏輯的 RTT,但腳本執行本身可能阻塞服務器。 |
復雜邏輯 | ? 只能執行簡單命令序列。 | ? 只能執行簡單命令序列。 | ? 支持條件、循環、變量、函數等復雜 Lua 邏輯。 |
錯誤處理 | 每個命令獨立成功/失敗。 | ?? 入隊錯誤導致事務放棄;運行時錯誤僅失敗命令,其他執行。 | ?? 腳本或命令錯誤導致腳本中止,已執行命令效果保留。 |
阻塞性 | 低。服務器按序執行,但命令間可穿插其他客戶端命令。 | EXEC ?執行期間阻塞其他命令。 | 整個腳本執行期間完全阻塞服務器! |
關鍵命令 | 客戶端實現(一次性發送/接收)。 | MULTI ,?EXEC ,?DISCARD ,?WATCH | EVAL ,?EVALSHA ,?SCRIPT LOAD ,?SCRIPT FLUSH |
典型場景 | 大批量獨立命令操作(無原子性要求)。 | 需要連續執行且不被干擾的命令組 + 樂觀鎖 (WATCH )。 | 需要原子性執行的復雜業務邏輯(如分布式鎖、限流)。 |
如何選擇?
-
需要高性能批量讀寫,且命令獨立、無原子性要求??->?管道。
-
需要保證幾個簡單命令要么都成功要么都不成功,并且并發競爭不激烈或可以通過?
WATCH
?重試解決??->?事務。 -
需要實現復雜的業務邏輯、需要基于中間結果做決策、需要真正的原子性保證(即使包含邏輯判斷和循環)、或者事務的運行時錯誤不回滾特性不符合需求??->?Lua 腳本?(務必保證腳本高效!)。
-
需要結合使用??很常見!例如,使用管道發送多個?
EVAL
?或?EVALSHA
?命令來批量執行多個(獨立的)原子操作。
重要補充:Redis 7 函數 (Functions)
????????Redis 7 引入了?Redis Functions,這是對 Lua 腳本的進一步封裝和增強。它允許你將 Lua 腳本注冊為命名的函數(FUNCTION LOAD
),然后像調用內置命令一樣調用它們(FCALL
,?FCALL_RO
)。這提供了更好的代碼組織、復用性、訪問控制和調試支持(通過?FUNCTION
?命令族管理)。函數在底層仍然是 Lua 腳本執行,繼承了其原子性、隔離性、阻塞風險等核心特性,但提供了更優雅和安全的使用方式。對于新項目,特別是需要復用復雜邏輯的場景,推薦優先考慮 Redis Functions。