Redis研究(十八)—管道(pipeline),redispipeline
用戶端和Redis使用TCP協議串連。不論是用戶端向Redis發送命令還是Redis向用戶端返回命令的執行結果,都需要經過網路傳輸,這兩個部分的總耗時稱為往返時延。根據網路效能不同,往返時延也不同,大致來說到本地迴環地址(loop back address)的往返時延在數量級上相當於Redis處理一條簡單命令(如LPUSH list 1 2 3)的時間。如果執行較多的命令,每個命令的往返時延累加起來對效能還是有一定影響的。
在執行多個命令時每條命令都需要等待上一條命令執行完(即收到Redis的返回結果)才能執行,即使命令不需要上一條命令的執行結果。如要獲得post :1、post :2和post :3這3個鍵中的title欄位,需要執行三條命令:
Redis的底層通訊協定對管道(pipeline )提供了支援。通過管道可以一次性發送多條命令並在執行完後一次性將結果返回,當一組命令中每條命令都不依賴於之前命令的執行結果時就可以將這組命令一起通過管道發出。管道通過減少用戶端與Redis的通訊次數來實現降低往返時延累計值的目的:
下面具體分析之:
Redis是一個cs模式的tcp server,使用和http類似的請求響應協議。一個client可以通過一個socket串連發起多個請求命令。每個請求命令發出後client通常會阻塞並等待redis服務處理,redis處理完後請求命令後會將結果通過響應報文返回給client。基本的通訊過程如下:
Client: INCR XServer: 1Client: INCR XServer: 2Client: INCR XServer: 3Client: INCR XServer: 4
基本上四個命令需要8個tcp報文才能完成。由於通訊會有網路延遲,假如從client和server之間的包傳輸時間需要0.125秒。那麼上面的四個命令8個報文至少會需要1秒才能完成。這樣即使redis每秒能處理100個命令,而我們的client也只能一秒鐘發出四個命令。這顯示沒有充分利用 redis的處理能力。
除了可以利用mget,mset之類的單條命令處理多個key的命令外我們還可以利用pipeline的方式從client打包多條命令一起發出,不需要等待單條命令的響應返回,而redis服務端會處理完多條命令後會將多條命令的處理結果打包到一起返回給用戶端。通訊過程如下:
Client: INCR XClient: INCR XClient: INCR XClient: INCR XServer: 1Server: 2Server: 3Server: 4
通過pipeline方式當有大批量的操作時候。我們可以節省很多原來浪費在網路延遲的時間。需要注意到是用 pipeline方式打包命令發送,redis必須在處理完所有命令前先緩衝起所有命令的處理結果。打包的命令越多,緩衝消耗記憶體也越多。所以並是不是打包的命令越多越好。具體多少合適需要根據具體情況測試。下面是個jedis用戶端使用pipeline的測試:
import redis.clients.jedis.Jedis;import redis.clients.jedis.Pipeline; public class PipelineTest { public static void main(String[] args) { int count = 1000; long start = System.currentTimeMillis(); withoutPipeline(count); long end = System.currentTimeMillis(); System.out.println("withoutPipeline: " + (end-start)); start = System.currentTimeMillis(); usePipeline(count); end = System.currentTimeMillis(); System.out.println("usePipeline: " + (end-start)); } private static void withoutPipeline(int count){ Jedis jr = null; try { jr = new Jedis("10.10.224.44", 6379); for(int i =0; i<count; i++){ jr.incr("testKey1"); } } catch (Exception e) { e.printStackTrace(); } finally{ if(jr!=null){ jr.disconnect(); } } } private static void usePipeline(int count){ Jedis jr = null; try { jr = new Jedis("10.10.224.44", 6379); Pipeline pl = jr.pipelined(); for(int i =0; i<count; i++){ pl.incr("testKey2"); } pl.sync(); } catch (Exception e) { e.printStackTrace(); } finally{ if(jr!=null){ jr.disconnect(); } } }}
輸出:
withoutPipeline: 11341usePipeline: 344
測試結果還是很明顯有較大的差距,所以多次操作用pipeline還是有明顯的優勢。