Spring Boot: 響應式編程以及 Spring Boot Webflux 快速入門

在這裏插入圖片描述

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

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

響應式編程是基於異步和事件驅動的非阻塞程序,只需要在程序內啓動少量線程擴展,而不是水平通過集羣擴展。

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

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

  1. 爲什麼要使用響應式編程**(瞭解源碼可+求求: 1791743380)**
    在如今互聯網時代的大背景下,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應用,緩存有多重要,更高的緩存命中率就意味着性能。

並行化:使用更多的線程和硬件資源;
異步化:基於現有的資源來提高執行效率。
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 的模式差別不是很大,只是在方法的返回值上有所區別。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章