JDK9 New Feature Reactive Stream Responsive Stream

Keywords: Programming Java WebFlux Lombok Spring

_This article focuses on the JDK9 feature Reactive Stream Responsive Stream, describes what the Reactive Stream is and what the backpressure is, and the interface and two use cases for Reactive Stream provided in JDK9, including how to use Processor.

_1.Reactive Stream concept

_Reactive Stream is a set of standards introduced by JDK9 and a set of data processing specifications based on publish/subscribe mode.Responsive streaming has been an initiative since 2013 to provide an asynchronous stream processing standard for non-blocking backpressure.It is designed to solve the problem of handling element streams -- how to pass element streams from the publisher to the subscriber without blocking the publisher or having the subscriber have an unlimited buffer or discard.More precisely, Reactive streams are designed to "find the smallest set of interfaces, methods, and protocols that describe the operations and entities necessary to achieve the goal of asynchronous streaming of data in a non-blocking backpressure manner".

_Reactive Stream specification was born, which defines the following four interfaces:

The Subscription interface defines how publishers and subscribers are connected
 The Publisher<T>interface defines the publisher's method
 The Subscriber<T>interface defines the method of the subscriber
 Processor<T,R>Interfaces define processors

_Since the Reactive Stream specification was born, RxJava has implemented the Reactive Stream specification since RxJava 2, and the Reactor framework provided by Spring (the basis of WebFlux) has also implemented the Reactive Stream specification successively.

_The following diagram shows the interaction between subscribers and publishers

_2. back pressure concept

_If producers send more messages than consumers can handle, consumers may be forced to keep catching them, consuming more and more resources and burying the potential risk of collapse.To prevent this, a mechanism is needed that allows consumers to notify producers and slow down message generation.Producers can adopt a variety of strategies to achieve this requirement, a mechanism called backpressure.

_Simply put, it is

  • Backpressure refers to the interaction between publishers and subscribers
  • Subscribers can tell the publisher how much data they need, adjust data traffic, and not cause the publisher to publish too much data, which can waste data or overwhelm subscribers

_3. Implementation of Reactive Stream specification in JDK9

_The implementation specification for Reactive Stream in JDK9 is often referred to as the Flow API, which implements responsive streaming through the java.util.concurrent.Flow and java.util.concurrent.SubmissionPublisher classes

_In JDK9, Reactive Stream's primary interface declaration In the Flow class, the Flow class defines four nested static interfaces that are used to build components of traffic control in which the publisher generates one or more data items for subscribers:

  • Publisher: Publisher, Producer of Data Items
  • Subscriber: Data item subscribers, consumers
  • Subscription: The relationship between publisher and subscriber, subscription token
  • Processor: Data Processor

_3.1 Publisher

Publisher publishes data streams to registered Subscriber s.It typically publishes items to subscribers asynchronously using Executor.Publisher needs to ensure that the ubscriber method for each subscription is called strictly in sequence.

  • subscribe: subscriber subscribes to publisher

      @FunctionalInterface 
      public static interface Flow.Publisher<T> { 
        public void subscribe(Subscriber<? super T> subscriber); 
      }
    

_3.2 Subscriber

Subscriber subscribes to Publisher's streams and accepts callbacks.If Subscriber does not make a request, it will not receive data.For a given Subscription, the method of calling Subscriber is strictly sequential.

  • onSubscribe: The publisher calls the subscriber's method to deliver the subscription asynchronously, which is executed after the publisher.subscribe method is called
  • onNext: The publisher calls this method to pass data to the subscriber
  • onError: Call this method when Publisher or Subscriber encounters an unrecoverable error, and then no other method is called
  • onComplete: Call this method when the data has been sent and no errors have caused the subscription to terminate, then no other methods will be called

_3.3 Subscription Contract Subscription

Subscription is used to connect Publisher to Subscriber.Subscriber receives items only when requested and can unsubscribe through Subscription.Subscription has two main methods:

  • Request: Subscribers call this method to request data

  • cancel: Subscribers invoke this method to unsubscribe and dissociate subscribers from publishers

      public static interface Flow.Subscription {
        public void request(long n);
        public void cancel();
      }
    

_3.4 Processor

_Processor is located between Publisher and Subscriber for data conversion.Multiple Processors can be used together to form a processing chain in which the results of the last processor are sent to Subscriber.The JDK does not provide any specific processors.Processors are both subscribers and publishers, and interface definitions inherit both as subscribers and as publishers, receiving data as subscribers, processing it, and publishing it as publishers.

/**
 * A component that acts as both a Subscriber and Publisher.
 *
 * [@param](https://my.oschina.net/u/2303379) <T> the subscribed item type
 * [@param](https://my.oschina.net/u/2303379) <R> the published item type
 */
public static interface Processor<T,R> extends Subscriber<T>, Publisher<R> {
}

_4. Reactive Stream (Flow API) specification call flow in JDK9

_Publisher is the publisher who can emit elements, and Subscriber is the subscriber who receives elements and responds.When the subscribe method in Publisher is executed, the publisher calls back the subscriber's onSubscribe method, in which the subscriber usually requests n data from the publisher with the help of an incoming Subscription.The publisher then sends out up to N data to the subscriber by constantly calling the subscriber's onNext method.If all the data is sent out, onComplete is called to inform subscribers that the stream has been sent out; if an error occurs, error data is sent through onError and the stream is terminated as well.

_Subscription is the "link" (contract) between Publisher and Subscriber.Because when the publisher calls the subscribe method to register the subscriber, the Subscription object is passed in through the subscriber's callback method onSubscribe, and the subscriber can then "ask" the publisher for data using the request method of the Subscription object.The mechanism of back pressure is based on this.

_5. Case-Response Basic Use Cases

_5.1 The following code briefly demonstrates SubmissionPublisher and the basic usage of this publish-subscribe framework:

_Note to use versions above JDK9

/**
* [@author](https://my.oschina.net/arthor) johnny
* [@create](https://my.oschina.net/u/192469) 2020-02-24 5:44 p.m.
**/
@Slf4j
public class ReactiveStreamTest {


public static void main(String[] args) throws InterruptedException {


    //1. Create Producer Publisher JDK9's own implementation of the Publisher interface
    SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();

    //2. Creating Subscriber requires implementing internal methods on your own

    Flow.Subscriber<Integer> subscriber = new Flow.Subscriber<>() {

        private Flow.Subscription subscription;

        @Override
        public void onSubscribe(Flow.Subscription subscription) {
            this.subscription = subscription;
            System.out.println("Subscription succeeded.");
            subscription.request(1);
            System.out.println("Request a data in a subscription method");
        }

        @Override
        public void onNext(Integer item) {
            log.info("[onNext Receive data item : {}] ", item);
            subscription.request(1);
        }

        @Override
        public void onError(Throwable throwable) {
            log.info("[onError Exception occurred)");
            subscription.cancel();
        }

        @Override
        public void onComplete() {
            log.info("[onComplete All data receipt completed)");
        }
    };

    //3.Publishers and subscribers establish a subscription relationship by calling back the subscriber's onSubscribe method to pass in the subscription contract
    publisher.subscribe(subscriber);


    //4. Publisher Generates Data
    for (int i = 1; i <= 5; i++) {
        log.info("[production data {} ]", i );
        //submit is a blocking method, which calls the subscriber's onNext method
        publisher.submit(i);
    }


    //5. When publisher data has been published, close the send and the subscriber's onComplete method will be callback
    publisher.close();

    //Main thread sleeps for a while
    Thread.currentThread().join(100000);


  }
}

_Print Output Results

_It looks like we can't see what Reactive Stream does, but the key point is publisher.submit(i); submit is a blocking method Let's modify the code a little

1. Add time-consuming operations to onNext to simulate business time-consuming logic 2. Increase the number of publishers publishing data to simulate real-world unlimited data

    @Override
        public void onNext(Integer item) {
            log.info("[onNext Receive data item : {}] ", item);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            subscription.request(1);
        }

    //Publisher Generates Data
    for (int i = 1; i <= 1000; i++) {
        log.info("[production data {} ]", i );
        //submit is a blocking method, which calls the subscriber's onNext method
        publisher.submit(i);
    }

_Direct-view printing

_You will find that the publisher will stop production after generating data for 256 because the publisher.submit(i) method is blocked. There is an internal buffer array with a maximum capacity of 256, only if the subscription.request (1) is sent by the subscriber;After a request, the onNext method takes the data out of the buffer array sequentially and passes it to the subscriber for processing. When the subscription.request(1) method is called, the publisher discovers that the array is not full to reproduce the data, which prevents the producer from generating too much data at once and crushing the subscriber, thereby implementing a backpressure mechanism

_6. Case 2 Responsive Use Case with Processor

_6.1 Create Custom Processor

package com.johnny.webflux.webfluxlearn.reactivestream;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;

/**
* Custom Processor
*
* @author johnny
* @create 2020-02-25 1:56 p.m.
**/
@Slf4j
public class MyProcessor extends SubmissionPublisher<Integer> implements Flow.Processor<Integer, Integer> {

private Flow.Subscription subscription;

@Override
public void onSubscribe(Flow.Subscription subscription) {
    log.info("[Processor Receive Subscription Request)");
    //Save the subscription relationship and use it to correspond to the publisher
    this.subscription = subscription;

    this.subscription.request(1);
}

@Override
public void onNext(Integer item) {
    log.info("[onNext Receive publisher data  : {} ]", item);

    //Do business processing.
    if (item % 2 == 0) {
        //Filter even numbers sent to subscribers
        this.submit(item);
    }
    this.subscription.request(1);
}

@Override
public void onError(Throwable throwable) {
    // We can tell the publisher that we won't accept the data later
    this.subscription.cancel();
 }

@Override
public void onComplete() {
    log.info("[Processor Finished)");
    this.close();
 }
}

_6.2 Run demo to associate publisher with Processor and subscriber

package com.johnny.webflux.webfluxlearn.reactivestream;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Flow;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.TimeUnit;

/**
* Case with Processor
*
* @author johnny
* @create 2020-02-25 2:17 p.m.
**/
@Slf4j
public class ProcessorDemo {

public static void main(String[] args) throws InterruptedException {


    //Create Publisher
    SubmissionPublisher<Integer> publisher = new SubmissionPublisher<>();


    //Creating a Processor is both a publisher and a subscriber
    MyProcessor myProcessor = new MyProcessor();


    //Create Final Subscriber
    Flow.Subscriber<Integer> subscriber = new Flow.Subscriber<>() {

        private Flow.Subscription subscription;

        @Override
        public void onSubscribe(Flow.Subscription subscription) {
            this.subscription = subscription;
            this.subscription.request(1);
        }

        @Override
        public void onNext(Integer item) {
            log.info("[onNext from Processor Receive filtered data item : {}] ", item);
            this.subscription.request(1);
        }

        @Override
        public void onError(Throwable throwable) {
            log.info("[onError Exception occurred)");
            subscription.cancel();
        }

        @Override
        public void onComplete() {
            log.info("[onComplete All data receipt completed)");
        }
    };

    //Establish a relationship between the publisher and the processor, where the processor acts as a subscriber
    publisher.subscribe(myProcessor);

    //Establish a relationship between the processor and the subscriber where the processor acts
    myProcessor.subscribe(subscriber);

    //Publisher Publishes Data

    publisher.submit(1);
    publisher.submit(2);
    publisher.submit(3);
    publisher.submit(4);

    publisher.close();

    TimeUnit.SECONDS.sleep(2);

  }
}

_7. Summary

_This article focuses on the JDK9 feature Reactive Stream Responsive Stream, describes what the Reactive Stream is and what the backpressure is, and the interface and two use cases for Reactive Stream provided in JDK9, including how to use Processor.

_Just pay attention to the four interfaces provided by JDK9 and the internal methods. Knock through the code for the case is actually a very simple process to refuel!!!

Personal blogging system: https://www.askajohnny.com Welcome! This article is published by blog OpenWrite Release!

Posted by simjay on Fri, 28 Feb 2020 18:03:33 -0800