溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的

發(fā)布時(shí)間:2021-09-29 09:47:38 來源:億速云 閱讀:129 作者:柒染 欄目:云計(jì)算

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的,針對這個(gè)問題,這篇文章詳細(xì)介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個(gè)問題的小伙伴找到更簡單易行的方法。


服務(wù)端通常需要支持高并發(fā)業(yè)務(wù)訪問,如何設(shè)計(jì)優(yōu)秀的服務(wù)端網(wǎng)絡(luò)IO工作線程/進(jìn)程模型對業(yè)務(wù)的高并發(fā)訪問需求起著至關(guān)重要的核心作用。

1. 線程模型一. 單線程網(wǎng)絡(luò)IO復(fù)用模型

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的

說明:

  1. 所有網(wǎng)絡(luò)IO事件(accept事件、讀事件、寫事件)注冊到epoll事件集

  2. 主循環(huán)中通過epoll_wait一次性獲取內(nèi)核態(tài)收集到的epoll事件信息,然后輪詢執(zhí)行各個(gè)事件對應(yīng)的回調(diào)。

  3. 事件注冊、epoll_wait事件獲取、事件回調(diào)執(zhí)行全部由一個(gè)線程處理

1.1一個(gè)完整請求組成

一個(gè)完整的請求處理過程主要包含以下幾個(gè)部分:

步驟1:通過epoll_wait一次性獲取網(wǎng)絡(luò)IO事件

步驟2:讀取數(shù)據(jù)及協(xié)議解析

步驟3:解析成功后進(jìn)行業(yè)務(wù)邏輯處理,然后應(yīng)答客戶端

1.2 該網(wǎng)絡(luò)線程模型缺陷

  1. 所有工作都由一個(gè)線程執(zhí)行,包括epoll事件獲取、事件處理(數(shù)據(jù)讀寫)、只要任一一個(gè)請求的事件回調(diào)處理阻塞,其他請求都會阻塞。例如redis的hash結(jié)構(gòu),如果filed過多,假設(shè)一個(gè)hash key包含數(shù)百萬filed,則該Hash key過期的時(shí)候,整個(gè)redis阻塞。

  2. 單線程工作模型,CPU會成為瓶頸,如果QPS過高,整個(gè)CPU負(fù)載會達(dá)到100%,時(shí)延抖動厲害。

1.3 典型案例

  1. redis緩存

  2. 推特緩存中間件twemproxy

1.4 主循環(huán)工作流程

while (1) {
	//epoll_wait等待網(wǎng)絡(luò)事件,如果有網(wǎng)絡(luò)事件則返回,或者超時(shí)范圍
	size_t numevents=  epoll_wait();

	//遍歷前面epoll獲取到的網(wǎng)絡(luò)事件,執(zhí)行對應(yīng)事件回調(diào)
	for (j = 0; j < numevents; j++) {
         if(讀事件) {
            //讀數(shù)據(jù)
	        readData()
//解析
parseData()
	        //讀事件處理、讀到數(shù)據(jù)后的業(yè)務(wù)邏輯處理
	        requestDeal()
         } else if(寫事件) {
	        //寫事件處理,寫數(shù)據(jù)邏輯處理
	        writeEentDeal()
         } else {
//異常事件處理
errorDeal()
         }
	 }
}

說明:后續(xù)多線程/進(jìn)程模型中,每個(gè)線程/進(jìn)程的主流程和該while()流程一致。

1.5 redis源碼分析及異步網(wǎng)絡(luò)IO復(fù)用精簡版demo

由于之前工作需要,需要對redis內(nèi)核做二次優(yōu)化開發(fā),因此對整個(gè)redis代碼做了部分代碼注釋,同時(shí)把redis的網(wǎng)絡(luò)模塊獨(dú)立出來做成了簡單demo,該demo對理解epoll網(wǎng)絡(luò)事件處理及Io復(fù)用實(shí)現(xiàn)會有幫助,代碼比較簡短,可以參考如下項(xiàng)目:

redis源碼詳細(xì)注釋分析

redis網(wǎng)絡(luò)模塊精簡版demo

推特緩存中間件twemproxy源碼分析實(shí)現(xiàn)

2. 線程模型二. 單listener+固定worker線程

該線程模型圖如下圖所示:

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的

說明:

  1. listener線程負(fù)責(zé)接受所有的客戶端鏈接

  2. listener線程每接收到一個(gè)新的客戶端鏈接產(chǎn)生一個(gè)新的fd,然后通過分發(fā)器發(fā)送給對應(yīng)的工作線程(hash方式)

  3. 工作線程獲取到對應(yīng)的新鏈接fd后,后續(xù)該鏈接上的所有網(wǎng)絡(luò)IO讀寫都由該線程處理

  4. 假設(shè)有32個(gè)鏈接,則32個(gè)鏈接建立成功后,每個(gè)線程平均處理4個(gè)鏈接上的讀寫、報(bào)文處理、業(yè)務(wù)邏輯處理

2.1 該網(wǎng)絡(luò)線程模型缺陷

  1. 進(jìn)行accept處理的listener線程只有一個(gè),在瞬間高并發(fā)場景容易成為瓶頸

  2. 一個(gè)線程通過IO復(fù)用方式處理多個(gè)鏈接fd的數(shù)據(jù)讀寫、報(bào)文解析及后續(xù)業(yè)務(wù)邏輯處理,這個(gè)過程會有嚴(yán)重的排隊(duì)現(xiàn)象,例如某個(gè)鏈接的報(bào)文接收解析完畢后的內(nèi)部處理時(shí)間過長,則其他鏈接的請求就會阻塞排隊(duì)

2.2 典型案例

memcache緩存,適用于內(nèi)部處理比較快的緩存場景、代理中間場景。memcache源碼實(shí)現(xiàn)中文分析可以詳見: memcache源碼實(shí)現(xiàn)分析

3. 線程模型三. 固定worker線程模型(reuseport)

該模型原型圖如下:

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的

說明:

  1. Linux kernel 3.9開始支持reuseport功能,內(nèi)核協(xié)議棧每獲取到一個(gè)新鏈接自動均衡分發(fā)給用戶態(tài)worker線程。

  2. 該模型解決了模型一的listener單點(diǎn)瓶頸問題,多個(gè)進(jìn)程/線程同時(shí)做為listener,都可以accept客戶端新鏈接。

3.1該網(wǎng)絡(luò)進(jìn)程/線程模型缺陷

reuseport支持后,內(nèi)核通過負(fù)載均衡的方式分發(fā)不同新鏈接到多個(gè)用戶態(tài)worker進(jìn)程/線程,每個(gè)進(jìn)程/線程通過IO復(fù)用方式處理多個(gè)客戶端新鏈接fd的數(shù)據(jù)讀寫、報(bào)文解析、解析后的業(yè)務(wù)邏輯處理。每個(gè)工作進(jìn)程/線程同時(shí)處理多個(gè)鏈接的請求,如果某個(gè)鏈接的報(bào)文接收解析完畢后的內(nèi)部處理時(shí)間過長,則其他鏈接的請求就會阻塞排隊(duì)。

該模型雖然解決了listener單點(diǎn)瓶頸問題,但是工作線程內(nèi)部的排隊(duì)問題沒有解決。

不過,Nginx作為七層轉(zhuǎn)發(fā)代理,由于都是內(nèi)存處理,所以內(nèi)部處理時(shí)間比較短,所以適用于該模型。

3.2典型案例

  1. nginx(nginx用的是進(jìn)程,模型原理一樣),該模型適用于內(nèi)部業(yè)務(wù)邏輯簡單的場景,如nginx代理等

  2. reuseport支持性能提升過程可以參考另一篇分享:Nginx多進(jìn)程高并發(fā)、低時(shí)延、高可靠機(jī)制在緩存(redis、memcache)twemproxy代理中的應(yīng)用

另,參考nginx源碼中文注釋分析

4. 線程模型四:一個(gè)鏈接一個(gè)線程模型

該線程模型圖如下圖:

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的

說明:

  1. listener線程負(fù)責(zé)接受所有的客戶端鏈接

  2. listener線程每接收到一個(gè)新的客戶端鏈接就創(chuàng)建一個(gè)線程,該線程只負(fù)責(zé)處理該鏈接上的數(shù)據(jù)讀寫、報(bào)文解析、業(yè)務(wù)邏輯處理。

4.1 該網(wǎng)絡(luò)線程模型缺陷:

  1. 一個(gè)鏈接創(chuàng)建一個(gè)線程,如果10萬個(gè)鏈接,那么就需要10萬個(gè)線程,線程數(shù)太多,系統(tǒng)負(fù)責(zé)、內(nèi)存消耗也會很多

  2. 當(dāng)鏈接關(guān)閉的時(shí)候,線程也需要銷毀,頻繁的線程創(chuàng)建和消耗進(jìn)一步增加系統(tǒng)負(fù)載

4.2 典型案例:

  1. mysql默認(rèn)方式、mongodb同步線程模型配置,適用于請求處理比較耗時(shí)的場景,如數(shù)據(jù)庫服務(wù)

  2. Apache web服務(wù)器,該模型限制了apache性能,nginx優(yōu)勢會更加明顯

5. 線程模型五:單listener+動態(tài)worker線程(單隊(duì)列)

該線程模型圖如下圖所示:

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的

說明:

  1. listener線程接收到一個(gè)新鏈接fd后,把該fd交由線程池處理,后續(xù)該鏈接的所有讀寫、報(bào)文解析、業(yè)務(wù)處理都由線程池中多個(gè)線程處理。

  2. 該模型把一次請求轉(zhuǎn)換為多個(gè)任務(wù)(網(wǎng)絡(luò)數(shù)據(jù)讀寫、報(bào)文解析、報(bào)文解析后的業(yè)務(wù)邏輯處理)入隊(duì)到全局隊(duì)列,線程池中的線程從隊(duì)列中獲取任務(wù)執(zhí)行。

  3. 同一個(gè)請求訪問被拆分為多個(gè)任務(wù),一次請求可能由多個(gè)線程處理

  4. 當(dāng)任務(wù)太多,系統(tǒng)壓力大的時(shí)候,線程池中線程數(shù)動態(tài)增加

  5. 當(dāng)任務(wù)減少,系統(tǒng)壓力減少的時(shí)候,線程池中線程數(shù)動態(tài)減少

5.1 工作線程運(yùn)行時(shí)間相關(guān)的幾個(gè)統(tǒng)計(jì):

T1:調(diào)用底層asio庫接收一個(gè)完整mongodb報(bào)文的時(shí)間

T2:接收到報(bào)文后的后續(xù)所有處理(含報(bào)文解析、認(rèn)證、引擎層處理、發(fā)送數(shù)據(jù)給客戶端等)

T3: 線程等待數(shù)據(jù)的時(shí)間(例如:長時(shí)間沒有流量,則現(xiàn)在等待讀取數(shù)據(jù))

5.2單個(gè)工作線程如何判斷自己處于”空閑”狀態(tài):

線程運(yùn)行總時(shí)間=T1 + T2 +T3,其中T3是無用等待時(shí)間。如果T3的無用等待時(shí)間占比很大,則說明線程比較空閑。工作線程每一次循環(huán)處理后判斷有效時(shí)間占比,如果小于指定閥值,則自己直接exit退出銷毀

5.3 如何判斷線程池中工作線程“太忙”:

控制線程專門用于判斷線程池中工作線程的壓力情況,以此來決定是否在線程池中創(chuàng)建新的工作線程來提升性能。

控制線程每過一定時(shí)間循環(huán)檢查線程池中的線程壓力狀態(tài),實(shí)現(xiàn)原理就是簡單的實(shí)時(shí)記錄線程池中的線程當(dāng)前運(yùn)行情況,為以下兩類計(jì)數(shù):總線程數(shù)_threadsRunning、當(dāng)前正在運(yùn)行task任務(wù)的線程數(shù)_threadsInUse。如果_threadsRunning=_threadsRunning,說明所有工作線程當(dāng)前都在處理task任務(wù),線程池中線程壓力大,這時(shí)候控制線程就開始增加線程池中線程數(shù)。

該模型詳細(xì)源碼實(shí)現(xiàn)過程更多細(xì)節(jié)我們之前發(fā)布的文章:MongoDB網(wǎng)絡(luò)傳輸處理源碼實(shí)現(xiàn)及性能調(diào)優(yōu)——體驗(yàn)內(nèi)核性能極致設(shè)計(jì)

5.4 該網(wǎng)絡(luò)線程模型缺陷:

  1. 線程池獲取任務(wù)執(zhí)行,有鎖競爭,這里就會成為系統(tǒng)瓶頸

5.5 典型案例:

mongodb動態(tài)adaptive線程模型,適用于請求處理比較耗時(shí)的場景,如數(shù)據(jù)庫服務(wù)。

該模型詳細(xì)源碼優(yōu)化分析實(shí)現(xiàn)過程參考: Mongodb網(wǎng)絡(luò)傳輸處理源碼實(shí)現(xiàn)及性能調(diào)優(yōu)-體驗(yàn)內(nèi)核性能極致設(shè)計(jì)

6. 線程模型六. 單listener+動態(tài)worker線程(多隊(duì)列)-mongodb網(wǎng)絡(luò)線程模型優(yōu)化實(shí)踐

該線程模型圖如下:

常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的

說明:

把一個(gè)全局隊(duì)列拆分為多個(gè)隊(duì)列,任務(wù)入隊(duì)的時(shí)候按照hash散列到各自的隊(duì)列,工作線程獲取獲取任務(wù)的時(shí)候,同理通過hash的方式去對應(yīng)的隊(duì)列獲取任務(wù),通過這種方式減少鎖競爭,同時(shí)提升整體性能。

6.1典型案例:

OPPO自研mongodb內(nèi)核多隊(duì)列adaptive線程模型優(yōu)化,性能有很好的提升,適用于請求處理比較耗時(shí)的場景,如數(shù)據(jù)庫服務(wù)。

該模型詳細(xì)源碼優(yōu)化分析實(shí)現(xiàn)過程同樣參考:Mongodb網(wǎng)絡(luò)傳輸處理源碼實(shí)現(xiàn)及性能調(diào)優(yōu)-體驗(yàn)內(nèi)核性能極致設(shè)計(jì)

6.2 疑問?為啥mysql、mongodb等數(shù)據(jù)庫沒有利用內(nèi)核的reuseport特殊-多線程同時(shí)處理accept請求?

答:實(shí)際上所有服務(wù)都可以利用這一特性,包括數(shù)據(jù)庫服務(wù)(mongodb、mysql等)。但是因?yàn)閿?shù)據(jù)庫服務(wù)訪問時(shí)延一般都是ms級別,如果reuseport特性利用起來,時(shí)延會有幾十us的性能提升,這相比數(shù)據(jù)庫內(nèi)部處理的ms級時(shí)延,這幾十us的性能提升,基本上可以忽略掉,這也是大部分?jǐn)?shù)據(jù)庫服務(wù)沒有支持該功能的原因。

緩存,代理等中間件,由于本身內(nèi)部處理時(shí)間就比較小,也是us級別,所以需要充分利用該特性。

關(guān)于常用高并發(fā)網(wǎng)絡(luò)線程模型設(shè)計(jì)及MongoDB線程模型優(yōu)化實(shí)踐是怎樣的問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI