Redis是一個cs模式的tcp server,使用和http類似的請求響應協議。
一個client可以通過一個socket連接發起多個請求命令。
每個請求命令發出后client通常會阻塞並等待redis服務處理,redis處理完后請求命令后會將結果通過響應報文返回給client。
基本的通信過程如下:
./bin/redis-cli -h 192.168.36.189 -p 6379 192.168.36.189:6379> incr x (integer) 1 192.168.36.189:6379> incr x (integer) 2 192.168.36.189:6379> incr x (integer) 3
客戶端和服務端通過網絡進行連接。這樣的連接可能非常快(在一個回路網絡中),也可能非常慢(在廣域網上經過多個結點才能互通的兩個主機)。但是無論是否存在網絡延遲,數據包從客戶端傳輸到服務端,以及客戶端從服務端獲得相應都需要花費一些時間。這段時間就成為往返時延(Round Trip Time)。因此當客戶端需要執行一串請求的時候,很容易看出它對性能的影響(例如往同一個隊列中加入大量元素,或者往數據庫中插入大量的鍵)。如果RTT時長為250毫秒(在基於廣域網的低速連接環境下),即使服務器每秒可以處理10萬個請求,但是實際上我們依然只能每秒處理最多4個請求。
如果處於一個回路網絡中,RTT時長則相當短(我的主機ping 127.0.0.1時只需要0.044ms),但是如果你執行一大串寫入請求的時候,還是會有點長。
幸運的是,redis給我們提供了管道技術。
1.Redis管道技術
一個請求/相應服務可以實現為,即使客戶端沒有讀取到舊請求的響應,服務端依舊可以處理新請求。通過這種方式,可以完全無需等待服務端應答地發送多條指令給服務端,並最終一次性讀取所有應答。管道技術最顯著的優勢是提高了redis服務的性能。
通過pipeline方式當有大批量的操作時候。我們可以節省很多原來浪費在網絡延遲的時間。需要注意到是用pipeline方式打包命令發送,redis必須在處理完所有命令前先緩存起所有命令的處理結果。打包的命令越多,緩存消耗內存也越多。所以並是不是打包的命令越多越好。具體多少合適需要根據具體情況測試。
$(echo -en "PING\r\n SET key redis\r\nGET key\r\nINCR x\r\nINCR x\r\nINCR x\r\n"; sleep 10) | nc 192.168.36.189 6379 +PONG +OK $5 redis :4 :5 :6
以上實例中我們通過使用 PING 命令查看redis服務是否可用, 之后我們們設置了key的值為 redis,然后我們獲取key 的值並使得x自增 3 次。
在返回的結果中我們可以看到這些命令一次性向redis服務提交,並最終一次性讀取所有服務端的響應。
2.java測試代碼
package cn.slimsmart.redis.demo.pipeline; import redis.clients.jedis.Jedis; import redis.clients.jedis.Pipeline; @SuppressWarnings("resource") public class PipelineTest { public static void main(String[] args) { long start = System.currentTimeMillis(); usePipeline(); long end = System.currentTimeMillis(); System.out.println("usePipeline:"+(end - start)); start = System.currentTimeMillis(); withoutPipeline(); end = System.currentTimeMillis(); System.out.println("withoutPipeline:"+(end - start)); } private static void withoutPipeline() { try { Jedis jedis = new Jedis("192.168.36.189", 6379); for (int i = 0; i < 1000; i++) { jedis.incr("test2"); } jedis.disconnect(); } catch (Exception e) { } } private static void usePipeline() { try { Jedis jedis = new Jedis("192.168.36.189", 6379); Pipeline pipeline = jedis.pipelined(); for (int i = 0; i < 1000; i++) { pipeline.incr("test2"); } pipeline.sync(); jedis.disconnect(); } catch (Exception e) { } } }
運行結果:
127.0.0.1 循環10000次 效果
usePipeline:151
withoutPipeline:6229
外網IP循環10000次 效果
usePipeline:201
withoutPipeline:94909
結果還是很明顯有較大的差距,所以多次操作用pipeline還是有明顯的優勢。