前言
只有光頭才能變強。
文本已收錄至我的GitHub精選文章,歡迎Star:https://github.com/ZhongFuCheng3y/3y
回顧一下上篇我對WebFlux的入門,如果沒讀過的同學建議讀一下再來看本篇文章,上一篇文章花了我很多的心血~~
開局再來一張圖,內容全靠編:
這篇主要寫寫我初學時對WebFlux的一些疑問,不知道大家在看上一篇文章的時候有沒有相應的問題呢?
這次學WebFlux主要的動力是公司組內分享,寫了一個PPT,有需要的同學在我的公眾號(Java3y)下回復“PPT”即可獲取。
一、本來就能實現異步非阻塞,為啥要用WebFlux?
相信有過相關了解的同學都知道,Servlet 3.1
就已經支持異步非阻塞了。
我們可以以自維護線程池的方式實現異步
- 說白了就是Tomcat的線程處理請求,然后把這個請求分發到自維護的線程處理,Tomcat的請求線程返回
@WebServlet(value = "/nonBlockingThreadPoolAsync", asyncSupported = true)
public class NonBlockingAsyncHelloServlet extends HttpServlet {
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(100));
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AsyncContext asyncContext = request.startAsync();
ServletInputStream inputStream = request.getInputStream();
inputStream.setReadListener(new ReadListener() {
@Override
public void onDataAvailable() throws IOException {
}
@Override
public void onAllDataRead() throws IOException {
executor.execute(() -> {
new LongRunningProcess().run();
try {
asyncContext.getResponse().getWriter().write("Hello World!");
} catch (IOException e) {
e.printStackTrace();
}
asyncContext.complete();
});
}
@Override
public void onError(Throwable t) {
asyncContext.complete();
}
});
}
}
流程圖如下:
上面的例子來源:
簡單的方式,我們還可以使用JDK 8 提供的CompletableFuture
類,這個類可以方便的處理異步調用。
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long t1 = System.currentTimeMillis();
// 開啟異步
AsyncContext asyncContext = request.startAsync();
// 執行業務代碼(doSomething 指的是處理耗費時間長的方法)
CompletableFuture.runAsync(() -> doSomeThing(asyncContext,
asyncContext.getRequest(), asyncContext.getResponse()));
System.out.println("async use:" + (System.currentTimeMillis() - t1));
}
要處理復雜的邏輯時,無論是回調或 CompletableFuture在代碼編寫上都會比較復雜(代碼量大,不易於看懂),而WebFlux使用的是Reactor響應式流,里邊提供了一系列的API供我們去處理邏輯,就很方便了。
更重要的是:
- WebFlux使用起來可以像使用SpringMVC一樣,能夠大大減小學習成本
- WebFlux也可以使用Functional Endpoints方式編程,總的來說還是要比
回調/CompletableFuture
要簡潔和易編寫。
值得一提的是:
如果Web容器使用的是Tomcat,那么就是使用Reactor橋接的servlet async api
如果Web容器是Netty,那么就是使用的Netty,天生支持Reactive官方的推薦是使用Netty跑WebFlux
二、WebFlux性能的問題
我們從上篇文章中就發現,瀏覽器去調用處理慢的接口,無論是該接口是同步的,還是說是異步的,返回到瀏覽器的時間都是一致的。
- 同步:服務器接收到請求,一個線程會處理請求,直到該請求處理完成,返回給瀏覽器
- 異步:服務器接收到請求,一個線程會處理請求,然后指派別的線程處理請求,請求的線程直接空閑出來。
官網也說了:
Reactive and non-blocking generally do not make applications run faster
使用異步非阻塞的好處就是:
The key expected benefit of reactive and non-blocking is the ability to scale with a small, fixed number of threads and less memory.That makes applications more resilient under load, because they scale in a more predictable way
好處:只需要在程序內啟動少量線程擴展,而不是水平通過集群擴展。異步能夠規避文件IO/網絡IO阻塞所帶來的線程堆積。
下面來看一下針對相同的請求量,同步阻塞和異步非阻塞的吞吐量和響應時長對比:
注:
- 請求量不大時(3000左右),同步阻塞多線程處理請求,吞吐量和響應時長都沒落后。(按道理WebFlux可能還要落后一些,畢竟多做了一步處理
-->
將請求委派給另一個線程去做處理 - 請求量大時,線程數不夠用,同步阻塞(MVC)只能等待,所以吞吐量要下降,響應時長要提高(排隊)。
Spring WebFlux在應對高並發的請求時,借助於異步IO,能夠以少量而穩定的線程處理更高吞吐量的請求,尤其是當請求處理過程如果因為業務復雜或IO阻塞等導致處理時長較長時,對比更加顯著。
三、WebFlux實際應用
WebFlux需要非阻塞的業務代碼,如果阻塞,需要自己開線程池去運行。WebFlux什么場景下可以替換SpringMVC呢?
- 想要內存和線程數較少的場景
- 網絡較慢或者IO會經常出現問題的場景
SpringMVC和WebFlux更多的是互補關系,而不是替換。阻塞的場景該SpringMVC還是SpringMVC,並不是WebFlux出來就把SpringMVC取代了。
如果想要發揮出WebFlux的性能,需要從Dao到Service,全部都要是Mono和Flux,目前官方的數據層Reactive框架只支持Redis,Mongo等幾個,沒有JDBC。
目前對於關系型數據庫,Pivotal團隊開源出R2DBC(Reactive Relational Database Connectivity),其GitHub地址為:
目前R2DBC支持三種數據源:
總的來說,因為WebFlux是響應式的,要想發揮出WebFlux的性能就得將代碼全改成響應式的,而JDBC目前是沒支持的(至少MySQL還沒支持),而響應式的程序不好調試和編寫(相對於同步的程序),所以現在WebFlux的應用場景還是相對較少的。
所以,我認為在網關層用WebFlux比較合適(本來就是網絡IO較多的場景)
現在再回來看Spring官網的圖,是不是就更親切了?
參考資料:
- [https://blog.lovezhy.cc/2018/12/29/webflux%E6%80%A7%E8%83%BD%E9%97%AE%E9%A2%98/](https://blog.lovezhy.cc/2018/12/29/webflux性能問題/)
四、有必要學Functional Endpoints 編程模式嗎?
前面也提到了,WebFlux提供了兩種模式供我們使用,一種是SpringMVC 注解的,一種是叫Functional Endpoints
的
Lambda-based, lightweight, and functional programming model
總的來看,就是配合Lambda和流式編程去使用WebFlux。如果你問我:有必要學嗎?其實我覺得可以先放着。我認為現在WebFlux的應用場景還是比較少,等真正用到的時候再學也不是什么難事,反正就是學些API嘛~
有Lambda表達式和Stream流的基礎,等真正用到的時候再學也不是啥問題~
以下是通過注解的方式來使用WebFlux的示例:
以下是通過Functional Endpoints
的方式來使用WebFlux的示例:
路由分發器,相當於注解的GetMapping...
UserHandler,相當於UserController:
五、WebFlux的實際使用場景
總的來說,因為WebFlux是響應式的,要想發揮出WebFlux的性能就得將代碼全改成響應式的,而JDBC目前是沒支持的(至少MySQL還沒支持),而響應式的程序不好調試和編寫(相對於同步的程序),老項目也不太可能把依賴直接升上Spring5.0,所以現在WebFlux的應用場景還是相對較少的(個人覺得)。
網關層用WebFlux比較合適(本來就是網絡IO較多的場景)
- SpringCloud Gateway是基於WebFlux實現的
最后
這次學WebFlux主要的動力是公司組內分享,寫了一個PPT,有需要的同學在我的公眾號(Java3y)下回復“PPT”即可獲取。
本已收錄至我的GitHub精選文章,歡迎Star:https://github.com/ZhongFuCheng3y/3y
樂於輸出干貨的Java技術公眾號:Java3y。公眾號內有300多篇原創技術文章、海量視頻資源、精美腦圖,關注即可獲取!
非常感謝人才們能看到這里,如果這個文章寫得還不錯,覺得「三歪」我有點東西的話 求點贊 求關注️ 求分享👥 求留言💬 對暖男我來說真的 非常有用!!!
創作不易,各位的支持和認可,就是我創作的最大動力,我們下篇文章見!