In computing, reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. - Reactive programming - Wikipediareact
在上述響應式編程(如下簡稱RP)的定義中,除了異步編程,還包含兩個重要的關鍵詞:spring
- Data streams: 即數據流,分爲靜態數據流(好比數組,文件)和動態數據流(好比事件流,日誌流)兩種。基於數據流模型,RP得以提供一套統一的Stream風格的數據處理接口。和Java 8中的Stream API相比,RP API除了支持靜態數據流,還支持動態數據流,而且容許複用和同時接入多個訂閱者。
- The propagation of change: 變化傳播,簡單來講就是以一個數據流爲輸入,通過一連串操做轉化爲另外一個數據流,而後分發給各個訂閱者的過程。這就有點像函數式編程中的組合函數,將多個函數串聯起來,把一組輸入數據轉化爲格式迥異的輸出數據。
一個容易混淆的概念是響應式設計,雖然它的名字中也包含了「響應式」三個字,但其實和RP徹底是兩碼事。響應式設計是指網頁可以自動調整佈局和樣式以適配不一樣尺寸的屏幕,屬於網站設計的範疇,而RP是一種關注系統可響應性,面向數據流的編程思想或者說編程框架。編程
特性
從本質上說,RP是一種異步編程框架,和其餘框架相比,RP至少包含了如下三個特性:數組
- 描述而非執行:在你最終調用
subscribe()
方法以前,從發佈端到訂閱端,沒有任何事會發生。就比如不管多長的水管,只要水龍頭不打開,水管裏的水就不會流動。爲了提升描述能力,RP提供了比Stream豐富的多的多的API,好比buffer()
, merge()
, onErrorMap()
等。
- 提升吞吐量: 相似於HTTP/2中的鏈接複用,RP經過線程複用來提升吞吐量。在傳統的Servlet容器中,每來一個請求就會發起一個線程進行處理。受限於機器硬件資源,單臺服務器所能支撐的線程數是存在一個上限的,假設爲T,那麼應用同時能處理的請求數(吞吐量)必然也不會超過T。但對於一個使用Spring 5開發的RP應用,若是運行在像Netty這樣的異步容器中,不管有多少個請求,用於處理請求的線程數是相對固定的,所以最大吞吐量就有可能超過T。
- 背壓(Backpressure)支持:簡單來講,背壓就是一種反饋機制。在通常的Push模型中,發佈者既不知道也不關心訂閱者的處理速度,當數據的發佈速度超過處理速度時,須要訂閱者本身決定是緩存仍是丟棄。若是使用RP,決定權就交回給發佈者,訂閱者只須要根據本身的處理能力問發佈者請求相應數量的數據。你可能會問這不就是Pull模型嗎?實際上是不一樣的。在Pull模型中,訂閱者每次處理完數據,都要從新發起一次請求拉取新的數據,而使用背壓,訂閱者只須要發起一次請求,就能接二連三的重複請求數據。
適用場景
瞭解了RP的這些特性,你可能已經猜測到RP有哪些適用場景了。通常來講,RP適用於高併發、帶延遲操做的場景,好比如下這些狀況(的組合):緩存
- 一次請求涉及屢次外部服務調用
- 非可靠的網絡傳輸
- 高併發下的消息處理
- 彈性計算網絡
代價
Every coin has two sides.服務器
和任何框架同樣,有優點必然就有劣勢。RP的兩個比較大的問題是:網絡
- 雖然複用線程有助於提升吞吐量,但一旦在某個回調函數中線程被卡住,那麼這個線程上全部的請求都會被阻塞,最嚴重的狀況,整個應用會被拖垮。
- 難以調試。因爲RP強大的描述能力,在一個典型的RP應用中,大部分代碼都是以鏈式表達式的形式出現,好比
flux.map(String::toUpperCase).doOnNext(s -> LOG.info("UC String {}", s)).next().subscribe()
,一旦出錯,你將很難定位到具體是哪一個環節出了問題。所幸的是,RP框架通常都會提供一些工具方法來輔助進行調試。
http://emacoo.cn/backend/reactive-overview/併發