×
Community Blog Reactive Programming Is the Architecture of the Future

Reactive Programming Is the Architecture of the Future

This article discusses the concept, specifications, value, and principles of Reactive from the author's perspective.

By Buwen

1

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 and Reactive Programming

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.

Reactive Manifesto

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:

Responsive

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.

Resilient

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.

Elastic

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.

Message-Driven

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.

41d536d9b869dfb6ae1cd1b69274ce0d2af71576

Note:

  • The preceding descriptions involve many proper nouns. If you are confused by any of the terms, see the glossary of relevant terms.
  • In the Reactor section, I will explain the reasons why systems built based on Reactive can deliver these valuable features.

Reactive Streams

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.

Problems Resolved by Reactive 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 Goal of Reactive Streams

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.

Scope of Reactive Streams

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:

Publisher

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);
}

Subscriber

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

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();
}

Processor

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?

  • In the synchronous mode, we usually improve performance through multithreading. However, the number of threads that can be created in the system is limited, and an increase in the number of threads produces thread switching overhead.
  • In the synchronous mode, resource utilization is difficult to improve further.
  • When the system where synchronous calls depend fails, the stability of synchronous calls will also be affected.

The non-blocking mode can be implemented in multiple ways, so why was the preceding implementation chosen?

Thread

  • Compared with the following implementations, threads are not very lightweight.
  • The number of threads is limited. Therefore, threads may eventually become the main bottleneck of the system.
  • Some platforms may not support multithreading. For example, platforms that use JavaScript do not support multithreading.
  • Tread debugging and implementation is complex.

Callback

  • Nested callbacks are complex and easily lead to a "callback mess."
  • Error handling is complex.
  • Callbacks are mostly used in languages with the event loop architecture, such as JavaScript.

Future

  • Behaviors cannot be combined logically, and supported business scenarios are limited.
  • Error handling is complex.

Rx

  • Reactive Extensions (Rx) are similar to Future. Future can return an independent element, while Rx returns a stream that can be subscribed to.
  • The same set of specifications is supported on different platforms.
  • The same API can be called asynchronously and synchronously.
  • Error handling is convenient.

Coroutines

  • Kotlin coroutines and goroutines support asynchronous syntax and are simpler than Rx. However, no unified specifications can be provided for them for platforms that use different languages.

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.

Reactor

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:

  • Resilient: Even if a function times out severely, with an RT greater than or equal to ten seconds, the upstream broker and gateway applications of the function are barely affected.
  • Responsive: The RT is always consistent in normal scenarios and high-concurrency scenarios as long as resources are sufficient.

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:

  • It asynchronized almost all I/O related operations. For example, asynchronous API calls are provided for middleware products, such as HSF and MetaQ.
  • It optimized the I/O threading model. Fewer threads are used to handle all requests. Typically, the number of threads to be used is the same as the number of CPU cores.

I/O Threading Model of Conventional Java Applications

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.

3

// 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 Threading Model for Reactive Applications

I/O threads can execute the business logic using the process function without a worker thread pool.

4

// 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.
        ...
        }
    }
}

Getting Started with Reactive Programming

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:

  • Basic Reactor Documentation
  • Reactive Streams Specification Documentation
  • Operator

Summary

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.

0 0 0
Share on

Alibaba Clouder

2,599 posts | 758 followers

You may also like

Comments

Alibaba Clouder

2,599 posts | 758 followers

Related Products