您好,登錄后才能下訂單哦!
這篇文章主要介紹“java異步并發請求和請求合并舉例分析”,在日常操作中,相信很多人在java異步并發請求和請求合并舉例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”java異步并發請求和請求合并舉例分析”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
在做業務系統需求開發中,經常需要從其他服務獲取數據,拼接數據,然后返回數據給前端使用;常見的服務調用就是通過http接口調用,而對于http,通常一個請求會分配一個線程執行,在同步調用接口的情況下,整個線程是一直被占用或者阻塞的;如果有大量的這種請求,整個系統的吞吐量就比較低,而在依賴的服務響應時間比較低的情況下,我們希望先讓出cpu,讓其他請求先執行,等依賴的服務請求返回結果時再繼續往下執行,這時我們會考慮將請求異步化,或者將相同的請求合并,從而達到提高系統執行效率和吞吐量的目的。
目前常見的幾種調用方式是同步調用,線程池+future,異步回調completableFuture;協程也是異步調用的解決方式,但java目前不支持協程;對于future方式,只能用get或者while(!isDone)輪詢這種阻塞的方式直到線程執行完成,這也不是我們希望的異步執行方式,jdk8提供的completableFuture其實也不是異步的方式,只是對依賴多服務的Callback調用結果處理做結果編排,來彌補Callback的不足,從而實現異步鏈式調用的目的,這也是比較推薦的方式。
RpcService rpcService = new RpcService(); HttpService httpService = new HttpService(); // 假設rpc1耗時10ms Map<String, String> rpcResult1=rpcService.getRpcResult(); // 假設rpc2耗時20ms Integer rpcResult2 = httpService.getHttpResult(); // 則線程總耗時:30ms
ExecutorService executor = Executors.newFixedThreadPool(2); RpcService rpcService = new RpcService(); HttpService httpService = new HttpService(); future1 = executor.submit(() -> rpcService.getRpcResult()); future2 = executor.submit(() -> httpService.getHttpResult()); //rpc1耗時10ms Map<String, String> rpcResult1 = future1.get(300, TimeUnit.MILLISECONDS); //rpc2耗時20ms Integer rpcResult2 = future2.get(300, TimeUnit.MILLISECONDS); //則線程總耗時20ms
/** * 場景:兩個接口并發異步調用,返回CompletableFuture,不阻塞主線程 * 兩個服務也是異步非阻塞調用 **/ CompletableFuture future1 = service.getHttpData("http://www.vip.com/showGoods/50"); CompletableFuture future2 = service.getHttpData("http://www.vip.com/showGoods/50"); CompletableFuture future3 = future1.thenCombine(future2, (f1, f2) -> { //處理業務.... return f1 + "," + f2; }).exceptionally(e -> { return ""; });
CompletableFuture使用ForkJoinPool執行線程,在ForkJoinPool類注冊ForkJoinWorkerThread線程時可以看到,ForkJoinPool里面的線程都是daemon線程(垃圾回收線程就是一個典型的deamon線程),
/** * Callback from ForkJoinWorkerThread constructor to establish and * record its WorkQueue. * * @param wt the worker thread * @return the worker's queue */ final WorkQueue registerWorker(ForkJoinWorkerThread wt) { UncaughtExceptionHandler handler; #注冊守護線程 wt.setDaemon(true); // configure thread if ((handler = ueh) != null) wt.setUncaughtExceptionHandler(handler); WorkQueue w = new WorkQueue(this, wt); int i = 0; // assign a pool index int mode = config & MODE_MASK; int rs = lockRunState(); try { WorkQueue[] ws; int n; // skip if no array if ((ws = workQueues) != null && (n = ws.length) > 0) { int s = indexSeed += SEED_INCREMENT; // unlikely to collide int m = n - 1; i = ((s << 1) | 1) & m; // odd-numbered indices if (ws[i] != null) { // collision int probes = 0; // step by approx half n int step = (n <= 4) ? 2 : ((n >>> 1) & EVENMASK) + 2; while (ws[i = (i + step) & m] != null) { if (++probes >= n) { workQueues = ws = Arrays.copyOf(ws, n <<= 1); m = n - 1; probes = 0; } } } w.hint = s; // use as random seed w.config = i | mode; w.scanState = i; // publication fence ws[i] = w; } } finally { unlockRunState(rs, rs & ~RSLOCK); } wt.setName(workerNamePrefix.concat(Integer.toString(i >>> 1))); return w; }
當主線程執行完后,jvm就會退出,所以需要考慮主線程執行完成時間和fork出去的線程執行時間,也需要考慮線程池的大小,默認為當前cpu的核數-1,可以參考下其他系統的故障記錄:CompletableFuture線程池問題。
當系統遇到瞬間產生大量請求時,可以考慮將相同的請求合并,最大化利用系統IO,提高系統的吞吐量。
設計時,可以將符合條件的url請求,先收集起來,直到滿足以下條件之一時進行合并發送:
收集到的請求數超過預設的最大請求數。
距離上次請求發送時長超過預設的最大時長。
實現方案有自行使用阻塞隊列方式:并發環境下的請求合并,也可以考慮Hystrix:Hystrix實現請求合并/請求緩存
目前公司網關組件janus也是通過合并auth請求的方式減少網絡開銷,提高cpu的利用率和系統吞吐量的。
nginx同樣有合并請求模塊nginx-http-concat用來減少請求io,參考:nginx 合并多個js/css請求為一個請求
賴澤坤@vipshop.com
到此,關于“java異步并發請求和請求合并舉例分析”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。