閱讀767 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《Redis官方文檔》 Pipelining – 請求應答模式和往返延時

 Redis是一個CS結構的TCP服務器,使用”請求-應答”的模式。,客戶端發起一個請求是這樣的步驟:

  • 客戶端發送一個請求給服務器,然後等待服務器的響應,一般客戶端使用阻塞模式來等待服務器響應。
  • 服務器收到請求並處理完畢後,發送結果給客戶端。

  舉個例子,發送下麵4個命令大概就是這樣的順序:

  • 客戶端發送: INCR X
  • 服務器響應: 1
  • 客戶端發送: INCR X
  • 服務器響應: 2
  • 客戶端發送: INCR X
  • 服務器響應: 3
  • 客戶端發送: INCR X
  • 服務器響應: 4

  客戶端和服務器通過網絡連接,網速可以非常快, 也可以非常慢。不管是快還是慢,消息包從客戶端到服務器,再從服務器返回到客戶端,總是要需要時間的。這個時間被稱之為RTT(Round Trip Time,往返延時)。顯然,當客戶端需要發送多條請求時(比如往一個list中加很多元素,或者往一個數據庫中填充很多keys),這個往返延時會影響到性能。假設網絡非常慢,往返延時達到250毫秒,就算服務器每秒可以處理10萬個請求,客戶端也隻能每秒處理4個請求。就算使用環回接口,往返延時非常小,如果需要執行很多寫的操作, 也是要浪費許多時間的。

  幸好我們有辦法改進這種情況。

Redis Pipelining

      “請求-響應”模式的服務器在處理完一個請求後就開始處理下一個請求,不管客戶端是否讀取到前一個請求的響應結果。這讓客戶端不需要發一個請求等一個響應的串行,可以一次發送多個請求,再最後一次性讀取所有響應。這就叫piplining(管道化),這種技術幾十年來廣泛的使用。比如很多POP3協議支持這個特性,大大的加速了從服務器上下載新郵件的速度。

    Redis在很早的版本就支持pipeling,所以無論你用的是什麼版本的redis,都可以用pipeling。下麵是一個使用netcat的演示例子:

$ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379
+PONG
+PONG
+PONG

  我們的第一個例子,如果使用pipeline,客戶端的請求和服務器的響應順序就是如下:

  • 客戶端發送: INCR X
  • 客戶端發送: INCR X
  • 客戶端發送: INCR X
  • 客戶端發送: INCR X
  • 服務器響應: 1
  • 服務器響應: 2
  • 服務器響應: 3
  • 服務器響應: 4

  注意:當客戶端使用pipelining發送很多請求時,服務器將在內存中使用隊列存儲這些指令的響應。所以批量發送的指令數量,最好在一個合理的範圍內,比如每次發1萬條指令,讀取完響應後再發送另外1萬條指令。2萬條指令,一次性發送和分2次發送,對客戶端來說速度是差不多的,但是對服務器來說,內存占用差了1萬條響應的大小。

性能測試

  下麵是我們用Redis Ruby客戶端測試使用pipelining帶來的性能改進:

require 'rubygems'
require 'redis'

def bench(descr)
    start = Time.now
    yield
    puts "#{descr} #{Time.now-start} seconds"
end

def without_pipelining
    r = Redis.new
    10000.times {
        r.ping
    }
end

def with_pipelining
    r = Redis.new
    r.pipelined {
        10000.times {
            r.ping
        }
    }
end

bench("without pipelining") {
    without_pipelining
}
bench("with pipelining") {
    with_pipelining
}

    在我的Mac OS X係統中運行上麵的腳本,由於使用環回接口往返延時非常小,這樣pipeling帶來的優化非常小。可以得到下麵的結果:

without pipelining 1.185238 seconds
with pipelining 0.250783 seconds

  從結果可以看到,使用pipelining技術,我們的傳輸速度提高了5倍。

管道化 VS 腳本

    大部分使用pipelining的情況都可以用Redis腳本(2.6或高於2.6的版本才支持)來代替,使之更高效的在服務器端執行。使用腳本的最大好處是,在最小的延遲下可以讀和寫,比如可以:讓“讀,計算,寫”這樣一個流程非常快(pipeling不能處理這種情景,因為客戶端需要得到響應之後才能計算和寫)。有時候,應用程序可能需要在一個pipeline中發送多個EVAL或EVALSHA指令,redis的SCRITP LOAD指令能很好的滿足這種需求(它保證了EVALSHA不會有調用失敗的風險)。

最後更新:2017-05-22 10:03:37

  上一篇:go  阿裏感悟(十三)降低成本的敏捷設計
  下一篇:go  《Redis官方文檔》Data types—數據類型