Redis 基於 TCP 的 Request/Response 協議,每條命令需經歷「發送 → 排隊 → 執行 → 返回」完整 RTT。管道化允許客戶端一次性發送多條命令、最後統一讀取回覆,大幅降低網絡開銷。本文從協議層面剖析原理、實戰基準測試、與 Lua 腳本的差異選型。
# 非管道:串行等待
Client: INCR X
Server: 1
Client: INCR X
Server: 2
...
# 管道:批量發送,一次性讀取回覆
Client: INCR X
Client: INCR X
Client: INCR X
Client: INCR X
Server: 1
Server: 2
Server: 3
Server: 4
require 'redis'
def bench(desc)
start = Time.now
yield
puts "#{desc} #{Time.now - start} 秒"
end
r = Redis.new
bench("without pipelining") { 10000.times { r.ping } }
bench("with pipelining") { r.pipelined { 10000.times { r.ping } } }
結果 (Mac 本地回環):
without pipelining: 1.185 秒
with pipelining: 0.251 秒
# 提升 ~4.7 倍
| 維度 | 管道 | Lua 腳本 (EVAL/EVALSHA) |
|---|---|---|
| 原子性 | 無(命令間可被其他客戶端插入) | 有(腳本執行期間阻塞其他命令) |
| 讀寫依賴 | 不支持(需先讀再寫,中間有 RTT) | 支持(服務端原子完成讀-算-寫) |
| 網絡開銷 | 1 RTT 發送 N 命令 | 1 RTT 執行整個邏輯 |
| 適用場景 | 批量獨立寫入、緩存預熱、指標批量更新 | 計數器檢查並增、分佈式鎖、限流、事務性業務 |
# 預先載入腳本獲取 SHA
SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])"
# 管道中批量調用
EVALSHA 1 key1 value1
EVALSHA 1 key2 value2
...
Redis 保證
SCRIPT LOAD後EVALSHA不會因腳本未載入而失敗。
slowlog、INFO clients 觀察管道積壓情況。