80后聊架構(gòu):增加線程到底能不能提升吞吐量? | 架構(gòu)師之路
《架構(gòu)師之路:架構(gòu)設(shè)計(jì)中的100個(gè)知識(shí)點(diǎn)》四:性能與擴(kuò)展性,線程數(shù)與吞吐量
之前聊了,啥時(shí)候應(yīng)該優(yōu)化延時(shí)(Latency),啥時(shí)候應(yīng)該優(yōu)化吞吐量(Throughput)。
畫外音:短視頻二維碼附在文末。
有一些評(píng)論,值得和大家擴(kuò)展討論下:
- 有人說,延時(shí)與吞吐量是一回事,說延時(shí)下去了,吞吐量自然就上來了;
- 有人說,增加線程數(shù),吞吐量就上來了;
- 有人說,增加線程數(shù),吞吐量未必上來;
1. 延時(shí)和吞吐量,是評(píng)估啥的指標(biāo)?
再次強(qiáng)調(diào)一下,在性能優(yōu)化中:
- 一個(gè)用戶慢,就去優(yōu)化延時(shí)。
- 多個(gè)用戶扛不住,就去優(yōu)化吞吐量。
- 延時(shí),是偏性能(performance)的指標(biāo)。
- 吞吐量,是偏擴(kuò)展性(scalability)的指標(biāo)。
performance和scalability,評(píng)估維度并不一樣。
前端架構(gòu),為什么聊performance更多?
前端FE,Android,IOS的童鞋,經(jīng)常說提升performance,很少說提升scalability。
壓縮資源,緩存圖片,異步加載,Webpack代碼拆分,PWA等等這些技術(shù),都是提升performance的,服務(wù)好一個(gè)用戶,讓一個(gè)用戶速度快。
后端架構(gòu),要不要提升performance,當(dāng)然要。數(shù)據(jù)庫訪問200ms,引入緩存20ms,速度更快了。
后端架構(gòu),為什么聊scalability會(huì)更多?
因?yàn)橄到y(tǒng)難點(diǎn)不在于1個(gè)用戶的延時(shí)是200ms還是20ms,難的是:
- 數(shù)據(jù)庫里有1億個(gè)用戶時(shí),系統(tǒng)扛不扛得住;
- 10萬個(gè)用戶同時(shí)訪問時(shí),系統(tǒng)扛不扛得??;
而這些,就是scalability的范疇。
這,是后端架構(gòu)設(shè)計(jì)的核心,是scalability相關(guān)的知識(shí)點(diǎn),也是我在“100個(gè)架構(gòu)知識(shí)點(diǎn)”里要重點(diǎn)講的內(nèi)容。
2. 線程數(shù)和吞吐量,到底是什么關(guān)系?
我在短視頻里舉例:“增加線程數(shù)是提高吞吐量的方法之一,1個(gè)線程1秒鐘處理5個(gè)請(qǐng)求,吞吐量是5,增加到10個(gè)線程,吞吐量變成50”。
有朋友指出我說的不對(duì),說增加線程數(shù),有時(shí)候能提升吞吐量,有時(shí)候不能提升吞吐量。
這位朋友說的對(duì),我表達(dá)不嚴(yán)密,那么問題來:
- 啥時(shí)候增加線程數(shù)能提升吞吐量,啥時(shí)候不能?
- 設(shè)置線程數(shù)的依據(jù)是什么,是不是越大越好?
- 線程數(shù)設(shè)為多少,吞吐量能最大?
下面稍微展開詳細(xì)說下。
首先,工作線程數(shù)是不是設(shè)置得越大越好?
答案顯然是否定的。
- 服務(wù)器CPU核數(shù)有限,能夠同時(shí)并發(fā)的線程數(shù)有限,單核CPU設(shè)置1000個(gè)工作線程沒有意義;
- 線程切換有開銷,如果線程切換過于頻繁,反而會(huì)使性能降低;
第二個(gè)問題,調(diào)用sleep()函數(shù)的時(shí)候,線程是否一直占用CPU?
不占用,休眠時(shí)會(huì)把CPU讓出來,給其他需要CPU資源的線程使用。
不止sleep,一些阻塞調(diào)用,例如網(wǎng)絡(luò)編程中的:
- 阻塞accept(),等待客戶端連接;
- 阻塞recv(),等待下游回包;
都會(huì)讓出CPU資源。
第三個(gè)問題,單核CPU,設(shè)置多線程有沒有意義?單核CPU,設(shè)置多線程能否提高并發(fā)性能?
即使是單核,使用多線程也是有意義的,大多數(shù)情況也能提高并發(fā)。
- 其一,多線程編碼可以讓代碼更加清晰,例如:IO線程收發(fā)包,Worker線程進(jìn)行任務(wù)處理,Timeout線程進(jìn)行超時(shí)檢測(cè);
- 其二,如果有一個(gè)任務(wù)一直占用CPU資源在進(jìn)行計(jì)算,此時(shí)增加線程并不能增加并發(fā),例如以下代碼會(huì)一直占用CPU,并使得CPU占用率達(dá)到100%:
while(1){ i++; }
- 其三,通常來說,Worker線程一般不會(huì)一直占用CPU進(jìn)行計(jì)算,此時(shí)即使CPU是單核,增加Worker線程也能夠提高并發(fā),因?yàn)檫@個(gè)線程在休息的時(shí)候,其他的線程可以繼續(xù)工作;
第四個(gè)問題,常見服務(wù)線程模型是怎樣的?
了解常見的服務(wù)線程模型,有助于理解服務(wù)并發(fā)的原理,一般來說互聯(lián)網(wǎng)常見的服務(wù)線程模型是:IO線程與工作線程通過任務(wù)隊(duì)列解耦模型。
畫外音:還有一種是無鎖純異步,可參考lighttpd的單線程模式,這種模型完全無鎖,但無法利用多核優(yōu)勢(shì)。
這類線程模型,示例如下:
如上圖,很多Web-Server與服務(wù)框架都是使用這樣的一種“IO線程與Worker線程通過隊(duì)列解耦”類線程模型:
- 有少數(shù)幾個(gè)IO線程監(jiān)聽上游發(fā)過來的請(qǐng)求,并進(jìn)行收發(fā)包(生產(chǎn)者);
- 有一個(gè)或者多個(gè)任務(wù)隊(duì)列,作為IO線程與Worker線程異步解耦的數(shù)據(jù)傳輸通道(臨界資源);
- 有多個(gè)工作線程執(zhí)行真正的任務(wù)(消費(fèi)者);
這個(gè)線程模型應(yīng)用很廣,其特點(diǎn)是,工作線程內(nèi)部是同步阻塞執(zhí)行任務(wù)的,因此可以通過增加Worker線程數(shù)來增加并發(fā)能力。
畫外音:純異步模型未來再聊。
“IO線程與工作線程通過隊(duì)列解耦”類線程模型,工作線程的工作模式是怎么樣的?
了解工作線程的工作模式,對(duì)量化分析線程數(shù)的設(shè)置非常有幫助:
上圖是一個(gè)典型的工作線程的處理過程,從開始處理start到結(jié)束處理end,該任務(wù)的處理共有7個(gè)步驟:
- 從工作隊(duì)列里拿出任務(wù),進(jìn)行一些本地初始化計(jì)算,例如http協(xié)議分析、參數(shù)解析、參數(shù)校驗(yàn)等;
- 訪問cache拿一些數(shù)據(jù);
- 拿到cache里的數(shù)據(jù)后,再進(jìn)行一些本地計(jì)算,這些計(jì)算和業(yè)務(wù)邏輯相關(guān);
- 通過RPC調(diào)用下游service再拿一些數(shù)據(jù),或者讓下游service去處理一些相關(guān)的任務(wù);
- RPC調(diào)用結(jié)束后,再進(jìn)行一些本地計(jì)算,怎么計(jì)算和業(yè)務(wù)邏輯相關(guān);
- 訪問DB進(jìn)行一些數(shù)據(jù)操作;
- 操作完數(shù)據(jù)庫之后做一些收尾工作,同樣這些收尾工作也是本地計(jì)算,和業(yè)務(wù)邏輯相關(guān);
分析整個(gè)處理的時(shí)間軸,會(huì)發(fā)現(xiàn):
- 其中1,3,5,7步驟中(上圖中粉色時(shí)間軸),線程進(jìn)行本地業(yè)務(wù)邏輯計(jì)算時(shí)需要占用CPU;
- 而2,4,6步驟中(上圖中橙色時(shí)間軸),訪問cache、service、DB過程中線程處于一個(gè)等待結(jié)果的狀態(tài),不需要占用CPU;
如何量化分析,并合理設(shè)置工作線程數(shù)呢?
通過上面的分析,Worker線程在執(zhí)行的過程中:
- 有一部計(jì)算時(shí)間需要占用CPU;
- 另一部分等待時(shí)間不需要占用CPU;
通過量化分析,例如打日志進(jìn)行統(tǒng)計(jì),可以統(tǒng)計(jì)出整個(gè)Worker線程執(zhí)行過程中這兩部分時(shí)間的比例,例如:
- 執(zhí)行計(jì)算,占用CPU的時(shí)間(粉色時(shí)間軸)是100ms;
- 等待時(shí)間,不占用CPU的時(shí)間(橙色時(shí)間軸)也是100ms;
得到的結(jié)果是,這個(gè)線程計(jì)算和等待的時(shí)間是1:1,即有50%的時(shí)間在計(jì)算(占用CPU),50%的時(shí)間在等待(不占用CPU):
- 假設(shè)此時(shí)是單核,則設(shè)置為2個(gè)工作線程就可以把CPU充分利用起來,讓CPU跑到100%;
- 假設(shè)此時(shí)是N核,則設(shè)置為2N個(gè)工作線程就可以把CPU充分利用起來,讓CPU跑到N*100%;
當(dāng)當(dāng)當(dāng)當(dāng)?。?!
結(jié)論來了:
N核服務(wù)器,通過執(zhí)行業(yè)務(wù)的單線程分析出本地計(jì)算時(shí)間為x,等待時(shí)間為y,則工作線程數(shù)(線程池線程數(shù))設(shè)置為 N*(x+y)/x,能讓CPU的利用率最大化。
一般來說,非CPU密集型的業(yè)務(wù)(加解密、壓縮解壓縮、搜索排序等業(yè)務(wù)是CPU密集型的業(yè)務(wù)),瓶頸都在后端數(shù)據(jù)庫訪問或者RPC調(diào)用,本地CPU計(jì)算的時(shí)間很少,所以設(shè)置幾十或者幾百個(gè)工作線程是能夠提升吞吐量的。
你,學(xué)廢了嗎?