標籤:des http io os 使用 ar 資料 art sp
請求/響應協議和RTT
Redis是一個使用用戶端/伺服器模型(也被稱作請求/響應協議)的TCP伺服器。
這說明通常來講一個一個請求的實現有以下步驟:
- 用戶端發送請求到伺服器,並從socket中以堵塞的方式讀取伺服器的響應資料。
伺服器對發動的命令進行處理並把響應資料發回用戶端。
比如發送連續四個命令就會像這樣:
- 用戶端: INCR X
- 服務端: 1
- 用戶端: INCR X
- 服務端: 2
- 用戶端: INCR X
- 服務端: 3
- 用戶端: INCR X
- 服務端: 4
用戶端和服務端通過網路連接。這個串連可能非常快(本地迴環介面)也可能非常慢(互連網上相隔很多跳數的兩台主機)。不論網路延遲是多少,資料包從用戶端發到服務端並從服務端返回用戶端都有一個時間。
這個時間被稱作環路時間。顯而易見,當用戶端需要一次發送很多請求時(比如一次向一個List添加很多元素,或者向一個資料庫添加很多Key),這個環路時間會對效能造成非常大的影響。如果環路時間是250毫秒(在網路連接非常慢的情況下),即使服務端能每秒處理10萬個請求,我們一秒最大也只能處理四個請求。
即使我們用的是本地迴環介面,本地環路時間會短得多(比如在我本機上用ping測試是0.044毫秒),但是如果你要一次進行大量寫操作,這個時間依然不少。
幸運的是我們有一種方法來最佳化這種使用情境。
Redis管道
一個請求/響應伺服器能夠做到在用戶端還沒有讀取上個傳回值的時候就處理新的請求。通過這種方式用戶端就可以在不擷取每次請求的響應的情況下給服務端一次發送多個命令,並在未來的某個時間一次獲得這些所有的響應。
這就叫做管道,這種技術早在多年前就被廣泛應用。比如很多POP3協議的實現方案就支援管道,極大的提高了從伺服器上下載新郵件的速度
Redis在很早的時候就支援管道,所以無論你現在用的是什麼版本,你都可以在Redis中使用管道。以下是一個使用原生netcat工具的例子:
$ (echo -en "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379+PONG+PONG+PONG
這次我們不在每次調用都產生RTT的花銷,所有命令加起來只產生一次RTT花銷。
To be very explicit, with pipelining the order of operations of our very first example will be the following:
為了顯示清楚,第一個例子中我們進行操作如果使用管道就會以下面的順序執行:
- 用戶端: INCR X
- 用戶端: INCR X
- 用戶端: INCR X
- 用戶端: INCR X
- 服務端: 1
- 服務端: 2
- 服務端: 3
- 服務端: 4
務必要注意:當用戶端使用管道發送多條命令時,服務端會把所有的響應都放入隊列,這會帶來額外的記憶體開銷。所以如果你想使用管道來一次發送多條命令,最好給定一合理的個命令數上限值,比如10000條命令,之後讀取響應,之後再次發送另外的10000條命令並迴圈往複。這樣請求速度不會有大的改變,但是額外記憶體的使用量只會相當於緩衝這10000條回複所需要的上限值。
一些效能指標
以下的效能檢測中我們會用到Redis的Ruby用戶端,使用管道,來測試管道對於速度的提升:
require ‘rubygems‘require ‘redis‘def bench(descr) start = Time.now yield puts "#{descr} #{Time.now-start} seconds"enddef without_pipelining r = Redis.new 10000.times { r.ping }enddef with_pipelining r = Redis.new r.pipelined { 10000.times { r.ping } }endbench("without pipelining") { without_pipelining}bench("with pipelining") { with_pipelining}
運行上面的簡單的指令碼可以提供一些在Mac OS X 系統上的效能指標,由於延遲只來源於本地迴環介面,RTT時間非常短,管道機制只提供了最小的效能提升:
without pipelining 1.185238 secondswith pipelining 0.250783 seconds
可以看出使用管道後有了五倍的效能提升。
管道VS指令碼
由於指令碼執行於服務端,所以使用Redis指令碼(可用於Redis2.6或以上版本)可以使某些原本使用管道的情境執行得更有效率。指令碼一個很大的優勢在於它的讀寫操作的延遲相當小,因此能使如read, compute, write之類的操作執行的非常快(管道在這個情境下表現就差強人意,因為管道在執行寫之前需要先得到讀請求的響應和傳回值)。
有些時候應用希望可以通過管道發送EVLA或者EVALSHA命令。這也是完全可以做的到的並且Redis通過SCRIPT LOAD命令顯式支援這種操作(並保證EVALSHA操作不會執行失敗)。
Redis教程02——管道(Pipelining)