I only intend to introduce the new Http Client of JDK 11, but I encountered the Flow API responsive flow, so I had to separate this part and briefly introduce it.
[TOC]
Introducing Reactive Streams
Reactive Stream Reactive stream or reactive stream, the word I encountered in the introduction of HttpClient in JDK 11:
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://openjdk.java.net/"))
.POST(HttpRequest.BodyPublishers.ofString("aaaa"))
.build();
// BodyHandlers.fromLineSubscriber要求的参数是Subscriber类实例
// 然后我点了点发现Subscriber位于Flow类内是一个静态接口
client.sendAsync(request, HttpResponse.BodyHandlers.fromLineSubscriber())
Looking up, I found that this Flow is from the hands of Doug Lea, and since 9 is written on it, which means that this class entered the JDK after JDK 9.
Doug Lea's comments have always been more comments than code. Let's look at the comments first and see what is the purpose of introducing this Flow class?
Interrelated interfaces and static methods for establishing flow-controlled components in which {@link Publisher Publishers} produce items consumed by one or more {@link Subscriber Subscribers}, each managed by a {@link Subscription Subscription}.
These interfaces and static methods are designed to establish a flow control component of the publish-subscriber pattern (Publisher publishes one or more Subscriber subscribers for consumption, and each subscriber is managed by Subscription).<p>These interfaces correspond to the reactive-streams
specification. They apply in both concurrent and distributed
asynchronous settings: All (seven) methods are defined in {@code
void} "one-way" message style. Communication relies on a simple form
of flow control (method {@link Subscription#request}) that can be
used to avoid resource management problems that may otherwise occur
in "push" based systems.
These interfaces follow the Reactive Streams specification, and they are used in concurrent and distributed asynchronous settings: all seven methods are defined in the style of one-way messages returning void.
Message exchanges relying on simple flow control (Subscription's request method) can be used to avoid some of the resource management problems of push-based systems.
What is this response stream specification? I opened this link in the href to check it out.
Why Introduce the Reactive Streams Specification
Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure. This encompasses efforts aimed at runtime environments (JVM and JavaScript) as well as network protocols.
Responsive Streaming An initiative to provide a standard for asynchronous stream processing with non-blocking back pressure, this includes work on the JVM runtime environment, JavaScript, network protocols.
Handling streams of data—especially “live” data whose volume is not predetermined—requires special care in an asynchronous system. The most prominent issue is that resource consumption needs to be controlled such that a fast data source does not overwhelm the stream destination. Asynchrony is needed in order to enable the parallel use of computing resources, on collaborating network hosts or multiple CPU cores within a single machine.
In asynchronous systems, special care should be taken when dealing with data streams, especially implementations where the amount of data is not predetermined. The most prominent and common problem is the problem of resource consumption control, in order to prevent the rapid arrival of large amounts of data to overwhelm the destination.
In order for a networked computer or a multi-core CPU in a computer to use parallel mode when performing computing tasks, we need asynchrony.
The main goal of Reactive Streams is to govern the exchange of stream data across an asynchronous boundary—think passing elements on to another thread or thread-pool—while ensuring that the receiving side is not forced to buffer arbitrary amounts of data. In other words , back pressure is an integral part of this model in order to allow the queues which mediate between threads to be bounded. The benefits of asynchronous processing would be negated if the communication of back pressure were synchronous (see also the Reactive Manifesto), therefore care has to be taken to mandate fully non-blocking and asynchronous behavior of all aspects of a Reactive Streams implementation.
The main goal of the response stream is to control the exchange of data across asynchronous boundaries, that is, to pass an element to a thread or threads outside of it, to ensure that the receiver is not forced to buffer an arbitrary amount of data. In other words, backpressure is an important component of the model that allows queues between threads to be bounded. If the communication using back pressure is synchronous, then asynchronous processing will be negated (see the Reactive Manifesto for details). Therefore it must be required that all reactive streams implementations be asynchronous and non-blocking.
It is the intention of this specification to allow the creation of many conforming implementations, which by virtue of abiding by the rules will be able to interoperate smoothly, preserving the aforementioned benefits and characteristics across the whole processing graph of a stream application.
Implementations that adhere to this specification can interoperate to benefit from the processing of the entire streaming application.
The official release time of JDK 9 is September 2017. If you search for Reactive Manifesto, you will find that this manifesto was released on September 16, 2014. This is a programming concept, corresponding to reactive programming, the same as object-oriented programming. , functional programming, is an idea. The introduction of the specification is to constrain its implementation and avoid each library having its own set of reactive implementations, which is a headache for developers. The proposal of reactive programming as shown above is mainly to solve the back pressure phenomenon of asynchronous data processing, what is back pressure.
Explanation of back pressure
Backpressure is not a unique concept of reactive programming. Backpressure in English is BackPressure. It is not a mechanism or a strategy, but a phenomenon: in the process of data flow transmission from upstream producers to downstream consumers , the upstream production speed is greater than the downstream consumption speed, resulting in the overflow of the downstream Buffer. This phenomenon is called Backpressure. The point of back pressure is that the upstream production speed is greater than the downstream consumption speed, but the buffer overflow.
An example is in Java, we submit tasks to the thread pool, and the queue is full to trigger the rejection policy (reject new tasks or discard old ones and process new ones). Writing here, some students may say, can't you use an unbounded queue? What if the submitted tasks keep expanding and your entire system crashes? If the production speed of the upstream system is so fast that it can crash the system, then the buffer limit needs to be set.
sort out
The concept of reactive programming first appeared, then the implementation of reactive programming, and then the specification of responsiveness. The responsive stream mainly solves the problem of processing element streams - how to pass element streams from publishers to subscribers without blocking the publisher. , or require the subscriber to have an infinite buffer. When the finite buffer reaches the upper bound of the buffer, it discards or rejects the arriving elements. The subscriber can asynchronously notify the publisher to reduce or increase the rate of data production and publication. It is Reactive programming implements the core features of effects.
The responsive specification is an initiative. Systems that follow this initiative can allow data to be processed responsively in various responsive systems. The form of the specification in Java is the interface, which is the theme of our article. Flow class , for a standard, its purpose is naturally to describe interactions with fewer protocols. And the reactive streaming model is also very simple:
- The subscriber asynchronously requests N elements from the publisher
- The publisher asynchronously sends ( 0 < M <= N) elements to the subscriber.
Writing here, some students may ask, why don't subscribers want how many elements and publishers how much? This is actually a coordination mechanism. There are two situations in the consumption data that deserve our attention:
- Subscribers consume too fast (in the responsive model, to deal with this situation is to notify the publisher to generate elements faster, similar to going to the bun shop to eat buns, if the customers with a relatively large appetite come, the bun shop will not be able to produce enough, and will tell the bun shop Make it faster, and then continue to eat the buns)
- The publisher publishes too fast (in the responsive model, to deal with this situation is to notify the producer to reduce the production rate, or to go to the bun store to eat buns, although the customer eats a lot, but the food is relatively slow, and they can't put it down soon, just I will tell the bun shop to make it slower)
A general introduction to Flow
Flow is a class modified by the final keyword, which contains several groups of public static interfaces and the length of buffer variables:
- Publisher
- Subscriber Subscriber
- Subscription subscription letter (or subscription token), through this instance, is used to coordinate the number of request elements and the number of request subscription elements between subscribers and publishers
- Processor inherits Publisher and Subscriber and is used to connect Publisher and Subscriber, and can also connect to other processors
Simple example
public class FlowDemo {
static class SampleSubscriber<T> implements Flow.Subscriber<T> {
final Consumer<? super T> consumer;
Flow.Subscription subscription;
SampleSubscriber(Consumer<? super T> consumer) {
this.bufferSize = bufferSize;
this.consumer = consumer;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
System.out.println("建立订阅关系");
this.subscription = subscription; // 赋值
subscription.request(2);
}
public void onNext(T item) {
System.out.println("收到发送者的消息"+ item);
consumer.accept(item);
// 可调用 subscription.request 接着请求发布者发消息
// subscription.request(1);
}
public void onError(Throwable ex) { ex.printStackTrace(); }
public void onComplete() {}
}
public static void main(String[] args) {
SampleSubscriber subscriber = new SampleSubscriber<>(200L,o->{
System.out.println("hello ....." + o);
});
ExecutorService executor = Executors.newFixedThreadPool(1);
SubmissionPublisher<Boolean> submissionPublisher = new SubmissionPublisher(executor,Flow.defaultBufferSize());
submissionPublisher.subscribe(subscriber);
submissionPublisher.submit(true);
submissionPublisher.submit(true);
submissionPublisher.submit(true);
executor.shutdown();
}
}
Output result:
Why did the publisher publish three messages, and your subscriber only processed two, because the subscriber explained to the publisher when the subscription relationship was established, I only need two messages, the current consumption power is insufficient, after consumption, You can also request the publisher to send again.
Let's demonstrate the effect of back pressure. Our task of setting the size of the buffer pool is the default value defined by Flow, 256. Let's try submitting 1000 tasks now:
public class FlowDemo {
static class SampleSubscriber<T> implements Flow.Subscriber<T> {
final Consumer<? super T> consumer;
Flow.Subscription subscription;
SampleSubscriber(Consumer<? super T> consumer) {
this.consumer = consumer;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
System.out.println("建立订阅关系");
this.subscription = subscription; // 赋值
subscription.request(1);
}
public void onNext(T item) {
try {
System.out.println("thread name 0"+Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("收到发送者的消息"+ item);
consumer.accept(item);
// 可调用 subscription.request 接着请求发布者发消息
subscription.request(1);
}
public void onError(Throwable ex) { ex.printStackTrace(); }
public void onComplete() {}
}
public static void main(String[] args) {
SampleSubscriber subscriber = new SampleSubscriber<>(o->{
System.out.println("hello ....." + o);
});
ExecutorService executor = Executors.newFixedThreadPool(1);
SubmissionPublisher<Boolean> submissionPublisher = new SubmissionPublisher(executor,Flow.defaultBufferSize());
submissionPublisher.subscribe(subscriber);
for (int i = 0; i < 1000; i++) {
System.out.println("开始发布第"+i+"条消息");
submissionPublisher.submit(true);
System.out.println("开始发布第"+i+"条消息发布完毕");
}
executor.shutdown();
}
}
Why is it blocked until the 257th, it is because the buffer is full, and the buffer is free before it is allowed to continue production.
public class MyProcessor extends SubmissionPublisher<Boolean> implements Flow.Processor<Boolean, Boolean> {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
this.subscription.request(1);
}
@Override
public void onNext(Boolean item) {
if (item){
item = false;
// 处理器将此条信息转发
this.submit(item);
System.out.println("将true 转换为false");
}
subscription.request(1);
}
@Override
public void onError(Throwable throwable) {
throwable.printStackTrace();
this.subscription.cancel();
}
@Override
public void onComplete() {
System.out.println("处理器处理完毕");
this.close();
}
}
public class FlowDemo {
static class SampleSubscriber<T> implements Flow.Subscriber<T> {
final Consumer<? super T> consumer;
Flow.Subscription subscription;
SampleSubscriber(Consumer<? super T> consumer) {
this.consumer = consumer;
}
@Override
public void onSubscribe(Flow.Subscription subscription) {
System.out.println("建立订阅关系");
this.subscription = subscription; // 赋值
subscription.request(1);
}
public void onNext(T item) {
System.out.println("收到发送者的消息"+ item);
consumer.accept(item);
// 可调用 subscription.request 接着请求发布者发消息
subscription.request(1);
}
public void onError(Throwable ex) { ex.printStackTrace(); }
public void onComplete() {}
}
public static void main(String[] args) throws Exception{
SampleSubscriber subscriber = new SampleSubscriber<>(o->{
System.out.println("hello ....." + o);
});
ExecutorService executor = Executors.newFixedThreadPool(1);
SubmissionPublisher<Boolean> submissionPublisher = new SubmissionPublisher(executor,Flow.defaultBufferSize());
MyProcessor myProcessor = new MyProcessor();
// 做信息转发
submissionPublisher.subscribe(myProcessor);
myProcessor.subscribe(subscriber);
for (int i = 0; i < 2; i++) {
System.out.println("开始发布第"+i+"条消息");
submissionPublisher.submit(true);
System.out.println("开始发布第"+i+"条消息发布完毕");
}
TimeUnit.SECONDS.sleep(2);
executor.shutdown();
}
}
Output result:
in conclusion
We saw the Flow API from the request parameters of the HTTP Client of JDK 11, the Reactive Stream in the comments in the Flow class, and the reactive specification from the Reactive Stream. Subscriber, publisher publishes too fast, subscriber asks publisher to slow down production, production is too slow, subscriber asks publisher to speed up. There are already some implementations of reactive in the Java field:
- RXJava is the Java implementation in the ReactiveX project, Rxjava is earlier than the Reactive Streams specification, and RXJava 2.0+ does implement the Reactive Streams API specification.
- Reactor is a Java implementation provided by Pivotal. As an important part of Spring Framework 5, it is the default reactive framework adopted by WebFlux.
- Akka Streams fully implements the Reactive Streams specification, but the Akka Streams API is completely separate from the Reactive Streams API.
In order to unify the specification, JDK 9 introduces Flow, which is similar to JDBC and belongs to the API specification. In actual use, the specific implementation corresponding to the API needs to be used. Reactive Streams provides us with an interface that we can code without caring about its implementation.
References
- Introduction to Reactive Streams https://zhuanlan.zhihu.com/p/95966853
- Reactive Streams http://www.reactive-streams.org/
- How to visualize the backpressure mechanism in reactive programming? https://www.zhihu.com/question/49618581/answer/237078934
- Java9 Features - Reactive Stream https://zhuanlan.zhihu.com/p/463579630
- Reactive programming exploration and summary https://developer.aliyun.com/article/728068#slide-13
- Reactive Manifesto https://www.reactivemanifesto.org/en-US
- Java9-Reactive Stream API reactive programming https://zhuanlan.zhihu.com/p/266407815
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。