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