Umgang mit Rückdruck mit RxJava

Umgang mit Gegendruck mit RxJava

1. Überblick

In diesem Artikel werden wir untersuchen, wieRxJava library uns hilft, mit Gegendruck umzugehen.

Einfach ausgedrückt: RxJava verwendet ein Konzept reaktiver Ströme, indemObservables, eingeführt werden, die ein oder mehrereObservers abonnieren können. Dealing with possibly infinite streams is very challenging, as we need to face a problem of a backpressure.

Es ist nicht schwierig, in eine Situation zu geraten, in der einObservable Elemente schneller ausgibt, als ein Abonnent sie verbrauchen kann. Wir werden uns die verschiedenen Lösungen für das Problem des wachsenden Puffers an nicht verbrauchten Artikeln ansehen.

2. HeißObservables versus kaltObservables

Erstellen wir zunächst eine einfache Consumer-Funktion, die als Consumer für Elemente ausObservables verwendet wird, die wir später definieren werden:

public class ComputeFunction {
    public static void compute(Integer v) {
        try {
            System.out.println("compute integer v: " + v);
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Unserecompute()-Funktion druckt einfach das Argument. Das Wichtigste dabei ist das Aufrufen einerThread.sleep(1000)-Methode. Wir emulieren damit eine lange laufende Aufgabe, die dazu führt, dassObservable schneller mit Elementen gefüllt werden, dieObserver schneller verbrauchen können Sie.

Wir haben zwei Arten vonObservables – Hot undCold - die sich beim Umgang mit Gegendruck völlig unterscheiden.

2.1. KalteObservables

Ein kaltesObservable sendet eine bestimmte Folge von Elementen aus, kann jedoch mit dem Ausgeben dieser Folge beginnen, wenn seinObserver dies für zweckmäßig hält und mit welcher Geschwindigkeit auch immer dasObserveres wünscht, ohne die Integrität des zu stören Reihenfolge. Cold Observable is providing items in a lazy way.

DasObserver nimmt Elemente nur dann auf, wenn es bereit ist, dieses Element zu verarbeiten, und Elemente müssen nicht inObservable gepuffert werden, da sie auf Pull-Weise angefordert werden.

Wenn Sie beispielsweise einObservable basierend auf einem statischen Bereich von Elementen von einer bis einer Million erstellen, gibt diesesObservable dieselbe Folge von Elementen aus, unabhängig davon, wie häufig diese Elemente beobachtet werden:

Observable.range(1, 1_000_000)
  .observeOn(Schedulers.computation())
  .subscribe(ComputeFunction::compute);

Wenn wir unser Programm starten, werden die Elemente vonObserver träge berechnet und auf Pull-Weise angefordert. Die MethodeSchedulers.computation() bedeutet, dass wir unsereObserver in einem Berechnungsthreadpool inRxJava. ausführen möchten

Die Ausgabe eines Programms besteht aus einem Ergebnis einercompute()-Methode, die für ein einzelnes Element von einemObservable aufgerufen wird:

compute integer v: 1
compute integer v: 2
compute integer v: 3
compute integer v: 4
...

KalteObservablesmüssen keinen Gegendruck haben, da sie im Pull-Modus arbeiten. Beispiele für Elemente, die von einem kaltenObservableausgegeben werden, können die Ergebnisse einer Datenbankabfrage, des Abrufs von Dateien oder einer Webanforderung sein.

2.2. HeißeObservables

Ein heißesObservable beginnt mit der Generierung von Elementen und gibt diese sofort aus, wenn sie erstellt werden. Dies steht im Gegensatz zu einem ColdObservables-Pull-Verarbeitungsmodell. Hot Observable emits items at its own pace, and it is up to its observers to keep up.

Wenn dieObserver nicht in der Lage sind, Elemente so schnell zu verbrauchen, wie sie vonObservable produziert werden, müssen sie gepuffert oder auf andere Weise behandelt werden, da sie den Speicher füllen und schließlichOutOfMemoryException.

Betrachten wir ein Beispiel für heißeObservable,, die eine Million Artikel an einen Endverbraucher produzieren, der diese Artikel verarbeitet. Wenn einecompute()-Methode inObserver einige Zeit benötigt, um jedes Element zu verarbeiten, fülltObservable einen Speicher mit Elementen, was dazu führt, dass ein Programm fehlschlägt:

PublishSubject source = PublishSubject.create();

source.observeOn(Schedulers.computation())
  .subscribe(ComputeFunction::compute, Throwable::printStackTrace);

IntStream.range(1, 1_000_000).forEach(source::onNext);

Das Ausführen dieses Programms schlägt mitMissingBackpressureException fehl, da wir keine Methode zur Behandlung der Überproduktion vonObservable definiert haben.

Beispiele für Elemente, die von einem heißenObservableausgegeben werden, können Maus- und Tastaturereignisse, Systemereignisse oder Aktienkurse sein.

3. Pufferung ÜberproduktionObservable

Die erste Möglichkeit, mit der Überproduktion vonObservable umzugehen, besteht darin, eine Art Puffer für Elemente zu definieren, die vonObserver. nicht verarbeitet werden können

Wir können dies tun, indem wir die Methodebuffer()aufrufen:

PublishSubject source = PublishSubject.create();

source.buffer(1024)
  .observeOn(Schedulers.computation())
  .subscribe(ComputeFunction::compute, Throwable::printStackTrace);

Das Definieren eines Puffers mit einer Größe von 1024 gibtObserver etwas Zeit, um eine überproduzierende Quelle einzuholen. Der Puffer speichert Elemente, die noch nicht verarbeitet wurden.

Wir können eine Puffergröße erhöhen, um genügend Platz für produzierte Werte zu haben.

Beachten Sie jedoch, dass im Allgemeinenthis may be only a temporary fix als Überlauf auftreten kann, wenn die Quelle die vorhergesagte Puffergröße überproduziert.

4. Versendete Gegenstände stapeln

Wir können überproduzierte Artikel in Fenstern mit N Elementen stapeln.

WennObservable Elemente schneller produziert, alsObserver sie verarbeiten kann, können wir dies verringern, indem wir produzierte Elemente zusammenfassen und einen Stapel von Elementen anObserver senden, die eine Sammlung von Elementen verarbeiten können anstelle von Element eins nach dem anderen:

PublishSubject source = PublishSubject.create();

source.window(500)
  .observeOn(Schedulers.computation())
  .subscribe(ComputeFunction::compute, Throwable::printStackTrace);

Wenn Sie die Methodewindow() mit dem Argument500, verwenden, werdenObservable angewiesen, Elemente in Stapeln der Größe 500 zu gruppieren. Diese Technik kann das Problem der Überproduktion vonObservable verringern, wennObserver in der Lage ist, eine Charge von Elementen schneller zu verarbeiten als Elemente nacheinander zu verarbeiten.

5. Elemente überspringen

Wenn einige der vonObservable erzeugten Werte sicher ignoriert werden können, können wir die Abtastung innerhalb einer bestimmten Zeit und Drosseloperatoren verwenden.

Die Methodensample() undthrottleFirst() nehmen die Dauer als Parameter:

  • Die Methode sample() untersucht regelmäßig die Reihenfolge der Elemente und gibt das letzte Element aus, das innerhalb der als Parameter angegebenen Dauer erstellt wurde

  • Die MethodethrottleFirst() gibt das erste Element aus, das nach der als Parameter angegebenen Dauer erstellt wurde

Die Dauer ist eine Zeit, nach der ein bestimmtes Element aus der Folge der produzierten Elemente ausgewählt wird. Wir können eine Strategie für den Umgang mit Gegendruck festlegen, indem wir Elemente überspringen:

PublishSubject source = PublishSubject.create();

source.sample(100, TimeUnit.MILLISECONDS)
  .observeOn(Schedulers.computation())
  .subscribe(ComputeFunction::compute, Throwable::printStackTrace);

Wir haben angegeben, dass die Strategie zum Überspringen von Elementen einesample()-Methode ist. Wir wollen ein Muster einer Sequenz mit einer Dauer von 100 Millisekunden. Dieses Element wird anObserver. ausgegeben

Beachten Sie jedoch, dass diese Operatoren nur die Rate des Wertempfangs um die nachgeschaltetenObserver reduzieren und daher immer noch zuMissingBackpressureException führen können.

6. Behandlung eines Füllpuffers inObservable

Für den Fall, dass unsere Strategien zum Abtasten oder Stapeln von Elementen beim Auffüllen eines Puffers, nicht helfen, müssen wir eine Strategie zum Behandeln von Fällen implementieren, in denen ein Puffer aufgefüllt wird.

Wir müssen eineonBackpressureBuffer()-Methode verwenden, umBufferOverflowException. zu verhindern

DieonBackpressureBuffer()-Methode verwendet drei Argumente: eine Kapazität einesObservable-Puffers, eine Methode, die beim Auffüllen eines Puffers aufgerufen wird, und eine Strategie zum Behandeln von Elementen, die aus einem Puffer verworfen werden müssen. Strategien für den Überlauf gehören zur KlasseBackpressureOverflow.

Es gibt 4 Arten von Aktionen, die ausgeführt werden können, wenn der Puffer voll ist:

  • ON_OVERFLOW_ERROR – Dies ist das Standardverhalten, dasBufferOverflowException signalisiert, wenn der Puffer voll ist

  • ON_OVERFLOW_DEFAULT – ist derzeit dasselbe wieON_OVERFLOW_ERROR

  • ON_OVERFLOW_DROP_LATEST - Wenn ein Überlauf auftreten würde, wird der aktuelle Wert einfach ignoriert und nur die alten Werte werden geliefert, sobald der nachgeschalteteObserver dies anfordert

  • ON_OVERFLOW_DROP_OLDEST - löscht das älteste Element im Puffer und addiert den aktuellen Wert

Mal sehen, wie man diese Strategie spezifiziert:

Observable.range(1, 1_000_000)
  .onBackpressureBuffer(16, () -> {}, BackpressureOverflow.ON_OVERFLOW_DROP_OLDEST)
  .observeOn(Schedulers.computation())
  .subscribe(e -> {}, Throwable::printStackTrace);

Hier besteht unsere Strategie zur Behandlung des überlaufenden Puffers darin, das älteste Element in einem Puffer zu löschen und das neueste Element hinzuzufügen, das von einemObservable erzeugt wird.

Beachten Sie, dass die letzten beiden Strategien eine Diskontinuität im Stream verursachen, wenn sie Elemente entfernen. Außerdem signalisieren sie nichtBufferOverflowException.

7. Alle überproduzierten Elemente löschen

Immer wenn der nachgeschalteteObserver nicht bereit ist, ein Element zu empfangen, können wir eineonBackpressureDrop()-Methode verwenden, um dieses Element aus der Sequenz zu entfernen.

Wir können uns diese Methode alsonBackpressureBuffer()-Methode mit einer Kapazität eines Puffers vorstellen, der mit einer StrategieON_OVERFLOW_DROP_LATEST. auf Null gesetzt ist

Dieser Operator ist nützlich, wenn wir Werte aus einer QuelleObservable (wie Mausbewegungen oder aktuelle GPS-Positionssignale) ignorieren können, da es später aktuellere Werte geben wird:

Observable.range(1, 1_000_000)
  .onBackpressureDrop()
  .observeOn(Schedulers.computation())
  .doOnNext(ComputeFunction::compute)
  .subscribe(v -> {}, Throwable::printStackTrace);

Die MethodeonBackpressureDrop() beseitigt das Problem der Überproduktion vonObservable, muss jedoch mit Vorsicht angewendet werden.

8. Fazit

In diesem Artikel haben wir uns mit dem Problem der Überproduktion vonObservable und dem Umgang mit einem Gegendruck befasst. Wir haben Strategien zum Puffern, Stapeln und Überspringen von Elementen untersucht, bei denenObserver Elemente nicht so schnell verbrauchen kann, wie sie vonObservable. erzeugt werden

Die Implementierung all dieser Beispiele und Codefragmente finden Sie inGitHub project - dies ist ein Maven-Projekt, daher sollte es einfach zu importieren und auszuführen sein, wie es ist.