Backpressure in Reactive Systems 響應式系統的反壓

祝坤榮發表於2022-02-20
原文
https://foojay.io/today/backp...

一月中,我基於我的文章遷移到Reactive的必要條件Spring Boot應用做了一個分享
https://www.youtube.com/watch...

因為那是一個Kotlin的聚會,我是用Kotlin程式碼展示的,同時我加了一個將程式碼庫遷移到協程的步驟。

在QA環節,有人問到是否協程實現了反壓。我承認我也不確定,所以我做了一點研究。

本文提供了關於反壓的概要資訊,還有如何用Rxjava(v3),Project Reactor和Kotlin的協程Coroutines如何處理。

什麼是反壓?

反壓是指對管道中流體的抵禦或反向作用力,導致喪失摩擦力和壓力降低。反壓的說法不太恰當,壓力是個標量,有大小,但沒有方向 -- 維基百科
在軟體中,反壓跟這有點關係但也有不同的含義:假設有一個很快的資料傳送方和一個比較慢的資料接收方,反壓是指一種機制可以反向推動傳送方不要把接收方壓垮。

無論是reactivestreams.org或java.until.concurrent.Flow,反應流都提供以下四個構建塊

  • Publisher傳送元素
  • Subscriber對收到的元素產生反應
  • 一個Subscription來繫結Publisher和Subscriber
  • 一個Processor
    這是類圖:

8efe4a3fda4717284296805e5977286d.jpeg

Subscription的request()方法是反壓的頂層。
規範很直白:

Subscriber必須通過Subscription.request(long n)來傳送需求訊號後接收onNext訊號。這裡隱含的規則就是由Subscriber決定什麼時候和有多少元素需要被接收。為了避免可重入Subscription方法引起的訊號重排序,強烈推薦Subscriber方法的實現在呼叫Subscription方法的最後對任何訊號處理都是用同步的方式。推薦Subscriber請求它們可以處理的上限,因為一次只請求一個元素會導致低效的“停止和等待”協議。 -- JVM的Reactive流規範

響應流的規範很標準。它們也有基於Java的TCK。

但要定義如何管理producer傳送下游無法處理的元素就超出這個規範的範圍了。問題比較簡單,解決方法也多。每種Reactive框架都有提供方案,我們來看下。

RxJava 3的反壓

RxJava v3提供以下基礎類:

描述
Flowable0到N號元素的流。支援Reactive-流和反壓
Observable0到N元素的流。不支援反壓
Single一個精確的流: 1個元素或一個錯誤
Maybe一個包括以下的流: 沒有元素 一個元素 或一個錯誤
Completable一個流沒有元素但: 是一個completion結束或一個錯誤的訊號

在這些類中,Flowable是唯一實現了Reactive流-反壓的流。因此,提供反壓不是唯一的問題。RxJava wiki指出:

反壓並沒有解決Observable過度生成或Subscriber過度消費。它只是將這個問題從處理的鏈條中移動到了一個比較好處理的地方。 --響應式進行反壓不是萬金油。

為了解決這個,RxJava提供處理“過度生產“元素的兩個主要策略:

  • 將元素儲存到一個快取裡,如果沒有足夠的快取,可能會產生OutOfMemoryError。
  • 丟掉資料
    下圖描述了這些策略的不同實現方法:

aa5d2392aa01d082f792af66887abb1f.jpeg

記住onBackPressureLatest操作同使用onBackpressureBuffer(1)類似:

這張圖來自RxJava的Wiki。

與其他框架不同的是,RxJava提供方法來在傳送完所有元素後傳送溢位異常訊號。這讓消費者可以收到資料而同時清楚傳送方已經丟了資料。

Project Reactor中的反壓

Project Reactor中提供的策略與RxJava類似。

API有點不一樣。比如,如果生產者溢位Project Reactor提供一個方便的方法來拋異常:

var stream = Stream.generate(Math::random);

// RxJava
Flowable.fromStream(stream) // 1
.onBackpressureBuffer(0); // 2

// Project Reactor
Flux.fromStream(stream) // 1
.onBackpressureError(); // 2

  • 建立Reactive流
  • 如果生產者溢位拋異常

下面是高亮了反壓能力的Flux類圖:

45d029e6310717195f1425f800fe0eaf.jpeg

與其他框架相比,Project Reactor提供設定快取TTL的方法來防止溢位。

協程中的反壓

協程提供同樣的快取和失效能力。協程的基礎類是Flow。

bd74a8c05f5372cca4d7cef0320dc4d4.jpeg

你可以這樣使用:

flow { // 1
while (true) emit(Math.random()) // 2
}.buffer(10)
  • 建一個Flow類,由下面定義content
  • 定義Flow的內容
  • 設定快取容量為10

結論

RxJava,Project Reactor,Kotlin協程都提供反壓能力。在生產者比消費者更快時提供兩種策略:快取資料或拋棄資料。

更多:

Reactive Streams JVM specifications
https://github.com/reactive-s...
How (not) to use Reactive Streams in Java 9+
https://blog.softwaremill.com...
RxJava Backpressure
https://github.com/ReactiveX/...


本文來自祝坤榮(時序)的微信公眾號「麥芽麵包」,公眾號id「darkjune\_think」

開發者/科幻愛好者/硬核主機玩家/業餘翻譯
轉載請註明。

B站: https://space.bilibili.com/23...
交流Email: zhukunrong@yeah.net

相關文章