最近在看 Servlet 3 的異步特性,在網上看了一些文章,有些不解,遂作了一些測試,測試的模擬場景:Web 容器工做線程較少,接口邏輯複雜耗時。數據庫
這個場景是大部分的業務場景,例如:Tomcat 的工做線程是 200,業務接口須要遠程 RPC 調用,或者訪問數據庫進行復雜計算等耗時操做。tomcat
因此,仍是比較有參考價值的。服務器
工具:idea,jmeter。app
服務端 idea:SpringBoot 默認容器 Tomcat,修改線程數爲 2,沒錯就是 2,爲了放大工做線程較少的 場景。同時,準備兩個接口:異步接口和同步接口,分別模擬耗時 500ms。異步
代碼以下:async
1, 異步接口ide
@ResponseBody @Controller @RequestMapping("async") public class AsyncTestDemo { static ExecutorService workerPool = Executors.newFixedThreadPool(1000); @RequestMapping("async") public DeferredResult<String> async() { DeferredResult<String> defer = new DeferredResult<>((long) 120000); workerPool.submit(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } defer.setResult("hello async"); }); return defer; } }
2, 同步接口工具
@ResponseBody @Controller @RequestMapping("sync") class SyncTestDemo{ static ExecutorService workerPool = Executors.newFixedThreadPool(1000); @RequestMapping("/sync") public String sync() throws ExecutionException, InterruptedException { Future<String> future = workerPool.submit(() -> { Thread.sleep(500); return "hello sync"; }); return future.get(); } }
3, Tomcat 配置:性能
server.port=8081 server.tomcat.max-threads=2
客戶端 jmeter:開啓 200 線程分別訪問兩個接口。獲得聚合報告。測試
從聚合報告能夠看出,二者差距巨大。 平均響應時間相差 45 倍,90% 的響應時間相差 73 倍,99% 的響應時間相差 66 倍。
同步 Servlet 只有在最初的 2 個請求中,響應時間在 500ms 左右,以後,因爲線程數不夠,後面的請求開始線性延時,最大響應時間爲 44 秒(最後一個請求)。同時,錯誤率達到 11.50%。平均響應時間爲 24 秒。
而異步 Servlet 則很是的穩定,錯誤率爲 0 ,全部的請求響應時間都控制在 500ms 到 680ms 之間,平均時間爲 541 ms,因爲 Tomcat 線程數只有 2 個,這個結果能夠接受。90% 的響應時間也是在 600 毫秒左右,屬於正常水平。
異步 Servlet 發佈已久,但彷佛 Java 社區使用的人仍是很少,不知爲什麼?但異步帶來的性能提高不言而喻。在服務器線程數較少,業務耗時的場景下,異步能明顯提升系統吞吐量,線程數以外的請求不會像同步請求同樣被拖慢。
實際上,這和 Netty 的最佳實踐是相似的,永遠不要在 IO 線程上作耗時任務,原則是:耗時任務丟進業務線程池,異步操做結束從 IO 線程返回或者直接返回。
防止 IO 線程阻塞,影響後面的請求。