本文已收錄于專欄
《Redis精通系列》
上千人點贊收藏,全套Redis學習資料,大廠必備技能!
目錄
1、簡介
2、深究pipeline
3、benchmark壓測pipeline
4、Jedis使用pipeline
Redis是一種基于用戶端-服務端模型以及請求/響應的TCP服務。一次Redis用戶端發起的請求,經過服務端的響應後,大緻會經曆如下的步驟:
用戶端發起一個(查詢/插入)請求,并監聽socket傳回,通常情況都是阻塞模式等待Redis伺服器的響應
服務端處理指令,并且傳回處理結果給用戶端
用戶端接收到服務的傳回結果,程式從阻塞代碼處傳回
Redis用戶端和服務端之間通過網絡連接配接進行資料傳輸,這個連接配接可以很快(loopback接口)或很慢(建立了一個多次跳轉的網絡連接配接)。無論網絡延如何延時,資料包總是能從用戶端到達伺服器,并從伺服器傳回資料回複用戶端,這個時間被稱之為RTT(Round Trip Time - 往返時間)。我們可以很容易就意識到,Redis在連續請求服務端時,即使Redis每秒能處理100k請求,但也會因為網絡傳輸花費大量時間,導緻整體性能的下降。
是以如果遇到大量的批處理,我們可以考慮使用Redis的pipeline(管道)。值得注意的是,管道技術并不是Redis特有的技術,管道技術往往需要用戶端-伺服器的共同配合,大部分工作任務其實是在用戶端完成,很顯然Redis支援管道技術,按照官網的意思,Redis的最低版本就考慮了管道技術的支援性設計。
如下圖,多個連續的incr指令,使用pipeline(管道)後,多個連續的incr指令隻會花費一次網絡來回開銷,這個開銷會随着n數值的增大,大幅減少網絡io開銷,進而提升整體服務的性能。

用戶端調用write将資料寫入作業系統核心(kernel)為socket連接配接配置設定的發送緩沖區(send buffer)
用戶端作業系統核心将發送緩沖區(send buffer)的資料發送到網卡(NIC)
網卡(NIC)将資料通過路由(route)将資料送到Redis伺服器機器網卡(NIC)
伺服器作業系統核心(kernel)将網卡(NIC)接收的資料,寫入核心為socket配置設定的接收緩沖區(recv buffer)
伺服器程序從接收緩沖區調用read讀取資料,并進行資料邏輯處理
資料處理完成之後,伺服器程序調用write将響應資料寫入作業系統核心為socket配置設定的發送緩沖區
作業系統核心将發送緩沖區的資料發送到伺服器網卡
伺服器網卡将響應資料通過路由發送到用戶端網卡
用戶端網卡接收響應資料
用戶端作業系統核心讀取網卡接收到的伺服器響應資料,并寫入作業系統為socket連接配接配置設定的介紹緩沖區
用戶端程序調用read從接收緩沖區中讀取伺服器響應資料
一次完整網絡請求來回過程結束
對于pipeline技術而言,就是将n * 12個步驟,合并成1 * 12,這樣服務請求響應的總體時間将會大大的減少。
有個值得注意的點:
在上述網絡請求來回中,可能出現我們經常說到的io阻塞:
當write操作發生,并且發送緩沖區(send buffer)滿時,就會導緻write操作阻塞
當read操作發生,并且接收緩沖區(recv buffer)滿時,就會導緻read操作阻塞
上述的這兩個阻塞如果出現,将會導緻整個請求時間變長,是以我們操作大批量指令的時候,比如10k個指令,我們可以合理的對指令分多次批量發送,這樣可以減少出現阻塞的情況,也可以避免伺服器響應一個過大的答複包,導緻用戶端記憶體負載過重。
使用Redis提供的benchmark對Redis進行性能測試,
如過你是Windows下的Redis,在安裝目錄下有個redis-benchmark.exe,進入cmd指令模式測試即可
package com.liziba.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import java.io.IOException;
/**
* <p>
* 測試pipeline
* </p>
*
* @Author: Liziba
* @Date: 2021/9/14 22:43
*/
public class PipelineTest {
public static void main(String[] args) throws IOException {
Jedis client = new Jedis("127.0.0.1", 6379);
long startPipe = System.currentTimeMillis();
Pipeline pipe = client.pipelined();
pipe.multi();
for (int i = 0; i < 100000; i++) {
pipe.set("pipe" + i, i + "" );
}
pipe.exec();
pipe.close();
long endPipe = System.currentTimeMillis();
System.out.println("pipeline set cost time : " + (endPipe - startPipe) + "ms");
for (int i = 0; i < 100000; i++) {
client.set("normal" + i, i + "");
}
System.out.println("normal set cost time : " + (System.currentTimeMillis() - endPipe)+ "ms");
}
}