日韩成人免费在线_国产成人一二_精品国产免费人成电影在线观..._日本一区二区三区久久久久久久久不

當(dāng)前位置:首頁 > 科技  > 軟件

【故障現(xiàn)場】多線程性能優(yōu)化最大的坑,99%人都不自知

來源: 責(zé)編: 時間:2024-01-22 17:25:45 244觀看
導(dǎo)讀1. 問題&分析當(dāng)我們在處理慢接口問題時,經(jīng)常會使用多線程技術(shù),將能夠并行處理的任務(wù)拆分到不同的線程中處理,等任務(wù)處理完成后,再收集各線程的處理結(jié)果,進行后續(xù)的處理。整體思路如下圖所示:圖片這樣可以將并行部分的總耗時

1. 問題&分析

當(dāng)我們在處理慢接口問題時,經(jīng)常會使用多線程技術(shù),將能夠并行處理的任務(wù)拆分到不同的線程中處理,等任務(wù)處理完成后,再收集各線程的處理結(jié)果,進行后續(xù)的處理。整體思路如下圖所示:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

這樣可以將并行部分的總耗時從 sum 降為 max,從而大幅降低接口的響應(yīng)時間。9KY28資訊網(wǎng)——每日最新資訊28at.com

1.1. 案例

訂單詳情頁耗時嚴(yán)重,p99 將近3秒,已經(jīng)驗證影響用戶體驗,本次迭代小艾專門對該接口進行優(yōu)化。迭代剛上線,該接口的響應(yīng)時間大幅降低,p99 降低到 800 毫秒以內(nèi),大家紛紛向小艾發(fā)來祝賀。但好景不長,隨著流量的增加,接口響應(yīng)時間也在逐漸變長,p99 超過 5 秒,最后系統(tǒng)拋出大量的 RejectedExecutionException 異常,這個接口不可用。最終,QA伙伴火速進行回滾操作,系統(tǒng)恢復(fù)正常。9KY28資訊網(wǎng)——每日最新資訊28at.com

系統(tǒng)恢復(fù)后,小艾仔細查看系統(tǒng)監(jiān)控,CPU使用率并不高,內(nèi)存也處于正常水位,接口性能居然比優(yōu)化前還差,真心不知道哪里出了問題。9KY28資訊網(wǎng)——每日最新資訊28at.com

優(yōu)化前代碼:9KY28資訊網(wǎng)——每日最新資訊28at.com

public RestResult<OrderDetailVO> getOrderDetail(@PathVariable Long orderId){        Stopwatch stopwatch = Stopwatch.createStarted();        OrderService.Order order = this.orderService.getById(orderId);        if (order == null){            return RestResult.success(null);        }        OrderDetailVO orderDetail = new OrderDetailVO();        orderDetail.setUser(userService.getById(order.getUserId()));        orderDetail.setAddress(addressService.getById(order.getUserAddressId()));        orderDetail.setCoupon(couponService.getById(order.getCouponId()));        orderDetail.setProduct(productService.getById(order.getProductId()));        log.info("串行 Cost {} ms", stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));        return RestResult.success(orderDetail);}

優(yōu)化前耗時:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

優(yōu)化后代碼:9KY28資訊網(wǎng)——每日最新資訊28at.com

public RestResult<OrderDetailVO> getOrderDetailNew(@PathVariable Long orderId){        Stopwatch stopwatch = Stopwatch.createStarted();        OrderService.Order order = this.orderService.getById(orderId);        if (order == null){            return RestResult.success(null);        }        Future<UserService.User> userFuture = this.executorService.submit(() -> userService.getById(order.getUserId()));        Future<AddressService.Address> addressFuture = this.executorService.submit(() -> addressService.getById(order.getUserAddressId()));        Future<CouponService.Coupon> couponFuture = this.executorService.submit(() -> couponService.getById(order.getCouponId()));        Future<ProductService.Product> productFuture = this.executorService.submit(() -> productService.getById(order.getProductId()));        OrderDetailVO orderDetail = new OrderDetailVO();        orderDetail.setUser(getFutureValue(userFuture));        orderDetail.setProduct(getFutureValue(productFuture));        orderDetail.setAddress(getFutureValue(addressFuture));        orderDetail.setCoupon(getFutureValue(couponFuture));        log.info("并行 Cost {} ms", stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));        return RestResult.success(orderDetail);    }

優(yōu)化后耗時:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

可見采用并行優(yōu)化后,接口的響應(yīng)時間從 4 秒 將至 1 秒,效果還是非常明顯的。9KY28資訊網(wǎng)——每日最新資訊28at.com

但,繼續(xù)加大請求量,系統(tǒng)便出現(xiàn)問題,如下圖所示:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

在流量逐漸增加的過程中,從日志中可以得到以下信息:9KY28資訊網(wǎng)——每日最新資訊28at.com

初期耗時穩(wěn)定,基本在 1 秒左右9KY28資訊網(wǎng)——每日最新資訊28at.com

接口耗時逐漸增大,甚至遠超串行處理的耗時(大于 4 秒)9KY28資訊網(wǎng)——每日最新資訊28at.com

有些請求直接拋出 RejectedExecutionException 異常9KY28資訊網(wǎng)——每日最新資訊28at.com

1.2. 問題分析

從代碼中并未發(fā)現(xiàn)任何問題,設(shè)計思路也非常清晰,其核心問題在線程池使用上,項目線程池配置如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

int coreSize = Runtime.getRuntime().availableProcessors();executorService = new ThreadPoolExecutor(coreSize, coreSize * 5,        5L, TimeUnit.MINUTES,        new LinkedBlockingQueue<Runnable>(1024)        );

核心配置為:9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 核心線程數(shù)為 cpu 核數(shù)
  2. 最大線程數(shù)為 cpu 核數(shù)的 5 倍
  3. 空閑線程存活時間為 5 分鐘
  4. 任務(wù)隊列為 LinkedBlockingQueue 大小為 1024

在這個配置下,我們推演下以上的三個現(xiàn)象。9KY28資訊網(wǎng)——每日最新資訊28at.com

1.2.1. 線程資源充足

如下圖所示:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

整體流程如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 主線程向線程池提交 Task
  2. 由于線程處于空閑狀態(tài),立即接受并處理問題
  3. 線程池線程處理完任務(wù),將最終的處理結(jié)果寫回到 Future
  4. 主線程等待所有任務(wù)執(zhí)行完成,獲取所有執(zhí)行結(jié)果,然后執(zhí)行后續(xù)流程

這正是想要的執(zhí)行結(jié)果,任務(wù)被并行執(zhí)行,大幅降低接口耗時。9KY28資訊網(wǎng)——每日最新資訊28at.com

1.2.2. 任務(wù)進入等待隊列

隨著流量的增加,所有的核心線程都處于忙碌狀態(tài),此時新任務(wù)將進入等待隊列,具體如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

整體流入如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 主線程向線程池提交任務(wù)
  2. 由于沒有核心線程可用,任務(wù)被放置到任務(wù)隊列
  3. 主線程進入等待狀態(tài),等待時間包括兩部分:

任務(wù)在隊列中等待線程調(diào)度時間9KY28資訊網(wǎng)——每日最新資訊28at.com

任務(wù)分配到線程后,任務(wù)實際執(zhí)行時間9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 如果前面等待的任務(wù)非常多,那等待時間將變的非常長

主線程等待時間 = 隊列等待時間 + 任務(wù)執(zhí)行時間。當(dāng)任務(wù)隊列非常長時,整體時間將遠超串行執(zhí)行時間。9KY28資訊網(wǎng)——每日最新資訊28at.com

1.2.3. 資源耗盡觸發(fā)拒絕策略

流量繼續(xù)增加,線程池的任務(wù)隊列已滿并且線程數(shù)量也達到上限,此時會觸發(fā)拒絕策略,具體如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

線程池默認拒絕策略為:AbortPolicy,直接拋出 RejectedExecutionException,從而觸發(fā)接口異常。9KY28資訊網(wǎng)——每日最新資訊28at.com

還有更可怕的情況,就是部分提交,也就是主線程已經(jīng)成功提交幾個任務(wù),如下圖所示:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

核心流程如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 主線程已經(jīng)成功提交兩個任務(wù)
  2. 在提交第三個任務(wù)時,由于資源不夠觸發(fā)拒絕策略,拋出異常導(dǎo)致主線程提前結(jié)束
  3. 已經(jīng)成功提交的任務(wù)仍舊會被線程執(zhí)行,由于主線程已經(jīng)退出,執(zhí)行結(jié)果沒有任何意義,從而白白浪費系統(tǒng)資源

2. 解決方案

前面已經(jīng)分析的很清楚,問題的本質(zhì)就是線程池資源分配不合理,核心參數(shù)設(shè)置錯誤:9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 隊列設(shè)置錯誤。在該場景下,需要充分利用線程資源,將任務(wù)放入隊列會增加任務(wù)在隊列的等待時間,隊列長度越大對系統(tǒng)的傷害越大;
  2. 拒絕策略設(shè)置錯誤。直接拋出異常會中斷主流程,導(dǎo)致部分無效任務(wù)(無意義任務(wù))提交,白白浪費系統(tǒng)資源;

除線程池參數(shù)問題外,還有個小問題:主線程完成任務(wù)提交后處于等待狀態(tài),未執(zhí)行任何有意義的操作,存在資源浪費。9KY28資訊網(wǎng)——每日最新資訊28at.com

2.1. 線程池改進方案

改進線程池如下所示:9KY28資訊網(wǎng)——每日最新資訊28at.com

int coreSize = Runtime.getRuntime().availableProcessors();executorService = new ThreadPoolExecutor(coreSize, coreSize * 5,        5L, TimeUnit.MINUTES,        new SynchronousQueue<>(),        new ThreadPoolExecutor.CallerRunsPolicy()        );

線程池配置如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 核心線程數(shù)不變,仍舊是 cpu 數(shù);
  2. 最大線程數(shù)不變,仍舊是 cpu 數(shù)的5倍;
  3. 空閑線程存活時間不變,仍舊是 5 分鐘;
  4. 使用 SynchronousQueue 替代 LinkedBlockingQueue(1024)。SynchronousQueue 是一個特殊的隊列,其最大容量是1。也就是說,任何一次插入操作都必須等待一個相應(yīng)的刪除操作,反之亦然。如果沒有相應(yīng)的操作正在進行,則該線程將被阻塞;
  5. 指定拒絕策略為 CallerRunsPolicy。當(dāng)線程池資源不夠時,由主線程來執(zhí)行任務(wù);

在這個配置下,及時線程池中的所有資源全部耗盡,也只會降級到串行執(zhí)行,不會讓系統(tǒng)變的更糟糕。9KY28資訊網(wǎng)——每日最新資訊28at.com

新配置下,系統(tǒng)表現(xiàn)如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

在最差的情況下也僅僅與串行執(zhí)行耗時一致。9KY28資訊網(wǎng)——每日最新資訊28at.com

總體來說就一句話:線程池有資源可用,那就為主線程分擔(dān)部分壓力;如果沒有資源可用,那就由主線程獨自完成。9KY28資訊網(wǎng)——每日最新資訊28at.com

2.1. 充分利用主線程

上面提到一個小問題,在資源充足情況下,所有任務(wù)均有線程池線程完成,主線程一致處于等待狀態(tài),存在一定的資源浪費。9KY28資訊網(wǎng)——每日最新資訊28at.com

如下圖所示:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

3 個任務(wù)耗費 4 個線程資源:9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 線程池3個線程負責(zé)執(zhí)行任務(wù)
  2. 主線程等待執(zhí)行結(jié)果,一直處于阻塞狀態(tài)

為了充分利用線程資源,可以讓主線程負責(zé)執(zhí)行任意一個任務(wù)。如下圖所示:9KY28資訊網(wǎng)——每日最新資訊28at.com

圖片圖片9KY28資訊網(wǎng)——每日最新資訊28at.com

主線程不在盲目等待,也負責(zé)一個任務(wù)的執(zhí)行,這樣 3 個任務(wù)只需 3 個線程即可。9KY28資訊網(wǎng)——每日最新資訊28at.com

代碼上也非常簡單,具體如下:9KY28資訊網(wǎng)——每日最新資訊28at.com

public RestResult<OrderDetailVO> getOrderDetailNew(@PathVariable Long orderId){    Stopwatch stopwatch = Stopwatch.createStarted();    OrderService.Order order = this.orderService.getById(orderId);    if (order == null){        return RestResult.success(null);    }    Future<UserService.User> userFuture = this.executorService.submit(() -> userService.getById(order.getUserId()));    Future<AddressService.Address> addressFuture = this.executorService.submit(() -> addressService.getById(order.getUserAddressId()));    Future<CouponService.Coupon> couponFuture = this.executorService.submit(() -> couponService.getById(order.getCouponId()));//        Future<ProductService.Product> productFuture = this.executorService.submit(() -> productService.getById(order.getProductId()));    OrderDetailVO orderDetail = new OrderDetailVO();    // 由主線程負責(zé)運行    orderDetail.setProduct(productService.getById(order.getProductId()));    orderDetail.setUser(getFutureValue(userFuture));    orderDetail.setAddress(getFutureValue(addressFuture));    orderDetail.setCoupon(getFutureValue(couponFuture));    log.info("并行 Cost {} ms", stopwatch.stop().elapsed(TimeUnit.MILLISECONDS));    return RestResult.success(orderDetail);}

主線程執(zhí)行不同的任務(wù),會對接口的響應(yīng)時間產(chǎn)生影響嗎?9KY28資訊網(wǎng)——每日最新資訊28at.com

不會,并行執(zhí)行整體耗時為 max(任務(wù)耗時),主線程必須獲取全部結(jié)果才能運行,所以必須等待這么長時間。9KY28資訊網(wǎng)——每日最新資訊28at.com

  1. 如果主線程運行的任務(wù)不是最耗時任務(wù),則需要等待最耗時任務(wù)執(zhí)行完成才能執(zhí)行后續(xù)邏輯;
  2. 如果主線程運行的是最耗時任務(wù),則其他線程已經(jīng)執(zhí)行完成并提前釋放資源;

3. 示例&源碼

代碼倉庫:https://gitee.com/litao851025/learnFromBug9KY28資訊網(wǎng)——每日最新資訊28at.com

代碼地址:https://gitee.com/litao851025/learnFromBug/tree/master/src/main/java/com/geekhalo/demo/thread/paralleltask9KY28資訊網(wǎng)——每日最新資訊28at.com


9KY28資訊網(wǎng)——每日最新資訊28at.com

本文鏈接:http://www.www897cc.com/showinfo-26-66204-0.html【故障現(xiàn)場】多線程性能優(yōu)化最大的坑,99%人都不自知

聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: CSS 滾動驅(qū)動動畫實現(xiàn)圓弧滾動條

下一篇: 牢記這 16 個 SpringBoot 擴展接口,寫出更加漂亮的代碼

標(biāo)簽:
  • 熱門焦點
Top 主站蜘蛛池模板: 乳山市| 岳池县| 天峨县| 双城市| 黄梅县| 蓬安县| 永顺县| 土默特右旗| 桂平市| 庆城县| 英德市| 武清区| 彭州市| 榆林市| 始兴县| 大名县| 衡东县| 攀枝花市| 新乡市| 林周县| 金平| 定远县| 宜春市| 全南县| 柘荣县| 霍山县| 壤塘县| 河池市| 宜城市| 乌拉特中旗| 五原县| 临漳县| 城市| 岢岚县| 洛浦县| 威海市| 全州县| 安泽县| 高邮市| 潼南县| 永登县|