大家好,我是你們的技術(shù)博主。之前和大家分享過(guò)Spring Boot 4.0虛擬線程的基礎(chǔ)啟用配置,不少小伙伴反饋說(shuō)開(kāi)啟虛擬線程后性能提升不明顯,甚至出現(xiàn)了內(nèi)存占用過(guò)高的問(wèn)題。其實(shí)這大多是因?yàn)楹诵膮?shù)沒(méi)有配置合理。今天我們就來(lái)深入拆解虛擬線程的兩個(gè)關(guān)鍵參數(shù)——stack-size和parallelism,結(jié)合實(shí)戰(zhàn)場(chǎng)景告訴大家怎么調(diào)才能拿到最優(yōu)性能。
一、先搞懂:這兩個(gè)參數(shù)到底管什么?
在開(kāi)始調(diào)參之前,我們得先明白這兩個(gè)參數(shù)的底層邏輯,不然就是瞎調(diào)。
1. stack-size:虛擬線程的"工作內(nèi)存"
虛擬線程的棧內(nèi)存和傳統(tǒng)平臺(tái)線程不一樣,它是動(dòng)態(tài)可伸縮的,默認(rèn)初始棧大小只有4KB,最大可以擴(kuò)展到幾MB。stack-size參數(shù)就是用來(lái)設(shè)置這個(gè)初始棧大小的。
太小了:如果業(yè)務(wù)邏輯復(fù)雜,棧空間不夠用,虛擬線程會(huì)頻繁動(dòng)態(tài)擴(kuò)容,反而增加性能開(kāi)銷
太大了:虛擬線程的優(yōu)勢(shì)就是輕量,要是每個(gè)虛擬線程都分配很大的棧,一下子創(chuàng)建幾萬(wàn)個(gè)虛擬線程,內(nèi)存占用會(huì)直線上升,甚至OOM
2. parallelism:載體線程的"數(shù)量上限"
虛擬線程是M:N映射模型,M個(gè)虛擬線程要映射到N個(gè)平臺(tái)線程(也就是載體線程)上執(zhí)行。parallelism參數(shù)就是設(shè)置這個(gè)N的最大值,也就是同時(shí)能有多少個(gè)虛擬線程在CPU上執(zhí)行。
太小了:CPU資源沒(méi)充分利用,虛擬線程排隊(duì)等待,并發(fā)上不去
太大了:載體線程太多,CPU上下文切換頻繁,反而降低整體性能
二、實(shí)戰(zhàn)調(diào)優(yōu):不同場(chǎng)景下的最優(yōu)取值
1. 高并發(fā)IO密集場(chǎng)景(比如電商訂單系統(tǒng)、API網(wǎng)關(guān))
場(chǎng)景特點(diǎn):大量數(shù)據(jù)庫(kù)查詢、遠(yuǎn)程接口調(diào)用,虛擬線程會(huì)頻繁掛起和恢復(fù),并發(fā)量通常在萬(wàn)級(jí)以上。
最優(yōu)配置:
spring:
threads:
virtual:
enabled: true
name-prefix: "biz-virtual-thread-" # 自定義線程名,方便日志排查
stack-size: 256k # 初始棧256KB
parallelism: 64 # 載體線程數(shù)=CPU核心數(shù)
取值邏輯:
stack-size=256k:IO密集場(chǎng)景下,虛擬線程大部分時(shí)間在等待IO,實(shí)際執(zhí)行的業(yè)務(wù)邏輯不會(huì)太復(fù)雜,256KB的初始棧完全夠用,既避免了頻繁擴(kuò)容,又保證了輕量性
parallelism=CPU核心數(shù):IO密集場(chǎng)景下,CPU大部分時(shí)間是空閑的,載體線程數(shù)等于CPU核心數(shù)剛好能把CPU資源利用起來(lái),不會(huì)因?yàn)榫€程太多導(dǎo)致上下文切換開(kāi)銷
壓測(cè)對(duì)比:
傳統(tǒng)線程池:1000并發(fā)下,QPS=1200,平均響應(yīng)時(shí)間=800ms,內(nèi)存占用=2.1GB
虛擬線程默認(rèn)配置:1000并發(fā)下,QPS=2800,平均響應(yīng)時(shí)間=350ms,內(nèi)存占用=1.2GB
虛擬線程最優(yōu)配置:1000并發(fā)下,QPS=3500,平均響應(yīng)時(shí)間=280ms,內(nèi)存占用=1.0GB
2. 計(jì)算密集場(chǎng)景(比如大數(shù)據(jù)處理、報(bào)表生成)
場(chǎng)景特點(diǎn):CPU使用率很高,虛擬線程掛起的機(jī)會(huì)很少,主要是純計(jì)算邏輯。
最優(yōu)配置:
spring:
threads:
virtual:
enabled: true
name-prefix: "compute-virtual-thread-"
stack-size: 512k # 初始棧512KB
parallelism: 128 # 載體線程數(shù)=CPU核心數(shù)*2
取值邏輯:
stack-size=512k:計(jì)算密集場(chǎng)景下,業(yè)務(wù)邏輯通常比較復(fù)雜,方法調(diào)用層級(jí)深,需要更大的初始??臻g,避免頻繁擴(kuò)容影響性能
parallelism=CPU核心數(shù)*2:計(jì)算密集場(chǎng)景下,CPU一直處于忙碌狀態(tài),適當(dāng)增加載體線程數(shù)可以利用CPU的超線程特性,提升計(jì)算效率
壓測(cè)對(duì)比:
傳統(tǒng)線程池:處理100萬(wàn)條數(shù)據(jù)耗時(shí)=120s,CPU使用率=90%,內(nèi)存占用=3.2GB
虛擬線程默認(rèn)配置:處理100萬(wàn)條數(shù)據(jù)耗時(shí)=95s,CPU使用率=95%,內(nèi)存占用=2.8GB
虛擬線程最優(yōu)配置:處理100萬(wàn)條數(shù)據(jù)耗時(shí)=75s,CPU使用率=98%,內(nèi)存占用=2.5GB
3. 混合場(chǎng)景(比如既有數(shù)據(jù)庫(kù)查詢又有復(fù)雜計(jì)算)
場(chǎng)景特點(diǎn):既有IO等待又有CPU計(jì)算,大部分Web應(yīng)用都屬于這種場(chǎng)景。
最優(yōu)配置:
spring:
threads:
virtual:
enabled: true
name-prefix: "mixed-virtual-thread-"
stack-size: 384k # 初始棧384KB
parallelism: 96 # 載體線程數(shù)=CPU核心數(shù)*1.5
取值邏輯:
stack-size=384k:折中IO密集和計(jì)算密集場(chǎng)景的需求,平衡內(nèi)存占用和擴(kuò)容開(kāi)銷
parallelism=CPU核心數(shù)*1.5:既保證CPU資源充分利用,又不會(huì)因?yàn)榫€程太多導(dǎo)致上下文切換開(kāi)銷過(guò)大
三、避坑指南:調(diào)參時(shí)容易踩的3個(gè)坑
1. 盲目增大stack-size
很多同學(xué)覺(jué)得棧越大越好,直接把stack-size設(shè)置成1MB甚至更大,這完全違背了虛擬線程輕量的設(shè)計(jì)初衷。虛擬線程的優(yōu)勢(shì)就是能創(chuàng)建大量線程,如果每個(gè)線程都分配1MB棧,創(chuàng)建10萬(wàn)個(gè)虛擬線程就要占用100GB內(nèi)存,直接就OOM了。
2. parallelism設(shè)置過(guò)大
有些同學(xué)覺(jué)得parallelism越大,并發(fā)能力越強(qiáng),直接設(shè)置成200甚至500。其實(shí)parallelism的最大值不應(yīng)該超過(guò)CPU核心數(shù)的2倍,超過(guò)之后CPU上下文切換開(kāi)銷會(huì)急劇增加,整體性能反而下降。
3. 忽略服務(wù)器硬件配置
調(diào)參一定要結(jié)合服務(wù)器的硬件配置,比如32核服務(wù)器和64核服務(wù)器的最優(yōu)parallelism肯定不一樣。給大家一個(gè)通用公式:
IO密集場(chǎng)景:parallelism = CPU核心數(shù)
計(jì)算密集場(chǎng)景:parallelism = CPU核心數(shù) * 2
混合場(chǎng)景:parallelism = CPU核心數(shù) * 1.5
四、如何驗(yàn)證配置是否合理?
1. 查看虛擬線程狀態(tài)
可以通過(guò)JDK自帶的jcmd命令查看虛擬線程的狀態(tài):
jcmd <pid> Thread.print -v
在輸出中找到虛擬線程的信息,查看棧大小和載體線程數(shù)是否符合預(yù)期。
2. 監(jiān)控內(nèi)存和CPU使用率
使用Prometheus+Grafana或者JDK自帶的jconsole、jvisualvm工具監(jiān)控應(yīng)用的內(nèi)存和CPU使用率:
如果內(nèi)存占用過(guò)高,說(shuō)明stack-size可能設(shè)置太大了
如果CPU使用率很低,說(shuō)明parallelism可能設(shè)置太小了
如果CPU使用率很高但響應(yīng)時(shí)間很長(zhǎng),說(shuō)明parallelism可能設(shè)置太大了
3. 壓測(cè)驗(yàn)證
通過(guò)JMeter、Gatling等壓測(cè)工具進(jìn)行壓測(cè),對(duì)比不同配置下的QPS、響應(yīng)時(shí)間、內(nèi)存占用等指標(biāo),找到最優(yōu)配置。
五、總結(jié)
虛擬線程確實(shí)能大幅提升Spring Boot應(yīng)用的并發(fā)能力,但要想拿到最優(yōu)性能,核心參數(shù)的配置非常關(guān)鍵。stack-size和parallelism這兩個(gè)參數(shù)沒(méi)有絕對(duì)的最優(yōu)值,需要結(jié)合具體的業(yè)務(wù)場(chǎng)景和服務(wù)器硬件配置來(lái)調(diào)整。
最后給大家一個(gè)調(diào)參步驟:
根據(jù)業(yè)務(wù)場(chǎng)景確定初始配置
進(jìn)行壓測(cè),監(jiān)控性能指標(biāo)
逐步調(diào)整參數(shù),找到最優(yōu)值
上線后持續(xù)監(jiān)控,根據(jù)實(shí)際運(yùn)行情況再微調(diào)
下次我們?cè)倭牧奶摂M線程的調(diào)度器配置和監(jiān)控排查技巧,敬請(qǐng)期待!?