Redis是一個響應式的服務,當客戶端發送一個請求後,就處於阻塞狀態等待Redis返回結果。這樣一次命令消耗的時間就包括三個部分:請求從客戶端到服務器的時間、結果從服務器到客戶端的時間和命令真正執行時間,前兩個部分消耗的時間總和稱爲RTT(Round Trip Time),當客戶端與服務器存在網絡延時時,RTT就可能會很大,這樣就會導致性能問題。管道(Pipeline)就是爲了改善這個情況的,利用管道,客戶端可以一次性發送多個請求而不用等待服務器的響應,待所有命令都發送完後再一次性讀取服務的響應,這樣可以極大的降低RTT時間從而提升性能。
下面這個例子,在本地分別以普通請求和管道對一個鍵調用2000次incr命令的測試。
public class App
{
public static void main( String[] args ) {
long start = System.currentTimeMillis();
withoutPipeline();
System.out.println("Without Pipeline takes: " + (System.currentTimeMillis() - start) + " ms.");
start = System.currentTimeMillis();
withPipeline();
System.out.println("With Pipeline takes: " + (System.currentTimeMillis() - start) + " ms.");
}
public static void withPipeline() {
Jedis jedis = null;
try {
jedis = new Jedis("localhost", 6379);
jedis.flushDB();
Pipeline p = jedis.pipelined();
p.set("thekey", Integer.toString(0));
for (int i = 1; i <= 2000; i++) {
p.incr("thekey");
}
Response<String> r = p.get("thekey");
p.sync();
System.out.println(r.get());
} finally {
jedis.close();
}
}
public static void withoutPipeline() {
Jedis jedis = null;
try {
jedis = new Jedis("localhost", 6379);
jedis.flushDB();
jedis.set("thekey", Integer.toString(0));
for (int i = 1; i <= 2000; i++) {
jedis.incr("thekey");
}
System.out.println(jedis.get("thekey"));
} finally {
jedis.close();
}
}
}
//輸出結果
2000
Without Pipeline takes: 183 ms.
2000
With Pipeline takes: 47 ms.
結果很直觀的反映出兩者的差別,要知道這是在本地測試,幾乎不存在網絡延時的問題,如果是在不同的網段測試的話,效果會更明顯。雖然管道在一定程度上對性能有所提升,但是在使用時一點要注意,每個命令的返回結果是先被緩存在服務器端的,最後一次性返回給客戶端。如果一次批量提交涉及大量的返回結果,可能會導至服務器的內存溢出,這在生產環境是致命的,一次批次處理多少量,最好在設計階段做出合理評估。
最後,管道只是一個方案,並不意味着在任何時候都要儘可能的使用它,而是應該結合考慮網絡延遲時間、業務涉及的請求量等等因素綜合考慮,畢竟創建管道本身也會有一定的消耗。