Spring Responsive Programming - Reading Notes

preamble

Hello everyone, I am Bittao. This article is the reading notes of "Spring Responsive Programming". Responsive technology stack can create extremely efficient, easy-to-acquire and resilient endpoints. At the same time, responsiveness can tolerate network delays and handle failures with less impact. Reactive microservices also isolate slow transactions and speed up the fastest ones. Through this book you can learn the following:

  • Reactive programming basic principles and Reactive Stream specification;
  • Use the Project Reactor responsive development framework integrated with Spring 5;
  • Use Spring Webflux to build responsive RESTful services;
  • Use Spring Data Reactive to build responsive data access components;
  • Use Spring Cloud Stream Reactive to build responsive message communication components.

Chapter 1 Reactive Spring

First, let’s understand the terms related to responsive programming:

  • Elasticity: can be expanded horizontally;
  • Rebound: fault isolation, achieve independence;
  • Message-driven: should not be blocking, instant response.

In the reactive style, the key is back pressure : it is mainly used to support resilience and realize the complex mechanism of workload management between processing stages, which can ensure that one processing stage will not overwhelm the other.

In the field of JVM, there are two most well-known frameworks for building responsive systems: Akka and Vert.x. In traditional Java, the responsive way:

  1. Callback function, but it will bring callback hell problem;
  2. JDK 8 CompletionStage.

In order to be compatible with the old JDK, Spring 4 did not support CompletionStage, and this method was only supported in Spring 5. At the same time, Servlet 3.0 introduced asynchronous client-server communication, and 3.1 supports I/O non-blocking writes. But Spring MVC doesn't provide an asynchronous non-blocking client out of the box. Complete loss of responsive support for obsolete baggage. It also caused Spring to open a separate technology stack for responsiveness.

Chapter 2 Basic Concepts of Spring Responsive Programming

The publish-subscribe pattern, which can be seen as a variant of the observer pattern. In the RxJava-based implementation, the temperature sensor does not detect temperature data when no one is listening. This behavior is a natural consequence of the fact that reactive programming has the concept of active subscriptions. Publish-topic based implementations have no such property and are therefore more restrictive.

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-Qzu0Aua4-1678244967432)(%E3%80%8ASpring%20%E5%93%8D%E5%BA% 94%E5%BC%8F%E7%BC%96%E7%A8%8B%E3%80%8B%20f33ef9259404475ab7d53d8b4b03f718/Untitled.jpeg)]
The RxJava library is a Java virtual machine implementation of Reactive Extensions (reactive extensions, also known as ReactiveX). Reactive Extensions is a set of tools for processing streams of data, whether synchronous or asynchronous, in an imperative language. ReactiveX is generally defined as a combination of Observer Pattern, Iterator Pattern and Functional Programming.

RxJava subscribes to an observable stream via a subscriber, which in turn triggers an asynchronous process of event generation. Taking this approach is detrimental to mutable objects, and the only reasonable strategy is to employ immutability. Immutability is one of the core principles of functional programming. Once an object is created, it cannot be changed. Such a simple rule prevents possible problems in parallel systems.

Without functional programming, one would have to create lots of anonymous or inner classes that would pollute the application. And they create more boilerplate code than effective code. Because of its immutability, it is very suitable for concurrent programming.

history

The history of asynchronous programming dates back to what Microsoft did in 2005: a programming model for large-scale asynchronous data-intensive Internet service architecture . In 2009, Rx.NET was open sourced. Later, Netflix was facing the complex problem of massive Internet traffic, so it opened RxJava as an open source. And based on it, well-known libraries such as: Hystrix, Ribbon, Zuul, RxNetty were developed. But in fact, it is Node.js that has fully implemented and successfully made great contributions in the direction of responsive programming.

RxJava is not the only solution in Java, Vert.x can also achieve similar functions. Vert.x is an event-driven development framework, similar in design to Node.js. It provides a simple concurrency model and primitive semantics for asynchronous programming.

With the success of RxJava, many companies and open source projects have started fierce competition. Since the behavior of the various libraries is very similar overall, but there are slight differences in the implementation. So if you share some responsive libraries, hidden bugs may appear. In order to solve these compatibility problems, a standard emerged: Reactive Streams.

Chapter 3 Reactive Streams - The New Standard

To use multiple responsive libraries, you need to write adapters for compatibility. There is no direct integration between ListenableFuture and CompletionStage in Spring 4. In Spring 5, the API of ListenableFuture was extended to a method called Comple-table to solve the incompatibility. question. The core problem is that there is no way for library providers to provide aligned APIs. Vert.x, Ratpack, and Retrofit all pay more attention to RxJava and provide support.

In the early stages of the evolution of the entire reactive environment, all libraries were designed with the idea of ​​pushing data from sources to subscribers. The main reason for adopting the push model is that it optimizes the overall processing time by reducing the request volume to a minimum.

This is why RxJava1.x and similar development libraries are designed for the purpose of pushing data, and this is why stream technology can be called an important communication technology between components in a distributed system. However, if the producer does not pay attention to the throughput capacity of the consumer, there are the following possibilities:

  • slow producer and fast consumer
  • Fast Producer and Slow Consumer

The solution for this situation is to collect unprocessed elements into a queue.

  • Unbounded queue: a queue of unlimited size;
  • Bounded drop queue: to avoid memory overflow;
  • Bounded blocking queue: payment order blocking;

In general, uncontrolled semantics in pure push models can lead to many undesirable situations. The Reactive Manifesto mentions the importance of mechanisms that allow a system to respond smartly to load, and the importance of backpressure.

The Reactive Streams specification defines 4 main interfaces: Publisher, Subscriber, Subscription, and Processor.

Among them, Publisher and Observable, Subscriber and Observer are basically the same;

In the onSubscribe method in Subscriber, Subscription is referenced, which provides the basis for the production of control elements. The Reactive Streams specification introduces the request method to extend the interaction between Publisher and Subscriber. In order to inform the Publisher how much data it should push, the Subscriber should signal via the request method about the desired amount and ensure that the number of incoming elements does not exceed the limit.
insert image description here

As opposed to a pure push model, this specification gives us a hybrid push-pull model with reasonable control over backpressure.

Processor is a combination of Publisher and Subscriber. Its goal is to add some processing stages between Publisher and Subscriber.

Chapter 4 Project Reactor Reactive Application Fundamentals

The reactive streams specification (reactivestreams) makes reactive libraries compatible with each other and solves the backpressure problem by introducing a pull-push data exchange model. But it only defines the specification and does not provide daily use. Spring implements Project Reactor (Reactor for short).

The reactive streams specification (reactivestreams) makes reactive libraries compatible with each other and solves the backpressure problem by introducing a pull-push data exchange model. But it only defines the specification and does not provide daily use. Spring implements Project Reactor (Reactor for short). The Reactor1.x version includes best practices for message processing, such as the Reactor Pattern (Reactor Pattern), as well as functional and reactive programming styles.
The Reactor pattern is a behavioral pattern that facilitates asynchronous event response and synchronous processing. This means that all events need to be queued, and the actual processing of events is performed later by a separate container. An event is dispatched to all concerned parties (event handlers) and processed synchronously.
Reactor 1.x is well integrated with the Spring framework. Reactor 1.x provides many additional components together with the message processing library, such as an additional component for Netty. Reactor 2 pioneered reactive streaming. Extracting the event bus and streaming functionality into separate modules makes the Reactor Streams library fully compliant with the Reactive Streams specification. Reactor API has better integration with Java Collections API. Reactor's Streams API is more similar to the RxJava API, with added support for backpressure management, thread scheduling, and resilience. The Reactor object that sends the message is renamed to EventBus.

The ideas of RxJava and ProjectReactor were condensed into a reactive-stream-commons library, which later became Reactor3.x. At the same time, Reactor3.x shaped the responsive variant of the Spring 5 framework (reactive metamorphosis)

The Reactor library is designed to avoid callback hell and deeply nested code when building asynchronous pipelines. We can think of the processed data of a reactive application as moving on an assembly line. The Reactor is both a conveyor belt and a workstation. Reactor API only triggers real data flow when subscribed. Common backpressure propagation patterns for this library:

insert image description here

  • Push only: when a subscriber requests a valid wireless amount element via subscription.request(Long.MAX_VALUE).
  • Pull-only: When a subscriber requests the next element only after receiving the previous one via subscription.request(1).
  • Pull-push (sometimes called hybrid): When subscribers have real-time control needs and publishers can accommodate the proposed rate of data consumption.

Project Reactor is built on top of the Reactive Streams specification, and org.reactivestreams:reactive-streams is the only mandatory dependency of Project Reactor. The Reactive Stream specification defines four interfaces, namely Publisher, Subscriber, Subscription, and Processor<T, R>.

ProjectReactor provides two implementations of the Publisher interface, Flux and Mono.

Mono is one of Reactor's two core types, the other being Flux. Both implement Reactive Streams' Publisher interface. A Flux represents a pipeline with zero, one, or many (possibly infinite) data items. Its formula is: onNext x 0…N [onError | onComplete]. For example: Flux.range(1, 5).repeat();

Mono is a special reactive type optimized for scenarios where there is no more than one data item. The formula is expressed as: onNext x 0…1 [onError | onComplete]. Mono is useful when the application API returns at most one element. Therefore, it can replace comple-tableFuture and provide similar semantics. CompletableFuture starts processing immediately, while Mono does nothing until a subscriber comes along. The benefit of the Mono type is only that it not only provides a large number of reactive operators, but also can be perfectly integrated into a larger reactive workflow. Example: When the operation is completed and the client needs to be notified, Mono can also be used to send the onComplete() signal when the processing is completed, and return onError() when a failure occurs. In this scenario, no data is returned, but a notification signal is emitted, which in turn can be used as a trigger for further calculations.

Flux and Mono are the most basic building blocks provided by Reactor, and the operators provided by these two reactive types are the glue that combine them to build data flow pipelines. Flux and Mono have a total of more than 500 operations, which can be roughly classified into:
creation operations;
combination operations;
conversion operations;
logical operations.

Chapter 5 Responsiveness with Spring Boot 2

Project Reactor can run without the Spring framework, and it will be even better when combined with Spring's dependency injection.

In 2009, the Spring team used the method of Convention-over-configuration in order to quickly develop applications. Spring boot was released in 2012, adopting the concept of containerless web applications and executable fat JAR (Fat JAR) technology. The two most critical annotations: @SpringBootApplication, used to run the IoC container; @spring-boot-autoconfigure, automatically configure some "-starter-" suffix components.

Spring 5.x introduces native support for reactive streams and reactive libraries, including RxJava 1/2 and Project Reactor3.

Adapters for Servlet API 3.1 provide purely asynchronous and non-blocking integration unlike WebMVC adapters. Of course, the Spring WebMVC module also supports Servlet API4.0, which supports HTTP/2.

insert image description here

In the early days, Spring Data mainly provided synchronous blocking access to the underlying storage area. Fortunately, the fifth generation of Spring Data provides the ReactiveCrudRepository interface, which exposes the responsive type of ProjectReactor to facilitate seamless integration of reactive workflows.

The Spring Sessiong module can use efficient abstraction for session management. Spring Sessiong introduces ReactiveSessionRepository, which can use Reactor's Mono type to perform asynchronous non-blocking access to stored sessions.

The old Spring Security uses ThreadLocal as the storage method of the SecurityContext instance, which is very effective when executed in a single Thread. At any time, we can access the SecurityContext stored in the ThreadLocal. But in asynchronous communication, we need to transfer ThreadLocal content to another Thread. Today's new generation of Spring Security adopts the Reactor context function to transmit security context in Flux or Mono streams.

Netflix Zuul is based on a Servlet API that uses blocking synchronous request routing, and the only way to invalidate processing requests and get better performance is to tune the underlying server thread pool. Fortunately Spring Cloud has introduced the new Spring Cloud Gateway module, which is built on top of Spring WebFlux and provides asynchronous and non-blocking routing with the support of Project Reactor 3. In addition to gateways, Spring Cloud Streams also gained support from Project Reactor and introduced a more fine-grained stream model. There are also Spring Cloud Function and Spring Cloud Data Flow, which can build their own FaaS (Function as a service).

Spring Actuator provides full integration with WebFlux and uses its asynchronous, non-blocking programming model in order to efficiently expose metrics endpoints. The Spring Cloud Sleuth module also supports Project Reactor's responsive programming and out-of-the-box distributed tracing.

Chapter 6 WebFlux Asynchronous Non-blocking Communication

The decision to integrate the Spring Web module with Java EE's Servlet API began when the Spring framework began to evolve in the web application space. The overall infrastructure of the Spring framework is built around the Servet API , and they are tightly coupled. For example, Spring Web MVC as a whole is based on the Front Controller pattern. This mode is implemented by the org.springframework.web.servlet.DispatcherServlet class in Spring Web MVC, and the introduction of this class extends the javax.servlet.http.HttpServlet class.

The overall design relies on the underlying Servlet container, which is responsible for handling all mapped Servlets within the container. DispatchServlet serves as an integration point for integrating the flexible and highly configurable Spring Web infrastructure with the heavy and complex Servlet API. The configurable abstraction of HandlerMapping helps to separate the final business logic (such as controllers and beans) from the Servlet API.

Although the Servlet API supports asynchronous, non-blocking communication (starting from version 3.1), the implementation of the SpringMVC module not only has many defects, but also does not allow non-blocking operations throughout the request life cycle. For example it does not have a non-blocking HTTP client out of the box, any external interaction may result in blocking I/O calls. Another disadvantage of Web abstraction in older versions of Spring is that there is no flexibility for a non-Servlet server (such as Netty) to reuse Spring Web functionality or the programming model.

insert image description here

WebFlux provides the ability to develop lightweight applications through functional route mapping and a built-in API where we can write complex request routing logic. The combination of purely functional routing is sufficient for new reactive programming approaches. Also, a reactive replacement for the old RestTemplate for WebClient is provided. Although WeboSocket was introduced to Spring in 2013, there are still some blocking operations, such as writing data to I/O or reading data from I/O are still blocking operations. The WebFlux module introduces an improved version of the infrastructure for WebSocket and provides client support.

Comparing WebFlux and WebMVC

In the past, computers were sequential, and everyone was used to browsing simple and static content, and the overall load of the system was always low. But now the number of Web users has reached more than a dozen levels, and the content has begun to become dynamic or even real-time, and the requirements for throughput and delay have changed a lot. To calculate how the number of parallel work units changes latency or throughput, you can use Little's Law: N = X x R. The average number of requests (or concurrently processed requests) (N) resident in a system or queue is equal to the throughput (or number of users per second) (X) multiplied by the average response time or latency (R); for example: System Average Response The time R is 0.2S, and the throughput X is 100 requests per second, then it should be able to handle 20 requests at the same time, or 20 users in parallel.

Traditional Servlet-based web frameworks, such as Spring MVC, are blocking and multi-threaded in nature, and each connection uses a thread. When the request is processed, a worker thread will be pulled from the thread pool to process the request. Meanwhile, the request thread is blocked until the worker thread signals that it has completed.
The consequence of this is that blocking web frameworks cannot scale effectively under a large number of requests. The delay introduced by a slow worker thread will make the situation worse, because it will take longer to send the worker thread back to the pool, ready to handle another request. In some scenarios, this design is perfectly acceptable. In fact, that's largely how most web applications have been developed for over a decade, but times are changing.
The clients of these web applications used to be people who browsed the site occasionally, but now these people consume content frequently and use applications that work with HTTP APIs. Today, the Internet of Things (which doesn't even need humans) has spawned cars, jet engines, and other non-traditional clients that are constantly exchanging data with Web APIs. With more and more clients consuming web applications, scalability is more important than ever.
Asynchronous web frameworks can achieve higher scalability with fewer threads, usually they only need the same number of threads as CPU cores. By using the so-called event looping mechanism (shown in Figure 11.1), these frameworks are able to handle many requests with one thread, so that the cost per connection will be lower.

The box in the upper right represents an alternative programming model that uses a functional programming paradigm to define controllers instead of using annotations.

insert image description here

Although Spring WebFlux controllers usually return Mono and Flux, that doesn't mean Spring MVC can't experience the joys of reactive typing. Spring MVC can also return Mono and Flux if you like.
The difference here is how these types are used. Spring WebFlux is a true reactive web framework that allows requests to be processed in event loops; while Spring MVC is Servlet-based and relies on multithreading to handle multiple requests.

Web MVC is built on blocking I/O, which means that the Thread processing each incoming request may be blocked by reading incoming messages from I/O.

insert image description here

In contrast, WebFlux is built on top of a non-blocking API, meaning that no operations need to interact with I/O-blocking Threads. WebFlux can utilize a Thread more efficiently than Web MVC.

insert image description here

Scenarios for WebFlux applications:

  1. Microservice system: The most notable feature of a typical microservice system is a large number of I/O communications. The existence of I/O, especially blocking I/O, will reduce the delay and throughput of the entire system.
  2. Systems that handle slow client connections: If the number of clients is high, the chances of the system crashing are high. For example, hackers can easily make our servers unavailable by using Denial-of-Service (DoS) attacks. In contrast, WebFlux allows us to accept connections without blocking worker threads.
  3. Streaming or real-time systems: These systems are characterized by low latency and high throughput, which can be achieved using non-blocking communication. However, this reactive framework has its own drawbacks, namely complex interaction models using channels and callbacks. But we can build an asynchronous non-blocking stream with a reactive library and still require very little overhead.

WebFlux is available with a responsive web server (Netty) and non-blocking Undertow functionality

Chapter 7 Reactive Database Access

Blocking I/O is always discouraged in reactive applications. Spring Data modules access data in a reactive manner. Even if the selected database does not provide a reactive or asynchronous driver, we can still use a dedicated thread pool to build a framework around it. Responsive application.

Eric Evans' "Domain-Driven Design: The Way to Cope with the Complexity of Software Core" defines and shapes the important theoretical basis for the successful microservice architecture. Domain-Driven Design (DDD), establishes a common vocabulary (ie, context, domain, model, and unified language), and a single bounded context (bounded context) defined according to DDD is usually mapped to a separate micro in service.

insert image description here

Since DDD is very concerned about the business core domain (core domain), especially the artifacts used to express, create and retrieve domain models, entities (Entity), value objects (Value object), aggregation (Aggregate), repository (Repository), etc. Objects will be referred to frequently in this chapter.

During an application implementation considering DDD, the above objects should be mapped to the application persistence layer. This domain model forms the basis for the logical and physical data model.

To be continued...

Guess you like

Origin blog.csdn.net/u012558210/article/details/129399429