1. 程式人生 > >Spring Boot (十四): 響應式程式設計以及 Spring Boot Webflux 快速入門

Spring Boot (十四): 響應式程式設計以及 Spring Boot Webflux 快速入門

1. 什麼是響應式程式設計

在計算機中,響應式程式設計或反應式程式設計(英語:Reactive programming)是一種面向資料流和變化傳播的程式設計正規化。這意味著可以在程式語言中很方便地表達靜態或動態的資料流,而相關的計算模型會自動將變化的值通過資料流進行傳播。

例如,在指令式程式設計環境中,a=b+c 表示將表示式的結果賦給 a,而之後改變 b 或 c 的值不會影響 a 。但在響應式程式設計中,a 的值會隨著 b 或 c 的更新而更新。

響應式程式設計是基於非同步和事件驅動的非阻塞程式,只需要在程式內啟動少量執行緒擴充套件,而不是水平通過叢集擴充套件。

設想一個場景,從底層資料庫驅動,經過持久層、服務層、MVC層中的model,到使用者的前端介面的元素,全部都採用宣告式的程式設計正規化,從而搭建一條能夠傳遞變化的管道,這樣我們只要更新一下資料庫中的資料,使用者的介面上就相應的發生變化,從而無需前端輪詢才能獲取到最新的資料。

簡單來講,我們以前寫的程式是阻塞式的,當一個請求任務過來時,執行緒會阻塞,等到這個任務完成後再返回出去。而響應式程式設計則是一個請求任務過來時,會有其他的執行緒去做處理,當任務執行結束後再非同步的通知回去。

2. 為什麼要使用響應式程式設計

在如今網際網路時代的大背景下,Web應用通常要面對高併發、海量資料的挑戰,效能從來都是必須要考量的核心因素。

阻塞便是效能殺手之一。

多數人不認為阻塞是一個比較大的問題,至少覺得除了網路I/O之外,讀寫檔案和資料庫還是很快的,許多人也一直在寫阻塞的程式碼。

那麼 I/O 操作具體有多慢?

2.1 CPU眼中的時間

以下內容來源 https://blog.csdn.net/get_set/article/details/79466402

CPU絕對稱得上是“閃電俠”,因為它們做事都有自己的一套時鐘。我們故事的主人公是一個主頻為2.5GHz的CPU,如果它的世界也有“秒”的概念,並且它的時鐘跳一下為一秒,那麼在CPU(CPU的一個核心)眼中的時間概念是啥樣的呢?

CPU先生所在的組是硬體部計算組。對它來說,與其一起緊密合作的幾個小夥伴還能跟的上它的節奏:

  • CPU先生很利索,只需要一秒就可以完成一個指令,複雜的動作可能需要多個指令。
  • 好在“貼身祕書”一級快取反應比較快,能夠秒懂CPU先生的意思。
  • 來自“祕書組”的二級快取雖然要十幾秒才能“get”到CPU先生的點,但也不算太遲鈍。
  • 和記憶體組的合作已經習以為常了,跟記憶體請求的資料通常要4-5分鐘才能找到(記憶體定址),不過也還好啦,畢竟一級快取那裡能拿到80%想要的資料,其餘的二級快取也能搞定一大部分,不怎麼耽誤事兒。

CPU先生是典型的工作狂,任務多的時候,通宵達旦也毫無怨言,但是有什麼事情讓它等,那簡直要他命了。恰恰一起共事的其他組(尤其是I/O組的磁碟和網絡卡)相對來說那效率是低的離譜啊:

  • 關於I/O組的同事,CPU先生已經抱怨很久了,每次找SSD要東西,都要花費4-5天才能找到(定址),等到資料傳送過來,幾周都過去了。機械磁碟更是過分地離譜,跟他要個數據,竟然要平均花費10個月才能找到,如果要讀取1M的資料,竟然要20個月!這種員工怎麼還不下崗?!
  • 關於網絡卡,CPU先生知道它們也盡力了,畢竟萬兆網路成本頗高。與機房內的其他小夥伴們用千兆網路互相溝通也算順暢,給另一臺機器的CPU朋友傳送1K的書信,最快七八個小時就可以送過去了。但是1K的書信經過層層包裹,實際也寫不了多少話。更要命的是,網絡卡們的溝通手續繁雜,每次網路溝通前的 “你好能聽到我嗎?——我能聽到,你那邊能聽到我嗎?——我也能聽到你,那我們開始吧!” 這樣的握手確認都要花掉很長的時間,不過不能當面溝通,也只能這樣了。這還好,最恐怖的是與其他城市的小夥伴溝通,有時候傳遞訊息要花費好幾年呢!

由此可見,對於CPU先生來說,想要讓工作充實起來實在不容易,不過多虧了記憶體組的小夥伴幫忙分批快取往返於I/O組的資料,矛盾才有所緩解。

這個圖只能明顯看出涉及I/O的時間條,我們轉換成對數刻度的圖看一下:

這個圖並不是直觀的比例,橫軸上每個刻度是一個數量級,可見I/O的速度與CPU和記憶體相比是要差幾個數量級的。由此可見,對於大型高併發場景下的Web應用,快取有多重要,更高的快取命中率就意味著效能。

  1. 並行化:使用更多的執行緒和硬體資源;
  2. 非同步化:基於現有的資源來提高執行效率。

3. 基礎概念

在介紹主題之前先普及幾個概念:

3.1 Backpressure(背壓)

背壓是一種常用策略,使得釋出者擁有無限制的緩衝區儲存元素,用於確保釋出者釋出元素太快時,不會去壓制訂閱者。

3.2 Reactive Streams(響應式流)

一般由以下組成:

  • 釋出者:釋出元素到訂閱者
  • 訂閱者:消費元素
  • 訂閱:在釋出者中,訂閱被建立時,將與訂閱者共享
  • 處理器:釋出者與訂閱者之間處理資料

3.3 Mono 和 Flux

  • Mono:實現釋出者,並返回 0 或 1 個元素
  • Flux:實現釋出者,並返回 N 個元素

4. Spring Webflux

Spring Boot Webflux 是基於 Reactor 實現的。Spring Boot 2.0 包括一個新的 spring-webflux 模組。該模組包含對響應式 HTTP 和 WebSocket 客戶端的支援,以及對 REST,HTML 和 WebSocket 互動等程式的支援。一般來說,Spring MVC 用於同步處理,Spring Webflux 用於非同步處理。

Spring Boot Webflux 有兩種程式設計模型實現,一種類似 Spring MVC 註解方式,另一種是使用其功能性端點方式。

4.1 適用性

一圖就很明確了,WebFlux 和 MVC 有交集。但是注意:

  • MVC 能滿足場景的,就不需要更改為 WebFlux。
  • 要注意容器的支援,可以看看下面內嵌容器的支援。
  • 微服務體系結構,WebFlux 和 MVC 可以混合使用。尤其開發 IO 密集型服務的時候,選擇 WebFlux 去實現。

4.2 內嵌容器

跟 Spring Boot 大框架一樣啟動應用,但 WebFlux 預設是通過 Netty 啟動,並且自動設定了預設埠為 8080。另外還提供了對 Jetty、Undertow 等容器的支援。開發者自行在新增對應的容器 Starter 元件依賴,即可配置並使用對應內嵌容器例項。

但是要注意,必須是 Servlet 3.1+ 容器,如 Tomcat、Jetty;或者非 Servlet 容器,如 Netty 和 Undertow。

4.3 資料庫

支援 reactive 程式設計的資料庫只有 MongoDB , redis , Cassandra , Couchbase 。

4.4 快速上手

工程依賴

程式碼清單:spring-boot-webflux/pom.xml
***

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

Service 類

程式碼清單:spring-boot-webflux/src/main/java/com/springboot/springbootwebflux/service/impl/UserServiceImpl.java
***

@Service
public class UserServiceImpl implements UserSerivice {

    private static Map<Long, User> map = new HashMap<>();

    static {
        map.put(1L, new User(1L, "www.geekdigging.com", 18));
        map.put(2L, new User(2L, "極客挖掘機", 28));
    }

    @Override
    public Mono<User> getUserById(Long id) {

        return Mono.just(map.get(id));
    }
}

Controller 類

程式碼清單:spring-boot-webflux/src/main/java/com/springboot/springbootwebflux/controller/UserController.java
***

@RestController
public class UserController {

    @Autowired
    UserSerivice userSerivice;

    @GetMapping("/getUserById/{id}")
    public Mono<User> getUserById(@PathVariable Long id) {
        return userSerivice.getUserById(id);
    }
}

通過上面的示例可以發現,開發模式和之前 Spring MVC 的模式差別不是很大,只是在方法的返回值上有所區別。

5. 示例程式碼

示例程式碼-Github

示例程式碼-Gitee

6. 參考

https://blog.csdn.net/get_set/article/details/79466402

http://www.ityouknow.com/springboot/2019/02/12/spring-boot-webflux.html

https://www.cnblogs.com/limuma/p/9315442.h