架構(gòu)師在設(shè)計(jì)微服務(wù)架構(gòu)的時(shí)候,一般會(huì)關(guān)注模式、拓?fù)湟约傲6鹊葐?wèn)題,但是有一個(gè)最為基礎(chǔ)的決策是線程模型。我們現(xiàn)在有了很多的開(kāi)源工具、編程語(yǔ)言和技術(shù)棧,軟件架構(gòu)師所面臨的選擇要比以往更多了。
這樣的話,我們很容易就會(huì)迷失在語(yǔ)言的細(xì)節(jié)和/或不同庫(kù)的差異之中,從而無(wú)法分辨什么東西才是最重要的。
為微服務(wù)選擇正確的線程模型并確定它將如何與數(shù)據(jù)庫(kù)連接進(jìn)行關(guān)聯(lián)非常重要,這決定了你的解決方案是剛剛能用,還是會(huì)成為一個(gè)很棒的產(chǎn)品。
作為架構(gòu)師,在考慮效率和復(fù)雜性之間的權(quán)衡時(shí),關(guān)注線程模型是一種有效的方式。服務(wù)會(huì)被分解為并行的操作,通過(guò)共享的資源來(lái)進(jìn)行處理,所以應(yīng)用會(huì)變得更加高效,其響應(yīng)的延遲也會(huì)更短(這會(huì)在一定的范圍之內(nèi),參見(jiàn) Amdahl定理 )。不過(guò),并行操作和安全的資源共享會(huì)為代碼引入更多的復(fù)雜性。
代碼越復(fù)雜,工程師完全理解起來(lái)就會(huì)越困難,這意味著在每次變更的時(shí)候更有可能引入新的bug。
架構(gòu)師最為重要的責(zé)任之一就是在效率和代碼復(fù)雜性之間找到一個(gè)平衡。
單線程單進(jìn)程的線程模型
最基本的線程模型就是單線程單進(jìn)程模型,按照這種方式編寫代碼是最簡(jiǎn)單的。

單線程單進(jìn)程服務(wù)同一時(shí)間無(wú)法在多個(gè)核心上執(zhí)行。在現(xiàn)代的裸機(jī)服務(wù)器上,核心數(shù)量一般能夠達(dá)到24個(gè)。如果按照這種模型構(gòu)建服務(wù)的話,它所能使用的服務(wù)器核心數(shù)量不會(huì)超過(guò)一個(gè)。如果有額外的負(fù)載的話,這些服務(wù)的吞吐量不會(huì)隨之增加,它們的CPU利用率百分比不會(huì)超過(guò)個(gè)位數(shù)。鑒于如此高的未利用率,所以有一種補(bǔ)償策略就是使用更大的服務(wù)器池來(lái)處理負(fù)載。
這種方式可以運(yùn)行,但是它非常浪費(fèi),最終的成本會(huì)非常高昂。最為流行的云計(jì)算供應(yīng)商都以非常便宜的價(jià)格提供單虛擬核心實(shí)例,這樣做是為了以更細(xì)的粒度來(lái)支持這種模式,從而應(yīng)對(duì)擴(kuò)展性的需求。
單線程多個(gè)新進(jìn)程的線程模型
在復(fù)雜性和效率方面更進(jìn)一步的就是單線程多進(jìn)程的線程模型,在這種方式下,會(huì)為每個(gè)請(qǐng)求創(chuàng)建一個(gè)新的進(jìn)程。編寫這種類型的微服務(wù)相對(duì)比較簡(jiǎn)單,但是跟前面的模型相比它包含了更多的復(fù)雜性。
(點(diǎn)擊放大圖像)

創(chuàng)建進(jìn)程的開(kāi)銷以及持續(xù)創(chuàng)建和銷毀數(shù)據(jù)庫(kù)連接會(huì)占用處理器的時(shí)間,因此會(huì)增加所有協(xié)作服務(wù)的延遲。這種線程模型之所以會(huì)創(chuàng)建更多的數(shù)據(jù)庫(kù)連接是因?yàn)閿?shù)據(jù)庫(kù)連接是屬于每個(gè)進(jìn)程的,無(wú)法跨進(jìn)程邊界共享。進(jìn)程的存活時(shí)間只會(huì)在請(qǐng)求的時(shí)間范圍內(nèi),所以每個(gè)請(qǐng)求必須要重新連接數(shù)據(jù)庫(kù)。
按照這種線程模型運(yùn)行的微服務(wù)應(yīng)該延遲對(duì)數(shù)據(jù)庫(kù)的連接,直到需要的時(shí)候再創(chuàng)建連接。如果代碼路徑不需要的話,那就沒(méi)有必要耗費(fèi)成本創(chuàng)建數(shù)據(jù)庫(kù)連接了。盡管數(shù)據(jù)庫(kù)連接無(wú)法跨進(jìn)程緩存,但是有些環(huán)境支持跨進(jìn)程的opcode緩存,這樣的話,我們可以將服務(wù)的配置數(shù)據(jù)存儲(chǔ)起來(lái),如連接到數(shù)據(jù)庫(kù)的主機(jī)IP和憑證信息,兩個(gè)流行的opcode緩存樣例就是Zend OpCache和APC。
單線程多進(jìn)程重用的線程模型
在代碼復(fù)雜性和性能方面的下一步提升就是這種線程模型,這是一種單線程多線程的模型,新的請(qǐng)求都會(huì)重用已有的worker進(jìn)程。這與前面的線程模型有所不同,在前面的模型中,會(huì)為每個(gè)請(qǐng)求都創(chuàng)建一個(gè)新的進(jìn)程。而在這個(gè)線程模型中,在進(jìn)程提供就緒之后,就不會(huì)創(chuàng)建新的進(jìn)程了。
(點(diǎn)擊放大圖像)

這種服務(wù)在復(fù)雜性方面相對(duì)來(lái)說(shuō)比較簡(jiǎn)單直接,但是需要額外的代碼來(lái)管理worker進(jìn)程的生命周期。這些代碼必須要正確地進(jìn)行重新初始化。例如,程序員可能會(huì)維護(hù)一些靜態(tài)變量,而不是以參數(shù)的形式傳遞大量的數(shù)據(jù)。這樣的話,代碼會(huì)更加簡(jiǎn)單,針對(duì)每個(gè)新的請(qǐng)求都對(duì)這些靜態(tài)變量進(jìn)行重置的話,代碼就能正常運(yùn)行。但是如果代碼沒(méi)有重置這些變量的話,那么它就會(huì)基于之前的請(qǐng)求來(lái)進(jìn)行處理,而不是基于當(dāng)前的請(qǐng)求。在代碼復(fù)雜性方面,另外一點(diǎn)就是需要包含恢復(fù)失效(stale)數(shù)據(jù)庫(kù)連接的邏輯。當(dāng)與數(shù)據(jù)庫(kù)的連接由于不活躍而斷掉的時(shí)候,原有的數(shù)據(jù)庫(kù)連接實(shí)例可能會(huì)出現(xiàn)失效的情況。