By Buwen
What value does the Reactive programming model give to us? How does the model work? How can we use it correctly? This article will discuss the concept, specifications, value, and principles of Reactive based on the author's experiences. Please feel free to learn more with us and correct any errors that you find.
Reactive means to exhibit a reaction or response. How this applies to architecture may not be easy to understand at first glance.
Here is an example: In an Excel file, configure the function sum(A+B) in cell C. Then, change the value of cell A or cell B. You will see that the value of cell C also changes. This behavior is reactive.
In the computer programming field, "reactive" generally refers to Reactive programming, an asynchronous programming paradigm that deals with data streams and propagates events.
Let me show you an example first:
public static void main(String[] args) {
FluxProcessor<Integer, Integer> publisher = UnicastProcessor.create();
publisher.doOnNext(event -> System.out.println("receive event: " + event)).subscribe();
publisher.onNext(1); // print 'receive event: 1'
publisher.onNext(2); // print 'receive event: 2'
}
The preceding code uses a Reactor class library as an example. The publisher generates data streams 1 and 2 and propagates the data streams to the OnNext event. In the preceding example, lambda responds to this event and outputs the corresponding information. The generation of data streams and the registration and execution of lambda in the code are performed by the same thread, but could also be performed by different threads.
Note: If you do not understand the execution logic in the preceding code, you can consider lambda to be a callback.
Now, you have a basic understanding of Reactive, but you may still fail to see the value provided by Reactive and its design principles. These questions are answered by the Reactive Manifesto.
A system built based on Reactive has the following features:
The system is responsive whenever possible. Being responsive is the cornerstone of usability and practicality. More importantly, being responsive enables you to quickly detect and effectively handle problems. A responsive system is dedicated to delivering consistent service quality by ensuring a short and consistent response time (RT) and a reliable upper maximum for feedback. This consistent behavior simplifies error handling, earns the trust of end users, and promotes further interactions between users and the system.
The system can remain responsive even when a failure occurs. This feature is not only necessary for highly available and mission-critical systems. Any system that lacks resilience will become unresponsive when a failure occurs. Resilience is implemented through replication, suppression, isolation, and delegation. Specifically, the impact of a failure is suppressed within each component, and components are isolated to ensure that a failure in one part of the system does not adversely affect the entire system and can be fixed independently. The recovery work for each component is delegated to an external component. In addition, high availability can be guaranteed through replication when necessary. This way, the component client no longer needs to handle component failures.
The system can remain responsive as its workloads constantly change. A Reactive system can react to rate changes in input workloads by increasing or reducing the resources allocated to serve these input workloads. This means that no contention points or central bottlenecks are found in the design, allowing you to shard or replicate a component and distribute input workloads among the sharded or replicated components. In addition, a Reactive system supports predictive and reactive scaling algorithms if real-time performance metrics are provided. These systems can achieve cost-effective elasticity based on conventional hardware and software platforms.
Reactive systems depend on asynchronous messaging, which ensures clear boundaries between loosely coupled, isolated, and transparently positioned components. The boundaries also provide a means to delegate a failure as a message. Explicit messaging allows you to implement load management, scaling, and throttling by creating and monitoring message flow queues in the system and implementing backpressure when required. Location-transparent messaging is used as a means of communication, enabling you to manage failures using the same structural components and semantics across clusters or in a single host. Non-blocking communication allows receivers to consume resources only when the receivers are active, which lowers the system overhead.
Note:
After learning about the concept, features, and value of Reactive, you may want to learn about the products or frameworks that can help us build a Reactive system. In earlier times, we could use class libraries, such as RxJava 1.x and Rx.NET. However, these libraries adopted different specifications. After learning from these earlier products, companies, such as Netflix and Pivotal, developed a set of specifications to provide guidance and facilitate the implementation of Reactive. This is what Reactive Streams does.
The purpose of Reactive Streams is to provide a standard for asynchronous stream processing with non-blocking backpressure. Currently, we have implemented specifications with the same set of semantics in JVM and JavaScript. In addition, based on various transmission protocols that involve serialization and deserialization, such as TCP, UDP, HTTP, and WebSockets, we are trying to define a network protocol to transmit reactive data streams.
When a data stream whose volume is not predetermined is received, the system can still ensure high availability while controlling the number of resources consumed.
The main goal of Reactive Streams is to govern the exchange of stream data across an asynchronous boundary. For example, data is passed on to another thread or thread pool to make sure the receiving side does not buffer arbitrary amounts of data. Backpressure is an indispensable feature that resolves this problem.
This standard only describes the necessary operations and entities and the minimum set of APIs needed to asynchronously exchange stream data through backpressure. For example, this standard regulates the publisher and the subscriber, as described in the following sections. Reactive Streams focuses only on transferring stream data between these components, not on assembling, segmenting, and converting the stream data, such as using a map, zip, and other operators. The Reactive Streams specification is listed below:
A publisher generates a data stream, which may contain an infinite volume of data. Subscribers can consume the data as needed.
public interface Publisher<T> {
public void subscribe(Subscriber<? super T> s);
}
A subscriber is the receiver of the elements created by a publisher. Subscribers monitor the specified events, such as OnNext, OnComplete, and OnError events.
publicinterface Subscriber<T> {
public void onSubscribe(Subscription s);
public void onNext(T t);
public void onError(Throwable t);
public void onComplete();
}
Subscription is an object that coordinates a publisher and a subscriber in a one-to-one manner. With subscription, the subscriber can cancel data sent to the publisher or request more data from the publisher.
public interface Subscription {
public void request(long n);
public void cancel();
}
A processor has features from the publisher and the subscriber. In code 1, FluxProcessor can send data, which generates an OnNext event and receive data, which generates a doOnNext event.
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {}
Why does the specification stress the use of non-blocking asynchronous mode instead of the blocking synchronous mode?
The non-blocking mode can be implemented in multiple ways, so why was the preceding implementation chosen?
I think the main implementation approach of Reactive is callback. The main implementation approach of Kotlin coroutines is also callback. However, Reactive and Kotlin coroutines implement callbacks in different ways. Reactive implements callbacks through event propagation while Kotlin coroutines implement callbacks using the state machine. Coroutines are easier to use than Rx in terms of programming. In the future, I will write a dedicated article to introduce the implementation principles of Kotlin coroutines.
After the Reactive Streams specification was developed, class libraries were also developed to implement the specification. Reactor is one of these class libraries.
Reactor is a Reactive class library written in the Java language. It builds non-blocking applications according to the Reactive Streams specification. Reactor has been integrated with Spring 5. Class libraries similar to Reactor include RxJava2, RxJs, and JDK9 Flow.
Alibaba's current internal FaaS system is built entirely based on Reactor, including function applications and various core applications in logical architectures. According to our stress testing results, systems built based on Reactive have the following features:
In principle, I think both the resource utilization and the throughput are higher than those of non-Reactive applications.
Why do Reactive systems have these features?
Alibaba's internal FaaS system has made the following improvements:
The following figure shows the Reactor I/O model with a worker thread pool in Netty. Below the figure, Kotlin's pseudocode has been simplified.
// Read request data from the client by using the in parameter in a non-blocking manner. Then, execute lambda.
inChannel.read(in) {
workerThreadPool.execute{
// Execute the business logic by using the process function in a blocking manner and execute the business logic in the worker thread pool. After the synchronous execution is complete, the system returns the out parameter to the client.
val out = process(in)
outChannel.write(out)
}
}
I/O threads can execute the business logic using the process function without a worker thread pool.
// Read request data from the client by using the in parameter in a non-blocking manner. Then, execute lambda.
inChannel.read(in) {
// The I/O thread executes the business logic by using the process function in a non-blocking manner and then returns the out parameter to the client.
process(in){ out->
outChannel.write(out) {
// This lambda is executed when the writing completes.
...
}
}
}
It is worthwhile to learn from and maximize the value of Reactive systems. However, speaking frankly, Reactive programming has not yet received wide acceptance, especially among Java developers. I feel the same way because the thinking of Reactive is very different from that of Java programming. Java programming revolves around command-based control processes. Java developers can consult the following references to learn more about Reactor:
Reactive systems have many advantages. However, building a Reactive system is not easy. Not only is the language different from more popular languages, but some components do not support non-blocking calling methods, such as JDBC. Fortunately, some open source organizations are promoting innovations in these areas, such as R2DBC. To facilitate the construction of a complete Reactive system, some organizations and individuals have adapted mainstream technical components, such as reactor-core, reactor-netty, reactor-rabbimq, and reactor-kafka.
When your system has been made to Reactive from the underlying layer to the upper layers and from the inside of the system to external dependencies, you have built a Reactive architecture.
How valuable is this architecture? I think the future is promising.
Disclaimer: The views expressed herein are for reference only and don't necessarily represent the official views of Alibaba Cloud.
2,599 posts | 758 followers
FollowJeffsky - November 8, 2021
fengcone - June 1, 2021
hujt - April 1, 2021
Alibaba Cloud Community - March 9, 2022
Alibaba Cloud Community - March 9, 2022
Aliware - January 4, 2021
2,599 posts | 758 followers
FollowCustomized infrastructure to ensure high availability, scalability and high-performance
Learn MoreAlibaba Cloud Function Compute is a fully-managed event-driven compute service. It allows you to focus on writing and uploading code without the need to manage infrastructure such as servers.
Learn MoreAccelerate software development and delivery by integrating DevOps with the cloud
Learn MoreA one-stop, cloud-native platform that allows financial enterprises to develop and maintain highly available applications that use a distributed architecture.
Learn MoreMore Posts by Alibaba Clouder