Redis的pipeline(管道)功能在命令行中沒有,但redis是支持pipeline的,而且在各個語言版的client中都有相應(yīng)的實現(xiàn)。 由于網(wǎng)絡(luò)開銷延遲,就算redis server端有很強(qiáng)的處理能力,也會由于收到的client消息少,而造成吞吐量小。當(dāng)client 使用pipelining 發(fā)送命令時,redis server必須將部分請求放到隊列中(使用內(nèi)存),執(zhí)行完畢后一次性發(fā)送結(jié)果;如果發(fā)送的命令很多的話,建議對返回的結(jié)果加標(biāo)簽,當(dāng)然這也會增加使用的內(nèi)存;
Pipeline在某些場景下非常有用,比如有多個command需要被“及時的”提交,而且他們對相應(yīng)結(jié)果沒有互相依賴,對結(jié)果響應(yīng)也無需立即獲得,那么pipeline就可以充當(dāng)這種“批處理”的工具;而且在一定程度上,可以較大的提升性能,性能提升的原因主要是TCP連接中減少了“交互往返”的時間。
不過在編碼時請注意,pipeline期間將“獨占”鏈接,此期間將不能進(jìn)行非“管道”類型的其他操作,直到pipeline關(guān)閉;如果你的pipeline的指令集很龐大,為了不干擾鏈接中的其他操作,你可以為pipeline操作新建Client鏈接,讓pipeline和其他正常操作分離在2個client中。不過pipeline事實上所能容忍的操作個數(shù),和socket-output緩沖區(qū)大小/返回結(jié)果的數(shù)據(jù)尺寸都有很大的關(guān)系;同時也意味著每個redis-server同時所能支撐的pipeline鏈接的個數(shù),也是有限的,這將受限于server的物理內(nèi)存或網(wǎng)絡(luò)接口的緩沖能力。
(一)簡介
Redis使用的是客戶端-服務(wù)器(CS)模型和請求/響應(yīng)協(xié)議的TCP服務(wù)器。這意味著通常情況下一個請求會遵循以下步驟:
- 客戶端向服務(wù)端發(fā)送一個查詢請求,并監(jiān)聽Socket返回,通常是以阻塞模式,等待服務(wù)端響應(yīng)。
- 服務(wù)端處理命令,并將結(jié)果返回給客戶端。
Redis客戶端與Redis服務(wù)器之間使用TCP協(xié)議進(jìn)行連接,一個客戶端可以通過一個socket連接發(fā)起多個請求命令。每個請求命令發(fā)出后client通常會阻塞并等待redis服務(wù)器處理,redis處理完請求命令后會將結(jié)果通過響應(yīng)報文返回給client,因此當(dāng)執(zhí)行多條命令的時候都需要等待上一條命令執(zhí)行完畢才能執(zhí)行。比如:
其執(zhí)行過程如下圖所示:
由于通信會有網(wǎng)絡(luò)延遲,假如client和server之間的包傳輸時間需要0.125秒。那么上面的三個命令6個報文至少需要0.75秒才能完成。這樣即使redis每秒能處理100個命令,而我們的client也只能一秒鐘發(fā)出四個命令。這顯然沒有充分利用 redis的處理能力。
而管道(pipeline)可以一次性發(fā)送多條命令并在執(zhí)行完后一次性將結(jié)果返回,pipeline通過減少客戶端與redis的通信次數(shù)來實現(xiàn)降低往返延時時間,而且Pipeline 實現(xiàn)的原理是隊列,而隊列的原理是時先進(jìn)先出,這樣就保證數(shù)據(jù)的順序性。 Pipeline 的默認(rèn)的同步的個數(shù)為53個,也就是說arges中累加到53條數(shù)據(jù)時會把數(shù)據(jù)提交。其過程如下圖所示:client可以將三個命令放到一個tcp報文一起發(fā)送,server則可以將三條命令的處理結(jié)果放到一個tcp報文返回。
需要注意到是用 pipeline方式打包命令發(fā)送,redis必須在處理完所有命令前先緩存起所有命令的處理結(jié)果。打包的命令越多,緩存消耗內(nèi)存也越多。所以并不是打包的命令越多越好。具體多少合適需要根據(jù)具體情況測試。
(2)代碼實現(xiàn)
List<Response> rs = new ArrayList<>();
Jedis j = new Jedis("192.168.113.132", 6379);
Pipeline pipe = j.pipelined();
for (int i = 0; i < 10000; i++) {
Response<String> result = pipe.set(String.valueOf(i), String.valueOf(i));
rs.add(result);
}
pipe.sync();// 必須加,否則結(jié)果會有問題
int i = 1;
for (Response s : rs) {
System.out.println(s.get()+"----------------------------"+i++);
}
Transaction multi = j.multi();//事務(wù)實現(xiàn)方式
multi.set("1","2");
multi.exec();
結(jié)果:
OK----------------------------1
OK----------------------------2
OK----------------------------3
.....
OK----------------------------9998
OK----------------------------9999
OK----------------------------10000
(三)適用場景
有些系統(tǒng)可能對可靠性要求很高,每次操作都需要立馬知道這次操作是否成功,是否數(shù)據(jù)已經(jīng)寫進(jìn)redis了,那這種場景就不適合。
還有的系統(tǒng),可能是批量的將數(shù)據(jù)寫入redis,允許一定比例的寫入失敗,那么這種場景就可以使用了,比如10000條一下進(jìn)入redis,可能失敗了2條無所謂,后期有補(bǔ)償機(jī)制就行了,比如短信群發(fā)這種場景,如果一下群發(fā)10000條,按照第一種模式去實現(xiàn),那這個請求過來,要很久才能給客戶端響應(yīng),這個延遲就太長了,如果客戶端請求設(shè)置了超時時間5秒,那肯定就拋出異常了,而且本身群發(fā)短信要求實時性也沒那么高,這時候用pipeline最好了。
(四)管道(Pipelining) VS 腳本(Scripting)
大量 pipeline 應(yīng)用場景可通過 Redis 腳本(Redis 版本 >= 2.6)得到更高效的處理,后者在服務(wù)器端執(zhí)行大量工作。腳本的一大優(yōu)勢是可通過最小的延遲讀寫數(shù)據(jù),讓讀、計算、寫等操作變得非??欤╬ipeline 在這種情況下不能使用,因為客戶端在寫命令前需要讀命令返回的結(jié)果)。
應(yīng)用程序有時可能在 pipeline 中發(fā)送 EVAL 或 EVALSHA 命令。Redis 通過 SCRIPT LOAD 命令(保證 EVALSHA 成功被調(diào)用)明確支持這種情況。
參考:https://blog.csdn.net/u011489043/article/details/78769428