1 背景
性能優(yōu)化是我們?nèi)粘9ぷ髦泻苤匾囊徊糠?,主要有以下原因?/p>
- 降低服務(wù)器和帶寬等硬件成本:用更少的資源處理更多的請(qǐng)求
- 提高現(xiàn)實(shí)世界的運(yùn)行效率:人機(jī)處理效率存在數(shù)量級(jí)的偏差,同樣機(jī)器世界的效率提升能帶來現(xiàn)實(shí)世界效率提升的方法效果
- 提高用戶的體驗(yàn):解決響應(yīng)緩慢、宕機(jī)等問題
而并行優(yōu)化在改善程序接口響應(yīng)時(shí)間和吞吐量指標(biāo)方面是個(gè)利器,所以本次結(jié)合前段時(shí)間做的一段長鏈路執(zhí)行邏輯代碼的優(yōu)化,給大家講講程序并行優(yōu)化的步驟及方法論。
2 多線程優(yōu)化六步法
2.1 定位優(yōu)化點(diǎn)
一般是通過全鏈路監(jiān)控、火焰圖、自定義打點(diǎn)、生產(chǎn)報(bào)警等先找到耗時(shí)長的性能問題點(diǎn),之后通過多線程并行化的方式達(dá)到優(yōu)化程序響應(yīng)時(shí)長和吞吐量的目的。
2.2 執(zhí)行鏈路分析
對(duì)問題點(diǎn)的執(zhí)行鏈路進(jìn)行分析,主要分幾方面:
- 鏈路里涉及的操作節(jié)點(diǎn);
- 節(jié)點(diǎn)自身的耗時(shí);是io密集型還是cpu密集型;是否依賴和修改外部變量;此節(jié)點(diǎn)是否是核心路徑;
- 節(jié)點(diǎn)間彼此依賴關(guān)系;
2.3 異步鏈路設(shè)計(jì)
- 將鏈路根據(jù)依賴關(guān)系進(jìn)行重排,把被依賴的放在前面;
- 彼此不依賴有相同起點(diǎn)的節(jié)點(diǎn)并行化;設(shè)計(jì)并行任務(wù)結(jié)果獲取及后續(xù)依賴節(jié)點(diǎn)的通知機(jī)制
- 如果有指定響應(yīng)時(shí)間目標(biāo)的鏈路,為核心路徑節(jié)點(diǎn)設(shè)計(jì)降級(jí)方案;根據(jù)響應(yīng)時(shí)間要求及已耗時(shí)數(shù)據(jù)對(duì)非核心路徑節(jié)點(diǎn)調(diào)用進(jìn)行舍棄;
- 將對(duì)變量修改的邏輯收攏,且盡量在主線程中處理,避免需要做的多線程變量可見性和時(shí)序性同步
2.4 并發(fā)框架選擇
1.線程池
描述:具體業(yè)務(wù)任務(wù)繼承接口 Runnable、Callable ,在調(diào)用 ExecutorService.submit 接口時(shí),會(huì)提交任務(wù)到 ExecutorService 內(nèi)部的一個(gè)任務(wù)隊(duì)列中。同時(shí),在 ExecutorService 內(nèi)部還存在一個(gè)預(yù)先申請(qǐng)的線程池(Thread Pool),線程池中的線程會(huì)從任務(wù)隊(duì)列中領(lǐng)取一個(gè)任務(wù)來執(zhí)行。
優(yōu)點(diǎn):復(fù)用線程,減少線程創(chuàng)建銷毀成本及減少請(qǐng)求時(shí)延
注意點(diǎn):cpu密集型和io密集型任務(wù)應(yīng)進(jìn)行不同的線程池配置;為避免不同任務(wù)相互干擾重要業(yè)務(wù)最好獨(dú)立使用線程池;不同線程之間要注意操作的有序和數(shù)據(jù)的可見性
2.AKKA
描述:每個(gè) Actor 代表的是可以被調(diào)度執(zhí)行的輕量單元。如圖中所示,Actor A 和 Actor C 在向 Actor B 發(fā)送消息時(shí),所有消息會(huì)被底層框架發(fā)送到 Actor B 的 Mailbox 中,然后底層的 Akka 框架調(diào)度代碼會(huì)觸發(fā) Actor B,來接收并執(zhí)行消息的后續(xù)處理。這樣,基于 Actor 模型的這套并發(fā)框架,首先就保證了消息可以被安全地在各個(gè) Actor 之間傳遞,同時(shí)也保證了每個(gè) Actor 實(shí)例可以串行處理接收到的所有消息。
優(yōu)點(diǎn):不需要關(guān)注多線程之間并發(fā)同步和數(shù)據(jù)一致性;輕量級(jí)高并發(fā)
注意點(diǎn):actor任務(wù)粒度要小,避免承接太多業(yè)務(wù)邏輯;計(jì)算密集型任務(wù)更能發(fā)揮出AKKA的優(yōu)勢(shì)
3.REACTOR
描述:輸入流 Flux 就是 Reactor 中典型的異步消息流,它代表了一個(gè)包含 0 個(gè)到 N 個(gè)的消息序列。另外,圖中的 Rule 代表的是一個(gè)基于消息的處理邏輯或規(guī)則,輸入流中的消息可以被中間多個(gè)處理邏輯組合連續(xù)加工之后,再生成一個(gè)包含 0 個(gè)到 N 個(gè)的輸出消息流 Flux。
優(yōu)點(diǎn):rule采用pull處理消息,避免消息積壓;異步非阻塞io,避免阻塞當(dāng)前線程
注意點(diǎn):函數(shù)式編程,會(huì)有一定的語法學(xué)習(xí)成本和理解成本;針對(duì)消息流處理的、基于 IO 密集型的異步交互場(chǎng)景比較有優(yōu)勢(shì)
2.5 并發(fā)工具選擇
多線程執(zhí)行涉及到一系列細(xì)節(jié)問題,如共享變量可見性,執(zhí)行順序,結(jié)果的獲取、后續(xù)操作的通知等,所以要結(jié)合需求使用一系列相關(guān)的并發(fā)工具類做多線程執(zhí)行正確性的保障
2.6 效果驗(yàn)證
1.壓測(cè)
一般通過jmeter、loadrunner等后端性能測(cè)試軟件,不斷對(duì)系統(tǒng)施加壓力,并驗(yàn)證系統(tǒng)化處于或長期處于臨界飽和階段的穩(wěn)定性以及性能指標(biāo),并試圖找到系統(tǒng)處于臨界狀態(tài)時(shí)的主要瓶頸點(diǎn)。
注意點(diǎn):
- 完全相同的環(huán)境以及測(cè)試負(fù)載
- 注意混部情況其他服務(wù)可能對(duì)驗(yàn)證服務(wù)造成的影響
- 通過加壓減壓調(diào)整請(qǐng)求量觀察服務(wù)器處理能力的變化及穩(wěn)定性
2.性能指標(biāo)驗(yàn)證
- 驗(yàn)證并發(fā)用戶數(shù)、響應(yīng)時(shí)間及吞吐量這種調(diào)優(yōu)目標(biāo)量;
- 觀察服務(wù)器的負(fù)載指標(biāo),防止因優(yōu)化帶來服務(wù)器超出負(fù)載能力;
- 觀察上下游服務(wù)的業(yè)務(wù)指標(biāo)和服務(wù)器負(fù)載,防止因優(yōu)化帶來上下游超出負(fù)載能力
3.業(yè)務(wù)結(jié)果驗(yàn)證
一般通過diff工具通過采集相同請(qǐng)求的響應(yīng)對(duì)比判別是否影響業(yè)務(wù);也可通過qa輔助構(gòu)建針對(duì)改動(dòng)的測(cè)試集去做驗(yàn)證
3 舉例
以我們前段時(shí)間進(jìn)行的商品主數(shù)據(jù)下發(fā)消費(fèi)能力調(diào)優(yōu)進(jìn)行舉例說明整個(gè)優(yōu)化過程:
3.1 優(yōu)化點(diǎn)定位
主數(shù)據(jù)程序接收商品批量下發(fā)處理緩慢,觸發(fā)下發(fā)積壓報(bào)警
3.2 執(zhí)行鏈路分析
梳理各步驟對(duì)入?yún)⒑捅4鏁r(shí)需要的變量的處理,分析各步驟相互依賴關(guān)系,是否可并行,進(jìn)行執(zhí)行過程優(yōu)化調(diào)整。
商品主數(shù)據(jù)處理步驟分析:
3.3 異步鏈路設(shè)計(jì)
- 1、 3、4、5異步并行處理,且因?qū)ζ渌兞啃薷倪壿嫙o依賴,放在最前面提交。
- 2、7、8、9、10、11根據(jù)依賴關(guān)系,把相關(guān)性的邏輯收攏,把被依賴的邏輯提前。
- 13也異步提交。最后通過completionService.take().get()遍歷獲取各任務(wù)執(zhí)行結(jié)果進(jìn)行合并返回最終結(jié)果
3.4 并發(fā)框架選擇
出于團(tuán)隊(duì)知識(shí)棧及框架應(yīng)用場(chǎng)景綜合考慮,這里選擇了線程池作為并發(fā)框架,并結(jié)合多io場(chǎng)景做了線程池參數(shù)配置。
/**
* io任務(wù)線程池
*/
public static ThreadPoolExecutor threadPoolExecutorForIO= new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),Runtime.getRuntime().availableProcessors()*2,1, TimeUnit.MINUTES,new ArrayBlockingQueue(2014),new ThreadPoolExecutor.CallerRunsPolicy());
3.5 并發(fā)工具選擇
這里使用CompletionService來獲取多線程的執(zhí)行結(jié)果,并進(jìn)行結(jié)果歸集。
CompletionService通過在線程結(jié)果完成時(shí)提交到阻塞隊(duì)列,避免通過遍歷future結(jié)果的方式導(dǎo)致先提交的任務(wù)耗時(shí)長造成的阻塞等待。
CountingExecutorCompletionService<Boolean> completionService= new CountingExecutorCompletionService(ExecutorCollector.threadPoolExecutorForIO);
//任務(wù)提交
completionService.submit(callableA);
//結(jié)果歸集
boolean result=true;
for(int i = 0; i<completionService.getSubmittedTaskCount(); i++)
{
result&=completionService.take().get();
}
3.6 效果驗(yàn)證
1.壓測(cè)
采用jmeter對(duì)兩臺(tái)相同配置的服務(wù)器(分別部署優(yōu)化版本和原始版本)加壓,觀察服務(wù)負(fù)載情況
2.性能指標(biāo)驗(yàn)證
1)耗時(shí)和吞吐量異步版本要優(yōu)于同步版本
異步版本耗時(shí)在80-100ms,同步版本耗時(shí)在120-160ms
異步版本吞吐量在17000/5分鐘,同步版本吞吐量在15000/5分鐘
2)cpu使用率異步版本略高一點(diǎn),線程數(shù)異步版本比較高
線程數(shù)高的原因:用到了線程池,預(yù)置的核心線程數(shù)為邏輯核數(shù)64,因?yàn)樯婕暗絠o操作較多,最大線程數(shù)配成了128。
3.業(yè)務(wù)結(jié)果驗(yàn)證
因?yàn)楣究蚣懿恢С謍ttp的diff,此處采用了自己抽檢請(qǐng)求結(jié)果及qa協(xié)助走查和code review的方式保證業(yè)務(wù)結(jié)果的準(zhǔn)確性
4 總結(jié)
程序性能優(yōu)化方法關(guān)系到方方面面,而多線程異步優(yōu)化無疑是其中很重要的一種途徑。它不光關(guān)系到并發(fā)框架的選擇、多種線程工具類的使用,還關(guān)系到對(duì)整個(gè)處理鏈路的業(yè)務(wù)理解和編排分析。希望通過這一課可以幫大家理清相關(guān)的思路,作為日常優(yōu)化工作的一個(gè)參考。
作者:京東物流 馮鴻儒文章來源:http://www.zghlxwxcb.cn/news/detail-454379.html
內(nèi)容來源:京東云開發(fā)者社區(qū)文章來源地址http://www.zghlxwxcb.cn/news/detail-454379.html
到了這里,關(guān)于rt下降40%?程序并行優(yōu)化六步法的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!