使用非同步任務降低API延遲_實踐總結

来源:https://www.cnblogs.com/fairjm/archive/2018/02/13/async_api_latency.html
-Advertisement-
Play Games

之前在想如何降低API的延遲,這些API里有幾個比較耗時的操作且是串列執行,那通過非同步執行的方式理論上可以降低運行的時間,如下圖所示: 具體的實現比較簡單,例如這樣: 用java8引入的 即可。 這裡不再贅述。 主要講一下這樣實踐遇到的坑和一些自己的理解。 性能測試 優化後的代碼需要和未修改(基準) ...


之前在想如何降低API的延遲,這些API里有幾個比較耗時的操作且是串列執行,那通過非同步執行的方式理論上可以降低運行的時間,如下圖所示:

具體的實現比較簡單,例如這樣:

public class ParallelRetrievalExample {
    final CacheRetriever cacheRetriever;
    final DBRetriever dbRetriever;
    ParallelRetrievalExample(CacheRetriever cacheRetriever,
                             DBRetriever dbRetriever) {
        this.cacheRetriever = cacheRetriever;
        this.dbRetriever = dbRetriever;
    }
    public Object retrieveCustomer(final long id) {
        final CompletableFuture<Object> cacheFuture =
           CompletableFuture.supplyAsync(() -> {
                    return cacheRetriever.getCustomer(id);
                });
        final CompletableFuture<Object> dbFuture =
           CompletableFuture.supplyAsync(() -> {
                    return dbRetriever.getCustomer(id);
                });
        return CompletableFuture.anyOf(
            cacheFuture, dbFuture);
    }

用java8引入的CompletableFuture即可。

這裡不再贅述。

主要講一下這樣實踐遇到的坑和一些自己的理解。

性能測試

優化後的代碼需要和未修改(基準)的版本做比較,要考慮在不同負載下的性能情況。
針對API的修改可以使用AB工具,比較方便,能通過設定不同的併發用戶模擬不同的負載。
測試是必要的,很多直覺上會提高性能的點可能會在實際表現上收到資源的限制等原因無法提高甚至不如優化前的性能。

適合處理的任務 & 線程池的設定

我們要優化怎樣的任務呢?

任務也就三大分類,計算密集,IO密集和混合,其中混合裡面也可以通過細化變為前兩類。
在一般的web開發中計算不太會成為瓶頸,主要是IO。
一些耗時的阻塞IO操作(資料庫,RPC調用)往往是導致介面慢的原因,這裡要優化的就是這類操作。
不過與其說是優化,更恰當的說法是讓這些阻塞操作非同步化,縮短整體的時間,這裡也要註意這些任務所在的位置,如果在API的最後面的邏輯里那優化他們也沒什麼必要,或者在不影響業務邏輯的情況下可以把他們置前。

我們需要的怎樣的線程池?

如上所說要優化的任務幾乎都是阻塞IO,也就意味著這些任務占用CPU的時間很短,主要是處在waiting狀態下,這種線程的增加最大的開銷就是記憶體,對上下文切換影響較小。
其次,線程數必定要有限,java的線程過於重量,不考慮CPU因素也需要考慮記憶體因素。
最後還要考慮線程池耗盡的情況,最差的情況是回到沒優化之前,也就是在調用者線程上執行。

CompletableFuturerunAsyncsupplyAsync方法有不帶Executor的版本,首先看一下預設的線程池是否合適。

private static final Executor asyncPool = useCommonPool ?
    ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();

useCommonPool的判斷是根據ForkJoinPool的並行度,可以簡單地先認為多核下會返回true(也可以通過java.util.concurrent.ForkJoinPool.common.parallelism參數進行設定)。
而使用的commonPool()線程數量不是很多(預設和CPU核數相等),其次ForkJoinPool是設計用於短任務的運行,不適合做阻塞IO,我們要優化的主要慢操作幾乎都是阻塞IO帶來的。

接下去看需求比較接近的Executors.newFixedThreadPool,但通過實現不難發現他的隊列是無界的,如果線程耗盡新的任務就會等待,也無法使用拒絕策略。

只有定製了,根據上面說到的需求,定製如下:

private static final ThreadPoolExecutor IO = new ThreadPoolExecutor(20, 20, 0, TimeUnit.MILLISECONDS,
            new SynchronousQueue<Runnable>(),
            new CallerRunsPolicy());

線程數量定長,數量的多少可以根據測試情況做下調整,使用SynchronousQueue不產生隊列,拒絕策略使用在調用者線程上運行,滿足了所需。

這個線程池專門為IO密集任務使用,不要讓計算密集的代碼使用。
在實踐中遇到了使用這種方式結果測試時性能降低了5倍左右的情況,一看代碼中除了從資料庫獲取數據還有幾個for迴圈在做修改欄位的工作,導致上下文切換帶來了很大的開銷。

思考

上述實現中,限制線程數量的原因是因為線程的開銷(這裡主要是在記憶體上)過大,這就意味著在這裡使用了線程過重了,更好的實現應該使用類似綠色線程的技術,和系統線程進行1對多的映射。
此外這種場景下用事件驅動的方式可能會更好。
追究其核心原因還是java世界中同步阻塞操作還是占多數,而主要的優化手段底層還是使用了昂貴的線程,一些在其他語言/平臺上很容易實現的擴展在java上就會遇到問題。
此外,非同步沒有得到語言上的支持,造成非同步編程在java上比較麻煩和顯式,這點C#asyncawait語法糖就要甜的多。
java之後的發展還是任重而道遠啊。

參考資料

apache ab

reactive design pattern 上述的圖和ParallelRetrievalExample代碼取自這裡

多線程的代價及上下文切換

Java CompletableFuture 詳解

併發之痛 Thread,Goroutine,Actor


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 傳統的給文件鏈接添加版本號的方法是使用gulp-rev,這裡提出的解決方案是使用python來替代gulp-rev。 將以上代碼另存成auto_version.py文件後,新建auto_version.bat文件,寫入以下內容: 修改好.bat文件里的路徑後,雙擊運行即可。 ...
  • Android上不應該使用枚舉,占記憶體,應該使用@XXXDef註解來替代 使用 Enum 的缺點 每一個枚舉值都是一個對象,在使用它時會增加額外的記憶體消耗,所以枚舉相比與 Integer 和 String 會占用更多的記憶體。 較多的使用 Enum 會增加 DEX 文件的大小,會造成運行時更多的開銷, ...
  • #用for 迴圈for i in range(1,10): #print(i) # print(i*'*') for j in range(1,i+1): print('%d * %d = %d'%(j,i,i*j),end=' ') print('\n')#用while 迴圈 i = 1while ...
  • 什麼是Ajax Ajax(Asynchronous JavaScript and XML) 非同步JavaScript和XML Ajax實際上是下麵這幾種技術的融合: (1)XHTML和CSS的基於標準的表示技術 (2)DOM進行動態顯示和交互 (3)XML和XSLT進行數據交換和處理 (4)XMLH ...
  • Spring MVC的配置和使用 筆記倉庫: "https://github.com/nnngu/LearningNotes" Spring MVC需要的jar包 文章中 Spring MVC 使用的版本是 3.2.18 , 需要的 jar 包如下: 使用 Maven 構建的 Java 項目,需要在 ...
  • 正文之前 在學習了一段時間的Java Web的內容之後,當然需要有個項目來練練手,我相信大多數人的首選項目都是信息管理系統吧,所以我選擇了商品信息管理系統 目前項目源碼已全部上傳至GitHub,歡迎大家來 fork —— "商品信息管理系統" 正文 項目構思 簡易的管理系統,結構為 Servlet ...
  • 本文主要內容 序列類型分類: (1)容器序列、扁平序列 (2)可變序列、不可變序列 列表推導式 生成器表達式 元組拆包 切片 排序(list.sort方法和sorted函數) bisect 文中代碼均放在github上:https://github.com/ampeeg/cnblogs/tree/m ...
  • 一、Hibernate查詢 1.Hibernate檢索(查詢)方式的分類 OID檢索 :根據主鍵查詢,get/load 對象導航檢索 :通過一個對象獲得其關聯對象.【重點】 Category category = session.get(Category.class, 1);Set<Product> ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...