如何设计真正高性能高并发分布式系统(万字长文)
“世間可稱之為天經(jīng)地義的事情沒(méi)幾樣,復(fù)雜的互聯(lián)網(wǎng)架構(gòu)也是如此,萬(wàn)丈高樓平地起,架構(gòu)都是演變而來(lái),那么演變的本質(zhì)是什么?”
—?1?—?引子
? ? 軟件復(fù)雜性來(lái)源于幾個(gè)方面:高并發(fā)、高性能、高可用、可擴(kuò)展、低成本、低規(guī)模、可維護(hù)、安全等。架構(gòu)演化、發(fā)展都是為了試圖降低復(fù)雜性:
-
高并發(fā)、高性能:互聯(lián)網(wǎng)系統(tǒng)特點(diǎn),用戶量大,請(qǐng)求量大,高并發(fā)高性能成為必備要求。性能差體驗(yàn)會(huì)差,用戶會(huì)有別的選擇。
-
高可用:系統(tǒng)高可用可提升用戶體驗(yàn),也變?yōu)楸貍湟蟆J畮啄昵拔覀冑I股票都需要T+N操作,而現(xiàn)在通過(guò)手機(jī)可以實(shí)時(shí)辦理。
-
可擴(kuò)展、易迭代:在產(chǎn)品初期,采用單體或簡(jiǎn)單的架構(gòu)。成熟期,演進(jìn)為現(xiàn)在大中臺(tái)、小前臺(tái)的概念,把不變的和變的拆分開來(lái)。產(chǎn)品經(jīng)理、架構(gòu)師需避免無(wú)限放大需求,面向未來(lái)設(shè)計(jì),進(jìn)入尷尬境地。
-
低成本:是個(gè)過(guò)程。ROI投入產(chǎn)出比越往后越低。
-
低規(guī)模:規(guī)模小,成本肯定低,運(yùn)維、擴(kuò)展.... 都將方便。所以簡(jiǎn)單、適用、演進(jìn)架構(gòu)設(shè)計(jì)原則很重要。
-
易運(yùn)維:除了傳統(tǒng)運(yùn)維方面。業(yè)務(wù)的快速發(fā)展,灰度發(fā)布、快速發(fā)布回滾、部分功能升級(jí)、ab測(cè)試等對(duì)架構(gòu)層面提出了更高要求,也是現(xiàn)在容器化技術(shù)這么流行的原因之一。
? ? 本文主要從如何實(shí)現(xiàn)高并發(fā)、高性能系統(tǒng)角度,剖析網(wǎng)絡(luò)應(yīng)用架構(gòu)演進(jìn)過(guò)程中,解決的那些關(guān)鍵點(diǎn),并找到一些規(guī)律。也可指導(dǎo)我們?cè)跇?gòu)建高并發(fā)、高性能系統(tǒng)時(shí),應(yīng)該注意哪些環(huán)節(jié)。
-
如何更有效的利用單機(jī)資源?開源軟件在高性能、高并發(fā)中做了哪些實(shí)踐。
-
如何在高并發(fā)前提下,利用跨機(jī)器遠(yuǎn)程調(diào)用提升并發(fā)及“性能”。分布式服務(wù)如何拆分,怎么拆分才能達(dá)到高性能高可用,并不浪費(fèi)資源?
注:太多的調(diào)用鏈路,性能是有很大損耗的。
... ...
篇幅有限,文章不會(huì)鋪開講所有細(xì)節(jié)。
—?2?—?從網(wǎng)絡(luò)連接開始
? ? 瀏覽器/app與后端通信一般使用http、https協(xié)議,底層都是使用TCP(Transmission Control Protocol 傳輸控制協(xié)議),而RPC遠(yuǎn)程調(diào)用可直接使用TCP連接。我們從TCP連接開始文章。
? ? 大家都知道TCP 三次握手建立連接、四次揮手?jǐn)嚅_連接,簡(jiǎn)述如下:
-
建立連接都是客戶端主動(dòng)發(fā)起,經(jīng)過(guò)三次交替交互后(中間會(huì)有狀態(tài)),雙方狀態(tài)都變?yōu)?ESTABLISHED狀態(tài),可以開始雙工數(shù)據(jù)傳送。
-
斷開連接雙方都可以主動(dòng)發(fā)起, 分別發(fā)起、回復(fù)一共四次交互(中間會(huì)有狀態(tài)),關(guān)閉連接。
注:詳細(xì)細(xì)節(jié)請(qǐng)參閱相關(guān)文檔,Windows和Linux服務(wù)器都可以使用netstat -an命令查看。
網(wǎng)絡(luò)編程中,關(guān)于連接這塊我們一般會(huì)關(guān)注以下指標(biāo):
1、連接相關(guān)
? ? 服務(wù)端能保持,管理,處理多少客戶端的連接。
-
活躍連接數(shù):所有ESTABLISHED狀態(tài)的TCP連接,某個(gè)瞬時(shí),這些連接正在傳輸數(shù)據(jù)。如果您采用的是長(zhǎng)連接的情況,一個(gè)連接會(huì)同時(shí)傳輸多個(gè)請(qǐng)求。也可以間接考察后端服務(wù)并發(fā)處理能力,注意不同于并發(fā)量。
-
非活躍連接數(shù):表示除ESTABLISHED狀態(tài)的其它所有狀態(tài)的TCP連接數(shù)。
-
并發(fā)連接數(shù):所有建立的TCP連接數(shù)量。并發(fā)連接數(shù) = 活躍連接數(shù) + 非活躍連接數(shù)。
-
新建連接數(shù):在統(tǒng)計(jì)周期內(nèi),從客戶端連接到服務(wù)器端,新建立的連接請(qǐng)求的平均數(shù)。主要考察應(yīng)對(duì) 突發(fā)流量或從正常到高峰流量的能力。如:秒殺、搶票場(chǎng)景。
-
丟棄連接數(shù):每秒丟棄的連接數(shù)。如果連接服務(wù)器做了連接熔斷處理,這部分?jǐn)?shù)據(jù)即熔斷的連接。
? ? 關(guān)于tcp連接數(shù)量,在linux下,跟文件句柄描述(fd)項(xiàng)有關(guān),可以u(píng)limit -n查看,也可修改。其它就是跟硬件資源cpu、內(nèi)存、網(wǎng)絡(luò)帶寬有關(guān)。單機(jī)可以做到數(shù)十萬(wàn)級(jí)的并發(fā)連接數(shù),如何實(shí)現(xiàn)呢?后面IO模型時(shí)講解。
2、流量相關(guān)
? ? 主要是網(wǎng)絡(luò)帶寬的配置。
-
流入流量:從外部訪問(wèn)服務(wù)器所消耗的流量。
-
流出流量:服務(wù)器對(duì)外響應(yīng)的流量。
3、數(shù)據(jù)包數(shù)?
? ? 數(shù)據(jù)包是TCP三次握手建立連接后,傳輸?shù)膬?nèi)容封裝
-
流入數(shù)據(jù)包數(shù):服務(wù)器每秒接到的請(qǐng)求數(shù)據(jù)包數(shù)量。
-
流出數(shù)據(jù)包數(shù):服務(wù)器每秒發(fā)出的數(shù)據(jù)包數(shù)量。
? ? 關(guān)于TCP/IP包的細(xì)節(jié)請(qǐng)查閱相關(guān)文檔。但是有一點(diǎn)一定注意,我們單次請(qǐng)求可能會(huì)分成多個(gè)包發(fā)送,拆包、粘包問(wèn)題網(wǎng)絡(luò)中間件都會(huì)為我們處理(比如消息補(bǔ)齊、回車結(jié)尾、自定義消息頭體、自定義協(xié)議等解決方案)。如果我們傳遞的用戶數(shù)據(jù)較小,那么效率肯定會(huì)提升。反過(guò)來(lái)無(wú)限制的壓縮傳輸包的大小,解壓也會(huì)耗費(fèi)cpu資源,需平衡處理。
4、應(yīng)用傳輸協(xié)議
? ? 傳輸協(xié)議壓縮率好,傳輸性能好,對(duì)并發(fā)性能提升高。但是也需要看調(diào)用雙方的語(yǔ)言可以使用協(xié)議才行。可以自己定義,也可以使用成熟的傳輸協(xié)議。比如redis的序列化傳輸協(xié)議、json傳輸協(xié)議、Protocol Buffers傳輸協(xié)議、http協(xié)議等。? 尤其在 rpc調(diào)用過(guò)程中,這個(gè)傳輸協(xié)議選擇需要仔細(xì)甄別選型。
5、長(zhǎng)、短連接
-
長(zhǎng)連接是指在一個(gè)TCP連接上,可以重用多次發(fā)送數(shù)據(jù)包,在TCP連接保持期間,如果沒(méi)有數(shù)據(jù)包發(fā)送,需要雙方發(fā)檢測(cè)包以維持此連接。?
-
半開連接的處理:當(dāng)客戶端與服務(wù)器建立起正常的TCP連接后,如果客戶主機(jī)掉線(網(wǎng)線斷開)、電源掉電、或系統(tǒng)崩潰,服務(wù)器將永遠(yuǎn)不會(huì)知道。長(zhǎng)連接中間件,需要處理這個(gè)細(xì)節(jié)。linux默認(rèn)配置2小時(shí),可以通過(guò)配置修改。
-
短連接是指通信雙方有數(shù)據(jù)交互時(shí),就建立一個(gè)TCP連接,數(shù)據(jù)發(fā)送完成后,則斷開此TCP連接。但是每次建立連接需要三次握手、斷開連接需要四次揮手。
-
關(guān)閉連接最好由客戶端主動(dòng)發(fā)起,TIME_WAIT這個(gè)狀態(tài)最好不要在服務(wù)器端,減少占用資源。
??? 選擇建議:
-
在客戶端數(shù)量少場(chǎng)景一般使用長(zhǎng)連接。后端中間件、微服務(wù)之間通信最好使用長(zhǎng)連接。如:數(shù)據(jù)庫(kù)連接,duboo默認(rèn)協(xié)議等。?
-
而大型web、app應(yīng)用,使用http短連接(http1.1的keep alive變相的支持長(zhǎng)連接,但還是串行請(qǐng)求/響應(yīng)交互)。http2.0支持真正的長(zhǎng)連接。
-
長(zhǎng)連接會(huì)對(duì)服務(wù)端耗費(fèi)更多的資源,上百萬(wàn)用戶,每個(gè)用戶獨(dú)占一個(gè)連接,對(duì)服務(wù)端壓力多大,成本多高。IM、push應(yīng)用會(huì)使用長(zhǎng)連接,但是會(huì)做很多優(yōu)化工作。
-
由于https需要加解密運(yùn)算等,最好使用http2.0(強(qiáng)制ssl),傳輸性能很好。但是服務(wù)端需要維持更多的連接。
6、關(guān)于并發(fā)連接與并發(fā)量
-
并發(fā)連接數(shù):= 活躍連接數(shù) + 非活躍連接數(shù)。所有建立的TCP連接數(shù)量。網(wǎng)絡(luò)服務(wù)器能并行管理的連接數(shù)。
-
活躍連接數(shù):所有ESTABLISHED狀態(tài)的TCP連接。
-
并發(fā)量:瞬時(shí)通過(guò)活躍連接傳輸數(shù)據(jù)的量,這個(gè)量一般在處理端好評(píng)估。跟活躍連接數(shù)沒(méi)有絕對(duì)的關(guān)系。網(wǎng)絡(luò)服務(wù)器能并行處理的業(yè)務(wù)請(qǐng)求數(shù)。
-
rt響應(yīng)時(shí)間:各類操作單機(jī)rt肯定不相同。比如:從cache中讀數(shù)據(jù)和分布式事務(wù)寫數(shù)據(jù)庫(kù),資源的消耗不同,操作時(shí)間本身就不同。
-
吞吐量:QPS/TPS,每秒可以處理的查詢或事務(wù)數(shù),這個(gè)是關(guān)鍵指標(biāo)。
從系統(tǒng)整體層面、各個(gè)服務(wù)個(gè)體、服務(wù)中某個(gè)方法都需綜合考慮。
舉例如下:
-
打開商品詳情頁(yè)操作,需要?jiǎng)屿o分離。后續(xù)一連串的動(dòng)態(tài)服務(wù)、cache機(jī)制,整體rt本身會(huì)短,單機(jī)可以支持的qps較高。(服務(wù)間、方法間也有差別)
-
而提交訂單操作需要分布式事務(wù)、分布式鎖等,rt本身會(huì)長(zhǎng),單機(jī)可支持的qps較低。
-
那是否我們就會(huì)針對(duì)訂單提交的服務(wù)部署更多機(jī)器呢?答案是不一定。因?yàn)橛脩魹g覽商品的頻度會(huì)很高,而提交訂單的頻度很低。如何正確的評(píng)估呢?
-
需要服務(wù)分類:關(guān)鍵服務(wù)/非關(guān)鍵服務(wù)、高峰各服務(wù)的qps需求,來(lái)均衡考慮。
? ? 系統(tǒng)整體吞吐量、RT響應(yīng)時(shí)間、支持并發(fā)數(shù) 是由小的操作、微服務(wù)組成的,各個(gè)微服務(wù)、操作也需要分別評(píng)估。平衡組合后,形成系統(tǒng)整體的各項(xiàng)指標(biāo)。
7、小節(jié)
首先看一個(gè)典型的互聯(lián)網(wǎng)服務(wù)端處理網(wǎng)絡(luò)請(qǐng)求的典型過(guò)程:
注:另外關(guān)于用戶態(tài)、內(nèi)核態(tài)數(shù)據(jù)轉(zhuǎn)換,有些特殊場(chǎng)景中,中間件如kafka可以使用zero copy技術(shù),避免兩態(tài)切換開銷。
a、(1,2,3 )三個(gè)步驟表示客戶端網(wǎng)絡(luò)請(qǐng)求,建立連接(管理連接),發(fā)送請(qǐng)求,服務(wù)器接收請(qǐng)求數(shù)據(jù)。
b、(4)構(gòu)建響應(yīng),在用戶空間處理客戶端的請(qǐng)求,構(gòu)建響應(yīng)完成。
c、(5,6,7) 服務(wù)器把響應(yīng),通過(guò)a中fd連接,send發(fā)送響應(yīng)客戶端。
? ? 可以把上面分為兩個(gè)關(guān)鍵點(diǎn):
-
a和c 服務(wù)器如何管理網(wǎng)絡(luò)連接,從客戶端獲得輸入數(shù)據(jù),為客戶端響應(yīng)數(shù)據(jù)。
-
b服務(wù)器如處理請(qǐng)求。
網(wǎng)絡(luò)應(yīng)用應(yīng)該考慮平衡a+c和b,處理這些連接的能力 與 能管理的連接請(qǐng)求達(dá)到平衡。?
比如:有個(gè)應(yīng)用并發(fā)連接數(shù)十萬(wàn);而這些連接大約每秒請(qǐng)求2萬(wàn)次;需要管理10萬(wàn)連接,每秒處理2萬(wàn)請(qǐng)求能能力,才能達(dá)到平衡。如何達(dá)到處理高qps呢,兩個(gè)方向:
-
單機(jī)優(yōu)化(見(jiàn)后中間件例子)
-
轉(zhuǎn)發(fā)到別的多臺(tái)機(jī)器處理(遠(yuǎn)程調(diào)用)
注:一般系統(tǒng)管理連接能力遠(yuǎn)遠(yuǎn)大于處理能力。
如上圖,客戶端的請(qǐng)求會(huì)形成一個(gè)大隊(duì)列;服務(wù)器會(huì)處理這個(gè)大隊(duì)列中的任務(wù)。這個(gè)隊(duì)列能有多大,看連接管理能力;如何保證進(jìn)入隊(duì)列任務(wù)的速率和處理移除任務(wù)的速度平衡,是關(guān)鍵。達(dá)到平衡是目的。
—?3?—?網(wǎng)絡(luò)編程中常用IO模型
? ? 客戶端與服務(wù)器的交互都會(huì)產(chǎn)生個(gè)連接,linux中在服務(wù)器端由文件描述項(xiàng) fd、socket編程中socket連接、java語(yǔ)言api中channel等體現(xiàn)。而IO模型,可以理解為管理fd,并通過(guò)fd從客戶端read獲取數(shù)據(jù)(客戶端請(qǐng)求)和通過(guò)fd往客戶端write數(shù)據(jù)(響應(yīng)客戶端)的機(jī)制。
? ? 關(guān)于同步,異步、阻塞、非阻塞 IO操作,網(wǎng)上、書籍上描述都不相同,也找不到準(zhǔn)確描述。我們按照《UNIX網(wǎng)絡(luò)編程:卷一》第六章——I/O復(fù)用為標(biāo)準(zhǔn)。書中向我們提及了5種類UNIX下可用的I/O模型:阻塞式I/O、非阻塞式I/O、I/O復(fù)用(selece,poll,epoll)、信號(hào)驅(qū)動(dòng)式I/O、異步I/O。(詳細(xì)可以查閱相關(guān)書籍資料)
1、阻塞式I/O:進(jìn)程會(huì)卡在recvfrom的調(diào)用,等到最終結(jié)果數(shù)據(jù)返回。肯定屬于同步。?
? ? 2、非阻塞式I/O:進(jìn)程反復(fù)輪詢調(diào)用recvfrom,直到最終結(jié)果數(shù)據(jù)返回。也是同步調(diào)用,但是IO內(nèi)核處理是非阻塞的。沒(méi)什么實(shí)用意義,不討論應(yīng)用。
? ? 3、I/O復(fù)用也屬于同步:進(jìn)程卡在select、epoll調(diào)用上,不會(huì)卡在recvfrom上,直到最終結(jié)果返回。?
注:select 模型:把要管理的fd放到一個(gè)數(shù)組里,循環(huán)這個(gè)數(shù)組。數(shù)組大小1024,可管理連接有限。poll 與select類似,只是把數(shù)組類型改為鏈表,沒(méi)有1024大小限制。
? ? 而epoll 為 event poll,只會(huì)管理有事件發(fā)生的 fd,也就是只會(huì)處理活躍的連接。epoll通過(guò)內(nèi)核和用戶空間共享一塊mmap()文件映射內(nèi)存來(lái)實(shí)現(xiàn)的消息傳遞。參考? http://libevent.org/
? ? 4、信號(hào)驅(qū)動(dòng)式I/O:也是同步。只有unix實(shí)現(xiàn),不討論。
? ? 5、異步:只有異步I/O屬于真正的異步。底層操作系統(tǒng)只有window實(shí)現(xiàn),不討論。nodejs中間件通過(guò)回調(diào)實(shí)現(xiàn),java AIO也有實(shí)現(xiàn)。開發(fā)難度較大。
IO模型中同步/異步、阻塞/非阻塞的差別(好繞):
-
同步異步:訪問(wèn)數(shù)據(jù)的方式,同步需主動(dòng)讀寫數(shù)據(jù),要求被調(diào)用方IO返回最終的結(jié)果。而異步發(fā)出請(qǐng)求后,只需等待IO操作完成的通知,并不主動(dòng)讀寫數(shù)據(jù),由系統(tǒng)內(nèi)核完成;
-
而阻塞和非租塞的區(qū)別在于,進(jìn)程或線程要訪問(wèn)的數(shù)據(jù)是否就緒,進(jìn)程或線程是否需要等待;等待就是阻塞,不需要等待就是非阻塞。
而我們平時(shí)在編程、函數(shù)接口調(diào)用過(guò)程中,除了超時(shí)以外,都會(huì)返回一個(gè)結(jié)果。同步異步調(diào)用按照以下區(qū)分:
-
如果返回的結(jié)果是最終結(jié)果,就是同步調(diào)用,如:調(diào)用數(shù)據(jù)查詢sql。
-
如果返回的結(jié)果是個(gè)中間通知,那么是異步:如:發(fā)送消息給mq,只會(huì)返回ack信息。對(duì)于發(fā)消息來(lái)說(shuō),是同步;如果從系統(tǒng)架構(gòu)層面看,算異步,因?yàn)樘幚斫Y(jié)果由消息消費(fèi)者來(lái)處理產(chǎn)生。如果發(fā)送成功,但是突然斷網(wǎng)沒(méi)有收到ack,這是屬于故障,不在討論范圍內(nèi)。
-
同步調(diào)用,參數(shù)中可以傳遞一個(gè)回調(diào)函數(shù)的方式:需要語(yǔ)言或中間件引擎執(zhí)行。如jvm支持,node v8引擎支持。(需要回調(diào)函數(shù)的執(zhí)行,跟調(diào)用端在一個(gè)context內(nèi),共享?xiàng)W兞康?#xff09;
注:select關(guān)鍵字可別混淆!!!IO多路復(fù)用從技術(shù)實(shí)現(xiàn)上有多種:select、poll、epoll 詳細(xì)自己參閱資料,幾乎所有中間件都會(huì)使用epoll模式。另外由于各個(gè)操作系統(tǒng)對(duì)多路復(fù)用實(shí)現(xiàn)機(jī)制不同,epoll、kqueue、IOCP接口都有自己的特點(diǎn),第三方庫(kù)封裝了這些差異,提供統(tǒng)一的API,如Libevent。另外如java語(yǔ)言,netty提供更高層面的封裝,javaNIO和netty使用保留了select方法,也引起一些混淆。?
小節(jié):現(xiàn)在網(wǎng)絡(luò)中間件都是用 阻塞IO和IO多路復(fù)用這兩個(gè)模型來(lái)管理連接,通過(guò)網(wǎng)絡(luò)IO獲取數(shù)據(jù)。下節(jié)講解,使用IO模型的一些中間件案例。
—?4?—?同步阻塞IO模型的具體實(shí)現(xiàn)模型-PPC,TPC
? ? 服務(wù)器處理數(shù)據(jù)問(wèn)題,從純網(wǎng)絡(luò)編程技術(shù)角度看,主要思路有兩個(gè):
-
一個(gè)是對(duì)于每個(gè)連接處理分配一個(gè)獨(dú)立的進(jìn)程/線程,直到處理完成。PPC,TPC模式;
-
另一個(gè)思路是用同一進(jìn)程/線程來(lái)同時(shí)處理若干連接,處理連接中數(shù)據(jù),通過(guò)多線程、多進(jìn)程技術(shù)。Reactor模式;
每個(gè)進(jìn)程/線程處理一個(gè)連接,叫PPC或TPC。PPC是Process Per Connection, TPC是Thread Per Conection ,傳統(tǒng)阻塞IO模型實(shí)現(xiàn)的網(wǎng)絡(luò)服務(wù)器采用這種模式。
? ??注:close特指主進(jìn)程對(duì)連接的計(jì)數(shù),連接實(shí)際在子進(jìn)程中關(guān)閉。而多線程實(shí)現(xiàn)中,主線程不需要close操作,因?yàn)楦缸泳€程共享存儲(chǔ)。如:java中jmm
注:pre模式,預(yù)先創(chuàng)建線程和進(jìn)程,連接進(jìn)來(lái),分配到預(yù)先創(chuàng)建好的線程或進(jìn)程。多進(jìn)程時(shí)有驚群現(xiàn)象。
申請(qǐng)線程或進(jìn)程會(huì)占用很多系統(tǒng)資源,操作系統(tǒng)cpu、內(nèi)存有限度,能同時(shí)管理的線程有限,處理連接的線程不能太多。雖然可以提前建立好進(jìn)程或線程來(lái)處理數(shù)據(jù)(prefork/prethead)或通過(guò)線程池來(lái)減少線程建立壓力。但是線程池的大小是個(gè)天花板。另外父子進(jìn)程通信也比較復(fù)雜。
apache MPM prefork(ppc),可支持256的并發(fā)連接,tomcat 同步IO(tpc)采用阻塞IO方式工作,可支持500個(gè)并發(fā)連接。java可以創(chuàng)建線程池來(lái)降低一定創(chuàng)建線程資源開銷來(lái)處理。
網(wǎng)絡(luò)連接fd可以支持上萬(wàn)個(gè),但是每個(gè)線程需要占有系統(tǒng)內(nèi)存,線程同時(shí)存在的總數(shù)有限。linux下用命令ulimit -s可以查看棧內(nèi)存分配。線程多了對(duì)cup的資源調(diào)度開銷。失衡情況發(fā)生,如何解決呢?
小節(jié):ppc、tpc瓶頸是能夠管理的連接數(shù)少。本來(lái)多線程處理業(yè)務(wù)能力夠,這下與fd綁定了,線程生命周期與fd一樣了,限定了線程處理能力。拆分:把fd生命周期與線程的生命周期拆分開來(lái)。
—?5?—?IO模型的具體實(shí)現(xiàn)模型-Reactor
? ? 每個(gè)進(jìn)程/線程同時(shí)處理多個(gè)連接(IO多路復(fù)用),多個(gè)連接共用一個(gè)阻塞對(duì)象,應(yīng)用程序只需要在一個(gè)阻塞對(duì)象上等待,無(wú)需阻塞等待所有連接。當(dāng)某條連接有新的數(shù)據(jù)可以處理時(shí),操作系統(tǒng)通知應(yīng)用程序,線程從阻塞狀態(tài)返回(還有更好優(yōu)化,見(jiàn)下小節(jié)),開始進(jìn)行業(yè)務(wù)處理;就是Reactor模式思想。
? ??Reactor 模式,是指通過(guò)一個(gè)或多個(gè)輸入同時(shí)傳遞給服務(wù)處理器的服務(wù)請(qǐng)求的事件驅(qū)動(dòng)處理模式。服務(wù)端程序處理客戶端傳入的多路請(qǐng)求,并將它們同步分派給請(qǐng)求對(duì)應(yīng)的處理線程,Reactor 模式也叫 Dispatcher 模式。即 I/O 多路復(fù)用統(tǒng)一監(jiān)聽(tīng)事件,收到事件后分發(fā)(Dispatch 給某進(jìn)程),是編寫高性能網(wǎng)絡(luò)服務(wù)器的必備技術(shù)之一。很多優(yōu)秀的網(wǎng)絡(luò)中間件都是基于該思想的實(shí)現(xiàn)。
注:由于epoll比select管理的連接數(shù)大了好多,libevent,netty等框架中底層實(shí)現(xiàn)都是epoll方式,但是編程API保留了select關(guān)鍵字。所以文章中epoll_wait跟select等同。
Reactor模式有幾個(gè)關(guān)鍵的組成:
-
Reactor:Reactor在一個(gè)單獨(dú)的線程運(yùn)行,負(fù)責(zé)監(jiān)聽(tīng)fd事件,分發(fā)給適當(dāng)?shù)奶幚沓绦驅(qū)O事件做出反應(yīng)。建立連接事件分發(fā)給Acceptor;分發(fā)read/write處理事件給Handler。
-
Acceptor:負(fù)責(zé)處理建立連接事件,并建立對(duì)應(yīng)的Handler對(duì)象。
-
Handlers:負(fù)責(zé)處理read和write事件。從fd中獲取請(qǐng)求數(shù)據(jù);處理數(shù)據(jù)得到相應(yīng)數(shù)據(jù);send相應(yīng)數(shù)據(jù)。處理程序執(zhí)行IO事件要完成的實(shí)際事情。
? ? 對(duì)于IO密集型(IO bound)場(chǎng)景,可以使用Reactor場(chǎng)景,但是ThreadLocal將不能使用。開發(fā)調(diào)試難度較大,一般不建議自己實(shí)現(xiàn),使用現(xiàn)有框架即可。
小節(jié):Reactor解決可管理的網(wǎng)絡(luò)連接數(shù)量提升到幾十萬(wàn)。但是如此多連接上請(qǐng)求任務(wù),還是需要通過(guò)多線程、多進(jìn)程機(jī)制處理。甚至負(fù)載轉(zhuǎn)發(fā)到其它服務(wù)器處理。
—?6?—?Reactor模式實(shí)踐案例(C語(yǔ)言)
? ? 通過(guò)幾個(gè)開源框架的例子,了解不同場(chǎng)景下的網(wǎng)絡(luò)框架,是如何使用Reactor模式,做了哪些細(xì)節(jié)調(diào)整。
注:實(shí)際實(shí)現(xiàn)肯定與圖差別很大。客戶端io及send比較簡(jiǎn)單,圖中省略。
A、單Reactor+單線程處理(整體一個(gè)線程)redis為代表
如圖所示:
客戶端請(qǐng)求->Reactor對(duì)象接受請(qǐng)求,并通過(guò)select(epoll_wait)監(jiān)聽(tīng)請(qǐng)求事件->通過(guò)dispatch分發(fā)事件;
如果是連接請(qǐng)求事件->dispatch->Acceptor(accept建立連接)->為這個(gè)連接創(chuàng)建一個(gè)Handler 對(duì)象等待后續(xù)業(yè)務(wù)處理。
如果不是建立連接事件->dispatch分發(fā)事件->觸發(fā)到為這個(gè)連接創(chuàng)建的那個(gè)Handler對(duì)象(read、業(yè)務(wù)處理、send),形成一個(gè)任務(wù)/命令隊(duì)列。
Handler對(duì)象完成read->業(yè)務(wù)處理->send整體流程。
把請(qǐng)求轉(zhuǎn)化為命令隊(duì)列,單進(jìn)程處理。注意圖中 隊(duì)列,單線程處理,是沒(méi)有競(jìng)爭(zhēng)的。
優(yōu)點(diǎn):
-
模型簡(jiǎn)單。這個(gè)模型是最簡(jiǎn)單的,代碼實(shí)現(xiàn)方便,適合計(jì)算密集型應(yīng)用
-
不用考慮并發(fā)問(wèn)題。模型本身是單線程的,使得服務(wù)的主邏輯也是單線程的,那么就不用考慮許多并發(fā)的問(wèn)題,比如鎖和同步
-
適合短耗時(shí)服務(wù)。對(duì)于像redis這種每個(gè)事件基本都是查內(nèi)存,是十分適合的,一來(lái)并發(fā)量可以接受,二來(lái)redis內(nèi)部眾多數(shù)據(jù)結(jié)構(gòu)都是非常簡(jiǎn)單地實(shí)現(xiàn)
缺點(diǎn):
-
性能問(wèn)題,只有一個(gè)線程,無(wú)法完全發(fā)揮多核 CPU 的性能。
-
順序執(zhí)行影響后續(xù)事件。因?yàn)樗刑幚矶际琼樞驁?zhí)行的,所以如果面對(duì)長(zhǎng)耗時(shí)的事件,會(huì)延遲后續(xù)的所有任務(wù),特別對(duì)于io密集型的應(yīng)用,是無(wú)法承受的
-
這也是為什么redis禁止大家使用耗時(shí)命令? ??
? ??注:redis是自己實(shí)現(xiàn)的io多路復(fù)用,沒(méi)有使用libevent,實(shí)現(xiàn)與圖不符,更加輕巧。
這種模型對(duì)于處理讀寫事件操作很短很短時(shí)間內(nèi)執(zhí)行完。大約可達(dá)到10萬(wàn)QPS吞吐量(redis各種命令差別很大)。
注:redis發(fā)布版本中自帶了redis-benchmark性能測(cè)試工具,可以使用它計(jì)算qps。示例:使用50個(gè)并發(fā)連接,發(fā)出100000個(gè)請(qǐng)求,每個(gè)請(qǐng)求的數(shù)據(jù)為2kb,測(cè)試host為127.0.0.1端口為6379的redis服務(wù)器性能:./redis-benchmark -h127.0.0.1 -p 6379 -c 50 -n 100000 -d 2
對(duì)于客戶端數(shù)量多的網(wǎng)絡(luò)系統(tǒng),強(qiáng)調(diào)多客戶端,也就是并發(fā)連接數(shù)。? 對(duì)于后端連接數(shù)少的的網(wǎng)絡(luò)系統(tǒng),采用長(zhǎng)連接,并發(fā)連接數(shù)少,但是每個(gè)連接發(fā)起的請(qǐng)求數(shù)多。
B、單 Reactor+單隊(duì)列+業(yè)務(wù)線程池
? ? 如圖所示,我們按把真正的業(yè)務(wù)處理從 Reactor線程中剝離出來(lái),通過(guò)業(yè)務(wù)線程池來(lái)實(shí)現(xiàn)。那么Reactor中每個(gè)fd的Handler對(duì)象如何與 Worker線程池通信的,通過(guò)待處理請(qǐng)求隊(duì)列 。客戶端對(duì)服務(wù)器的請(qǐng)求,本來(lái)可以想象成一個(gè)請(qǐng)求隊(duì)列IO, 這里經(jīng)過(guò)Reactor(多路復(fù)用)處理后,(拆分)轉(zhuǎn)化為一個(gè)待處理工作任務(wù)的隊(duì)列。?
注:處處是拆分啊!
? ? 業(yè)務(wù)線程池分配獨(dú)立的線程池,從隊(duì)列中拿到數(shù)據(jù)進(jìn)行真正的業(yè)務(wù)處理,將結(jié)果返回Handler。Handler收到響應(yīng)結(jié)果后,send結(jié)果給客戶端。
與A模型相比,利用線程池技術(shù)加快了客戶端請(qǐng)求處理能力。例如:thrift0.10.0版本中 nonblocking server ?采用這種模型,能達(dá)到幾萬(wàn)級(jí)別的QPS。
缺點(diǎn):這種模型的缺點(diǎn)就在于這個(gè)隊(duì)列上,是性能瓶頸。線程池從隊(duì)列獲取任務(wù)需要加鎖,會(huì)采用高性能的讀寫鎖實(shí)現(xiàn)隊(duì)列。
C、單 Reactor+N隊(duì)列+N線程
這種模型是 A和B的變種模型,memcached采用這種模型。待處理工作隊(duì)列分為多個(gè),每個(gè)隊(duì)列綁定一個(gè)線程來(lái)處理,這樣最大的發(fā)揮了IO多路復(fù)用對(duì)網(wǎng)絡(luò)連接的管理,把單隊(duì)列引起的瓶頸得到釋放。QPS估計(jì)可達(dá)到20萬(wàn)。
但是這種方案有個(gè)很大的缺點(diǎn),負(fù)載均衡可能導(dǎo)致有些隊(duì)列忙,有些空閑。好在memcached 也是內(nèi)存的操作,對(duì)負(fù)載問(wèn)題不是很敏感,可以使用該模型。
D、單進(jìn)程Reactor監(jiān)聽(tīng)+N進(jìn)程(accept+epoll_wait+處理)模型
流程:
master(Reactor主進(jìn)程)進(jìn)程監(jiān)聽(tīng)新連接的到來(lái),并讓其中一個(gè)worker進(jìn)程accept。這里需要處理驚群效應(yīng)問(wèn)題,詳見(jiàn)nginx的accept_mutex設(shè)計(jì)
worker(subReactor進(jìn)程)進(jìn)程accept到fd之后,把fd注冊(cè)到到本進(jìn)程的epoll句柄里面,由本進(jìn)程處理這個(gè)fd的后續(xù)讀寫事件
worker進(jìn)程根據(jù)自身負(fù)載情況,選擇性地不去accept新fd,從而實(shí)現(xiàn)負(fù)載均衡
優(yōu)點(diǎn):
-
進(jìn)程掛掉不會(huì)影響這個(gè)服務(wù)
-
是由worker主動(dòng)實(shí)現(xiàn)負(fù)載均衡的,這種負(fù)載均衡方式比由master來(lái)處理更簡(jiǎn)單
缺點(diǎn):
-
多進(jìn)程模型編程比較復(fù)雜,進(jìn)程間同步?jīng)]有線程那么簡(jiǎn)單
-
進(jìn)程的開銷比線程更多
nginx使用這種模型,由于nginx主要提供反向代理與靜態(tài)內(nèi)容web服務(wù)功能,qps指標(biāo)與被nginx代理的處理服務(wù)器有關(guān)系。
注:nodejs多進(jìn)程部署方式與nginx方式類似。
小節(jié):期望從這幾個(gè) Reactor的實(shí)例中,找到拆分解決了哪些問(wèn)題,引起了哪些問(wèn)題。
—?7?—?Reactor模式實(shí)踐案例(Java語(yǔ)言Netty)
? ? Netty是 一個(gè)異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架,用于快速開發(fā)可維護(hù)的高性能協(xié)議服務(wù)器和客戶端,java語(yǔ)言的很多開源網(wǎng)絡(luò)中間件使用了netty,本文只描述針對(duì)NIO多路復(fù)用相關(guān)部分,很多拆包粘包、定時(shí)任務(wù)心跳監(jiān)測(cè)、序列化鉤子等等可參閱資料。如圖所示:
netty可以通過(guò)配置,來(lái)實(shí)現(xiàn)各個(gè)模塊在哪個(gè)線程(池)中運(yùn)行:
1、單Reactor單線程
EventLoopGroup?bossGroup?=?new?NioEventLoopGroup(1);//netty默認(rèn)只會(huì)單Reactor EventLoopGroup?workerGroup?=?bossGroup?;//監(jiān)聽(tīng)線程和工作線程使用一個(gè) ServerBootstrap?server?=?new?ServerBootstrap(); server.group(bossGroup, workerGroup);2、單Reactor多線程subReactor
EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup();//默認(rèn)cup核心*2 ServerBootstrap server = new ServerBootstrap(); server.group(bossGroup, workerGroup);//主線程和工作線程分開3、單Reactor、多線程subReactor、指定線程池處理業(yè)務(wù)
https://netty.io/4.1/api/io/netty/channel/ChannelPipeline.html
? ? 我們?cè)谝粋€(gè)pipeline中定義多個(gè)ChannelHandler,用以接收I / O事件(例如,讀取)和請(qǐng)求I / O操作(例如,寫入和關(guān)閉)。例如,典型的服務(wù)器在每channel的pipiline中,都有以下Handler:(具體取決于使用的協(xié)議和業(yè)務(wù)邏輯的復(fù)雜性和特征):
-
Protocol Decoder - 將二進(jìn)制數(shù)據(jù)(例如ByteBuf)轉(zhuǎn)換為Java對(duì)象。
-
Protocol Encoder - 將Java對(duì)象轉(zhuǎn)換為二進(jìn)制數(shù)據(jù)。
-
Business Logic Handler - 執(zhí)行實(shí)際的業(yè)務(wù)邏輯(例如數(shù)據(jù)庫(kù)訪問(wèn))。
如下例所示:
static final EventExecutorGroupgroup = new DefaultEventExecutorGroup(16);...ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(“decoder”,new MyProtocolDecoder());pipeline.addLast(“encoder”,new MyProtocolEncoder());//告訴這個(gè)MyBusinessLogicHandler的事件處理程序方法不在I / O線程中,//以便I?/?O線程不被阻塞,一項(xiàng)耗時(shí)的任務(wù)運(yùn)行在自定義線程組(池)//如果您的業(yè)務(wù)邏輯完全異步或很快完成,則不需要額外指定一個(gè)線程組。pipeline.addLast(group,“handler”,new MyBusinessLogicHandler());? ? 前文中提到過(guò),web應(yīng)用程序接受百萬(wàn)、千萬(wàn)的網(wǎng)絡(luò)連接,并管理轉(zhuǎn)化為請(qǐng)求、響應(yīng),就像一個(gè)大隊(duì)列一樣,如何更好的處理隊(duì)列里面的任務(wù),牽扯到負(fù)載均衡分配、鎖、阻塞、線程池、多進(jìn)程、轉(zhuǎn)發(fā)、同步異步等一系列負(fù)載問(wèn)題。? 單機(jī)及分布式都要優(yōu)化,netty做了很多優(yōu)化,這部分netty源碼不好讀懂:
業(yè)務(wù)處理與IO任務(wù)公用線程池 自定義線程池處理業(yè)務(wù)如圖所示:netty中, 不固定數(shù)量的channel、固定的NioEventLoop、可外置線程池的EventExecutor,在眾多channel不定時(shí)的事件驅(qū)動(dòng)下,如何協(xié)調(diào)線程很是復(fù)雜。
留個(gè)問(wèn)題:基于netty的spring webflux 、nodejs,為什么能支撐大量連接,而cpu成為瓶頸?
小節(jié):這樣我們從 客戶端發(fā)起請(qǐng)求->到服務(wù)端建立連接->服務(wù)端非阻塞監(jiān)聽(tīng)傳輸->業(yè)務(wù)處理->響應(yīng)? 整個(gè)流程,通過(guò)IO多路復(fù)用、線程池、業(yè)務(wù)線程池 讓整個(gè)處理鏈條沒(méi)有處理瓶頸、處理短板,達(dá)到整體高性能、高吞吐。
? ? 但是耗時(shí)處理能力遠(yuǎn)遠(yuǎn)低于IO連接的管理能力,單機(jī)都會(huì)達(dá)到天花板,繼續(xù)拆分(專業(yè)中間件干專業(yè)事),RPC、微服務(wù)調(diào)用是解決策略。
—?8?—?分布式遠(yuǎn)程調(diào)用(不是結(jié)尾才是開始)
? ? 由前文看出,單機(jī)的最終瓶頸會(huì)出在業(yè)務(wù)處理上。對(duì)java語(yǔ)言來(lái)說(shuō),線程數(shù)量不可能無(wú)限擴(kuò)大。就算使用go語(yǔ)言更小開銷的協(xié)程,cpu也會(huì)成為單機(jī)瓶頸。所以跨機(jī)器的分布式遠(yuǎn)程調(diào)用肯定是解決問(wèn)題的方向。業(yè)內(nèi)已經(jīng)有很多實(shí)踐,我們從三個(gè)典型架構(gòu)圖,看看演進(jìn)解決的問(wèn)題是什么,靠什么解決的:
注:本文不從soa,rpc,微服務(wù)等方面討論,只關(guān)注拆分的依據(jù)和目標(biāo)。
A、單體應(yīng)用
B、把網(wǎng)絡(luò)連接管理和靜態(tài)內(nèi)容拆分
C、業(yè)務(wù)功能性拆分
A:典型單體應(yīng)用。
A->B:連接管理與業(yè)務(wù)處理拆分。使用網(wǎng)絡(luò)連接管理能力強(qiáng)大的nginx,業(yè)務(wù)處理單獨(dú)拆分為多臺(tái)機(jī)器。
B->C:業(yè)務(wù)處理從功能角度拆分。有些業(yè)務(wù)側(cè)重協(xié)議解析、有些側(cè)重業(yè)務(wù)判斷、有些側(cè)重?cái)?shù)據(jù)庫(kù)操作,繼續(xù)拆分。
通過(guò)圖C,從高性能角度,看服務(wù)分層(各層技術(shù)選型也有很多)的準(zhǔn)則及需要注意點(diǎn):
1、反向代理層(關(guān)聯(lián)https連接)
-
可以通過(guò)nginx集群實(shí)現(xiàn),也可以通過(guò)lvs,f5實(shí)現(xiàn)。
-
通過(guò)上層nginx實(shí)現(xiàn),可以知道該層應(yīng)對(duì)的是大量http或https請(qǐng)求。
-
核心指標(biāo)是:并發(fā)連接數(shù)、活躍連接數(shù)、出入流量、出入包數(shù)、吞吐量等。
-
內(nèi)部關(guān)于協(xié)議解析模塊、壓縮模塊、包處理模塊優(yōu)化等。關(guān)鍵反向代理出去的請(qǐng)求吞吐量,也就是nginx轉(zhuǎn)發(fā)到后端應(yīng)用服務(wù)器的處理能力,決定整體吞吐量。
-
靜態(tài)文件都走cdn。
-
關(guān)于https認(rèn)證比較費(fèi)時(shí),建議使用http2.0,或保持連接時(shí)間長(zhǎng)點(diǎn)。但這也與業(yè)務(wù)情況有關(guān)。如:每個(gè)app與后端交互是否頻繁。畢竟維護(hù)太多連接,成本也很高,影響多路復(fù)用性能。
2、網(wǎng)關(guān)層(通用無(wú)業(yè)務(wù)的操作)
反向代理層通過(guò)http協(xié)議連接網(wǎng)關(guān)層,二者之間通過(guò)內(nèi)網(wǎng)ip通信,效率高很多。我們假定網(wǎng)關(guān)層往下游都使用tcp長(zhǎng)連接,java語(yǔ)言中dobbo等rpc框架都可以實(shí)現(xiàn)。
網(wǎng)關(guān)層主要做幾個(gè)事情:
-
鑒權(quán)
-
數(shù)據(jù)包完整性檢查
-
http json 傳輸協(xié)議轉(zhuǎn)化為java對(duì)象
-
路由轉(zhuǎn)義(轉(zhuǎn)化為微服務(wù)調(diào)用)
-
服務(wù)治理相關(guān)(限流、降級(jí)、熔斷等)功能
-
負(fù)載均衡
? ? 網(wǎng)關(guān)層可以由:有開源的Zuul,spring cloud gateway,nodejs等實(shí)現(xiàn)。nginx也可以做網(wǎng)關(guān)需要定制開發(fā),與反向代理層物理上合并。
3、業(yè)務(wù)邏輯層(業(yè)務(wù)層面的操作)
? ? 從這層可以考慮按照業(yè)務(wù)邏輯垂直分層。例如:用戶邏輯層、訂單邏輯層等。如果這樣拆分,可能會(huì)抽象一層通過(guò)的業(yè)務(wù)邏輯層。我們盡量保證業(yè)務(wù)邏輯層不橫向調(diào)用,只上游調(diào)用下游。
-
業(yè)務(wù)邏輯判斷
-
業(yè)務(wù)邏輯處理(組合)
-
分布式事務(wù)實(shí)現(xiàn)
-
分布式鎖實(shí)現(xiàn)
-
業(yè)務(wù)緩存
4、數(shù)據(jù)訪問(wèn)層(數(shù)據(jù)庫(kù)存儲(chǔ)相關(guān)的操作)
-
專注數(shù)據(jù)增刪改查操作。
-
orm封裝
-
隱藏分庫(kù)分表的細(xì)節(jié)。
-
緩存設(shè)計(jì)
-
屏蔽存儲(chǔ)層差異
-
數(shù)據(jù)存儲(chǔ)冪等實(shí)現(xiàn)
注:本節(jié)引用了孫玄老師《百萬(wàn)年薪架構(gòu)師課程》中一些觀點(diǎn),推薦一下這門課,從架構(gòu)實(shí)踐、微服務(wù)實(shí)現(xiàn)、服務(wù)治理等方面,從本質(zhì)到實(shí)戰(zhàn)面面俱到。
網(wǎng)關(guān)層以下,數(shù)據(jù)庫(kù)以上,RPC中間件技術(shù)選型及技術(shù)指標(biāo)如下(來(lái)源dubbo官網(wǎng)):
-
核心指標(biāo)是:并發(fā)量、TQps、Rt響應(yīng)時(shí)間。
-
選擇協(xié)議因素:dubbo、rmi、hession、webservice、thrift、memcached、redis、rest
-
連接個(gè)數(shù):長(zhǎng)連接一般單個(gè);短連接需要多個(gè)
-
是否長(zhǎng)連接:長(zhǎng)短連接
-
傳輸協(xié)議:TCP、http
-
傳輸方式::同步、NIO非阻塞
-
序列化:二進(jìn)制(hessian)
-
使用范圍:大文件、超大字符串、短字符串等
-
根據(jù)應(yīng)用場(chǎng)景選擇,一般默認(rèn)dubbo即可。
小節(jié):
單機(jī)時(shí)代:從每個(gè)線程管理一個(gè)網(wǎng)絡(luò)連接;再到通過(guò)io多路復(fù)用,單個(gè)線程管理網(wǎng)絡(luò)連接,騰出資源處理業(yè)務(wù);再到io線程池和業(yè)務(wù)線程池分離;大家能發(fā)現(xiàn)個(gè)規(guī)律,客戶端連接請(qǐng)求是總起點(diǎn)->后端處理能力逐步平衡加強(qiáng)的過(guò)程。業(yè)務(wù)處理能力總是趕不上接受處理的能力。
反向代理時(shí)代:nginx能夠管理的連接足夠的多了,后端可以轉(zhuǎn)發(fā)到N臺(tái)應(yīng)用服務(wù)器tomcat。從某種程度上,更加有效的利用的資源,通過(guò)硬件、軟件選型,把 管理連接(功能)和處理連接(功能)物理上拆分開,軟件和硬件配合處理自己更擅長(zhǎng)的事情。
SOA、微服務(wù)時(shí)代:(SOA的出現(xiàn)其實(shí)是為了低耦合,跟高性能高并發(fā)關(guān)系不大)業(yè)務(wù)處理有很多種類型。有的是運(yùn)算密集型;有的需要操作數(shù)據(jù)庫(kù);有的只需從cache讀一些數(shù)據(jù);有些業(yè)務(wù)使用率很高;有些使用頻度很低。為了更好利用又有了兩種拆分機(jī)制。把操作數(shù)據(jù)庫(kù)的服務(wù)單獨(dú)拆出來(lái)(數(shù)據(jù)訪問(wèn)層),把業(yè)務(wù)邏輯處理的拆分出來(lái)(業(yè)務(wù)邏輯層);按照以上邏輯推斷:可能一臺(tái)nginx+3臺(tái)tomcat網(wǎng)關(guān)+5臺(tái)duboo業(yè)務(wù)邏輯+10臺(tái)duboo數(shù)據(jù)訪問(wèn)配置合適。? 我們配置的目的是,各層處理的專屬的業(yè)務(wù)都能把服務(wù)器壓到60%資源占用。
注:文章只關(guān)注了功能層面的水平分層。而垂直層面也需要分層。例如:用戶管理和訂單管理是兩類不同的業(yè)務(wù),業(yè)務(wù)技術(shù)特點(diǎn)、訪問(wèn)頻次也不同。存儲(chǔ)層面也需要垂直分庫(kù)、分表。本文暫且略過(guò)。?
單機(jī)階段,多線程多進(jìn)程其實(shí)相當(dāng)于一種垂直并發(fā)拆分,盡量保證無(wú)狀態(tài),盡量避免鎖等,跟微服務(wù)無(wú)狀態(tài)、分布式鎖原理上是一致的。
—?9?—?總結(jié)
? ? 回顧前文,客戶端連接到服務(wù)器端后都要干什么呢?性能瓶頸是維護(hù)這么多連接?還是針對(duì)每個(gè)連接的處理達(dá)不到要求失衡?如何破局?從單機(jī)內(nèi)部、再到物理機(jī)器拆分的描述看來(lái),有三點(diǎn)及其重要:
關(guān)注平衡:達(dá)到平衡的架構(gòu),才可能是高性能、高并發(fā)架構(gòu)。任何性能問(wèn)題都會(huì)由某個(gè)點(diǎn)引起。甚至泛指業(yè)務(wù)需求與復(fù)雜度也要平衡。
拆分之道:合適的事情,讓合適的技術(shù)、合適的中間件解決。具體:如何橫向、縱向拆分還需分析場(chǎng)景。
了解業(yè)務(wù)場(chǎng)景、問(wèn)題本質(zhì)&&了解常用場(chǎng)景下解決方案:按照發(fā)現(xiàn)問(wèn)題、分析問(wèn)題、解決問(wèn)題思路來(lái)看,我們把彈藥庫(kù)備齊,解決問(wèn)題的過(guò)程,就是個(gè)匹配的過(guò)程。
除了文中提到的技術(shù)以及拆分方案,很多技術(shù)點(diǎn),都可以提升吞吐及性能,列舉如下:
-
IO多路復(fù)用:管理更多的連接
-
線程池技術(shù):挖掘多核cpu的潛力
-
zero-copy:減少用戶態(tài)和內(nèi)核態(tài)交互次數(shù)。如java中transferTo,linux中sendfile系統(tǒng)接口;
-
磁盤順序?qū)?#xff1a;降低尋址開銷。消息隊(duì)列或數(shù)據(jù)庫(kù)日志,都會(huì)采用此技術(shù)。
-
壓縮更好的協(xié)議:網(wǎng)絡(luò)傳輸上減少開支,如:自定義或二進(jìn)制傳輸協(xié)議;
-
分區(qū):在存儲(chǔ)系統(tǒng)中,分庫(kù)分表都算分區(qū);而微服務(wù)中,設(shè)計(jì)服務(wù)無(wú)狀態(tài),本身也可以理解為分區(qū)。
-
批量傳輸:典型數(shù)據(jù)庫(kù) batch技術(shù)。很多網(wǎng)絡(luò)中間件也可以使用,如消息隊(duì)列中。
-
索引技術(shù):這里不是特指數(shù)據(jù)庫(kù)的索引技術(shù)。而是我們?cè)O(shè)計(jì)切合業(yè)務(wù)場(chǎng)景的索引,提高效率。例如:kafka針對(duì)文件的存儲(chǔ),采用一些hack的索引技巧。
-
緩存設(shè)計(jì):當(dāng)數(shù)據(jù)生命修改不頻繁、變更規(guī)律性很強(qiáng)、生成一次成本太高時(shí),可以考慮緩存
-
空間換時(shí)間:其實(shí)分區(qū)、索引技術(shù)、緩存技術(shù)都可歸為這類。例如:我們使用倒排索引存儲(chǔ)數(shù)據(jù)、使用多份數(shù)據(jù)多份節(jié)點(diǎn)提供服務(wù)等。
-
網(wǎng)絡(luò)連接的選型:長(zhǎng)短連接,可靠、非可靠協(xié)議等。
-
拆包粘包:batch、協(xié)議選型于此有些關(guān)系。
-
高性能分布式鎖:并發(fā)編程中,鎖不可避免。盡量使用高性能的分布式鎖,能cas樂(lè)觀鎖,盡量避免悲觀鎖。如果業(yè)務(wù)允許,盡量異步鎖,不要同步阻塞鎖,減少鎖競(jìng)爭(zhēng)。
-
柔性事務(wù)代替剛性事務(wù):有些異常或者故障,試圖通過(guò)重試是恢復(fù)不了的。
-
最終一致性:如果業(yè)務(wù)場(chǎng)景允許,盡量保證數(shù)據(jù)最終一致性。
-
非核心業(yè)務(wù)異步化:把某些任務(wù)轉(zhuǎn)化為另外一個(gè)隊(duì)列(消息隊(duì)列),消費(fèi)端可以批量、多消費(fèi)者處理。
-
direct IO:例如數(shù)據(jù)庫(kù)等自己構(gòu)建緩存機(jī)制的應(yīng)用程序,直接使用directIO,放棄操作系統(tǒng)提供的緩存。
-
... .... 歡迎留言討論補(bǔ)充拍磚
注:脫離業(yè)務(wù)場(chǎng)景,很多只能是紙上談兵。但不了解手段,遇到場(chǎng)景也會(huì)懵逼。客戶端請(qǐng)求形成的超級(jí)隊(duì)列,后端如何分而治之、分散逐個(gè)擊破,是整體思想。
總結(jié)
以上是生活随笔為你收集整理的如何设计真正高性能高并发分布式系统(万字长文)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 史上最易懂的 Kubernetes 儿童
- 下一篇: 终于有篇看的懂的 B 树文章了!