管道和鏈接復(fù)用
延遲嚴(yán)重。現(xiàn)代計(jì)算機(jī)可以以驚人的速度攪動(dòng)數(shù)據(jù)并齐,并且高速網(wǎng)絡(luò)(通常具有在重要服務(wù)器之間的多個(gè)并行鏈路)提供巨大的帶寬独悴,但是... 該延遲意味著計(jì)算機(jī)花費(fèi)大量的時(shí)間等待數(shù)據(jù) 和 這是基于連續(xù)的編程越來(lái)越受歡迎的幾個(gè)原因之一耸袜。
讓我們考慮一些常規(guī)的程序代碼:
string a = db.StringGet("a");
string b = db.StringGet("b");
在涉及的步驟方面扇谣,這看起來(lái)大致是這樣:
[req1] # client: the client library constructs request 1
[c=>s] # network: request one is sent to the server
[server] # server: the server processes request 1
[s=>c] # network: response one is sent back to the client
[resp1] # client: the client library parses response 1
[req2]
[c=>s]
[server]
[s=>c]
[resp2]
現(xiàn)在讓我們突出客戶端正在做的一些事的時(shí)間點(diǎn):
[req1]
[====waiting=====]
[resp1]
[req2]
[====waiting=====]
[resp2]
請(qǐng)記住昧捷,這是不成比例的 - 如果這是按時(shí)間縮放,它將是完全由waiting
控制的罐寨。
管道線
因此靡挥,許多redis客戶端允許你使用 pipelining, 這是在管道上發(fā)送多個(gè)消息而不等待來(lái)自每個(gè)的答復(fù)的過(guò)程 - 并且(通常)稍后當(dāng)它們進(jìn)入時(shí)處理答復(fù)鸯绿。
在 .NET 中跋破,可以啟動(dòng)但尚未完成,并且可能以后完成或故障的由TPL封裝的操作可以由 TPL 通過(guò) Task
/ Task <T>
API 來(lái)實(shí)現(xiàn)楞慈。
本質(zhì)上幔烛, Task<T>
表示 " T
類型未來(lái)可能的值"(非泛型的 Task
本質(zhì)尚是 Task<void>
)啃擦。你可以使用任意一種用法:
- 在稍后的代碼塊等待直到操作完成(
.Wait()
) - 當(dāng)操作完成時(shí)囊蓝,調(diào)度一個(gè)后續(xù)操作(
.ContinueWith(...)
或await
)
例如,要使用過(guò)程化(阻塞)代碼來(lái)借助管道傳遞這兩個(gè) get 操作令蛉,我們可以使用:
var aPending = db.StringGetAsync("a");
var bPending = db.StringGetAsync("b");
var a = db.Wait(aPending);
var b = db.Wait(bPending);
注意聚霜,我在這里使用db.Wait
,因?yàn)樗鼤?huì)自動(dòng)應(yīng)用配置的同步超時(shí)珠叔,但如果你喜歡你也可以使用 aPending.Wait()
或 Task.WaitAll(aPending,bPending);
蝎宇。
使用管道技術(shù),我們可以立即將這兩個(gè)請(qǐng)求發(fā)送到網(wǎng)絡(luò)祷安,從而消除大部分延遲姥芥。
此外,它還有助于減少數(shù)據(jù)包碎片:?jiǎn)为?dú)發(fā)送(等待每個(gè)響應(yīng))的20個(gè)請(qǐng)求將需要至少20個(gè)數(shù)據(jù)包汇鞭,但是在管道中發(fā)送的20個(gè)請(qǐng)求可以通過(guò)少得多的數(shù)據(jù)包(也許只有一個(gè))凉唐。
執(zhí)行后不理
管道的一個(gè)特例是當(dāng)我們明確地不關(guān)心來(lái)自特定操作的響應(yīng)時(shí)庸追,這允許我們的代碼在排隊(duì)操作在后臺(tái)繼續(xù)時(shí)立即繼續(xù)。 通常台囱,這意味著我們可以在單個(gè)調(diào)用者的連接上并發(fā)工作淡溯。 這是使用 flags
參數(shù)來(lái)實(shí)現(xiàn)的:
// sliding expiration
db.KeyExpire(key, TimeSpan.FromMinutes(5), flags: CommandFlags.FireAndForget);
var value = (string)db.StringGet(key);
FireAndForget
標(biāo)志使客戶端庫(kù)正常地排隊(duì)工作,但立即返回一個(gè)默認(rèn)值(因?yàn)?KeyExpire
返回一個(gè) bool
簿训,這將返回 false
咱娶,因?yàn)?default(bool)
是 false
- 但是返回值是無(wú)意義的,應(yīng)該忽略)强品。
這也適用于 *Async
方法:一個(gè)已經(jīng)完成的 Task <T>
返回默認(rèn)值(或者為 void
方法返回一個(gè)已經(jīng)完成的 Task
)膘侮。
復(fù)用鏈接
管道是很好的,但是通常任何單個(gè)代碼塊只需要一個(gè)值(或者可能想要執(zhí)行幾個(gè)操作择懂,但是依賴于彼此)喻喳。
這意味著我們?nèi)匀挥幸粋€(gè)問(wèn)題,我們花大部分時(shí)間等待數(shù)據(jù)在客戶端和服務(wù)器之間傳輸困曙。
現(xiàn)在考慮一個(gè)繁忙的應(yīng)用程序表伦,也許是一個(gè)Web服務(wù)器。
這樣的應(yīng)用程序通常是并發(fā)的慷丽,所以如果你有20個(gè)并行應(yīng)用程序請(qǐng)求都需要數(shù)據(jù)蹦哼,你可能會(huì)想到旋轉(zhuǎn)20個(gè)連接,或者你可以同步訪問(wèn)單個(gè)連接(這意味著最后一個(gè)調(diào)用者需要等待 延遲的所有其他19之前要糊,甚至開(kāi)始)纲熏。 或者折中一下,也許一個(gè)5個(gè)連接的租賃池 - 無(wú)論你怎么做锄俄,都會(huì)有很多的等待局劲。
StackExchange.Redis不做這個(gè); 相反,它做 很多 的工作奶赠,使你有效地利用所有這個(gè)空閑時(shí)間復(fù)用 一個(gè)連接鱼填。
當(dāng)不同的調(diào)用者同時(shí)使用它時(shí),它自動(dòng)把這些單獨(dú)的請(qǐng)求加入管道毅戈,所以不管請(qǐng)求使用阻塞還是異步訪問(wèn)苹丸,工作都是按進(jìn)入管道的順序處理的。
因此苇经,我們可能有10或20個(gè)的“get a 和 b”包括此前的(從不同的應(yīng)用程序請(qǐng)求)情景中赘理,并且他們都將盡快到達(dá)連接。
基本上扇单,它用完成其他調(diào)用者的工作的時(shí)間來(lái)填充 waiting
時(shí)間商模。
因此,StackExchange.Redis不提供的唯一redis特性(不會(huì)提供)是“阻塞彈出”(BLPOP,BRPOP 和 BRPOPLPUSH)施流,因?yàn)檫@將允許單個(gè)調(diào)用者停止整個(gè)多路復(fù)用器凉倚,阻止所有其他調(diào)用者 。
StackExchange.Redis 需要保持工作的唯一其他時(shí)間是在驗(yàn)證事務(wù)的前提條件時(shí)嫂沉,這就是為什么 StackExchange.Redis 將這些條件封裝到內(nèi)部管理的 condition
實(shí)例中稽寒。
在這里閱讀更多關(guān)于事務(wù)。
如果你覺(jué)得你想“阻止出椞苏拢”杏糙,那么我強(qiáng)烈建議你考慮 發(fā)布 / 訂閱 代替:
sub.Subscribe(channel, delegate {
string work = db.ListRightPop(key);
if (work != null) Process(work);
});
//...
db.ListLeftPush(key, newWork, flags: CommandFlags.FireAndForget);
sub.Publish(channel, "");
這實(shí)現(xiàn)了相同的目的,而不需要阻塞操作蚓土。 注意:
- 數(shù)據(jù) 不通過(guò) 發(fā)布 / 訂閱 發(fā)送; 發(fā)布 / 訂閱 API只用于通知處理器檢查更多的工作
- 如果沒(méi)有處理器宏侍,則新項(xiàng)目保持緩存在列表中; 工作不會(huì)丟失
- 只有一個(gè)處理器可以彈出單個(gè)值; 當(dāng)消費(fèi)者比生產(chǎn)者多時(shí),一些消費(fèi)者會(huì)被通知蜀漆,然后發(fā)現(xiàn)沒(méi)有什么可做的
- 當(dāng)你重新啟動(dòng)一個(gè)處理器谅河,你應(yīng)該假設(shè) 有工作,以便你處理任何積壓的任務(wù)
- 但除此之外确丢,語(yǔ)義與阻止出棧相同
StackExchange.Redis 的多路復(fù)用特性使得在使用常規(guī)的不復(fù)雜代碼的同時(shí)在單個(gè)連接上達(dá)到極高的吞吐量成為可能绷耍。
并發(fā)
應(yīng)當(dāng)注意,管道/鏈接復(fù)用器/未來(lái)值 方法對(duì)于基于連續(xù)的異步代碼也很好地起作用鲜侥;例如你可以寫(xiě):
string value = await db.StringGetAsync(key);
if (value == null) {
value = await ComputeValueFromDatabase(...);
db.StringSet(key, value, flags: CommandFlags.FireAndForget);
}
return value;
查看原文
More
作者水平有限褂始,若有疏漏或錯(cuò)誤還望提醒,十分感謝描函。
您可以在這里 提出問(wèn)題 崎苗。