Reactor Scheduler and threading model--Response Spring's Path wizard

Source: Internet
Author: User

This series of articles index the "Response Spring's word Wizard"
Previously summary Spring Webflux Quick Start | Spring Webflux Performance Test
Previously summary: Reactor 3 Quick Start | Responsive Flow Specification | Custom Data Flow
This article tests the source code

2.4 Scheduler and threading model

Sections 1.3.2 briefly describe the different types of schedulers and Scheduler how to use publishOn and subscribeOn switch different thread execution environments.

Here's a simple example to recall:

    @Test    public void testScheduling() {        Flux.range(0, 10)//                .log()                .publishOn(Schedulers.newParallel("myParallel"))//                .log()                .subscribeOn(Schedulers.newElastic("myElastic"))                .log()                .blockLast();    }
    1. If you keep this log (), you can see that the source data stream is executed on the myElastic-x thread;
    2. Just keep this log () and you can see that the publishOn data flow is executed on the myParallel-x thread;
    3. Just keep this log (), and you can see that the subscribeOn data flow is still executing on the myParallel-x thread.

Through the above three log() outputs, it can be found that for the operation chain as shown:

    • publishOn will affect subsequent operators in the chain , such as the first Publishon adjustment Scheduler is elastic, the filter processing operation is performed in the elastic thread pool, and the same flatMap is done in a fixed-size parallel thread pool;
    • Regardless of where subscribeOn it occurs, it only affects the execution environment of the source , that range is, the method is executed in a single thread until it is first switched to the publishOn scheduler, so range the post is map also executed in a single thread.

In this section we look at its implementation mechanism.

2.4.1 Scheduler

The scheduler is equivalent to Executorservice in reactor, and different schedulers define different thread execution environments. Schedulersthe static methods provided by the tool class can be used to build different thread execution environments.

SchedulersClass has pre-created several commonly used scheduler for different thread pool models: The single() elastic() Scheduler created using, and parallel() methods can use the built-in single-threaded, elastic thread pool, and fixed-size thread pools, respectively. If you want to create a new scheduler, you can use the newSingle() , newElastic() and newParallel() methods. All of these methods return a Scheduler specific implementation.

See Scheduler What the behavior is:

public interface Scheduler extends Disposable {    // 调度执行Runnable任务task。    Disposable schedule(Runnable task);    // 延迟一段指定的时间后执行。    Disposable schedule(Runnable task, long delay, TimeUnit unit);    // 周期性地执行任务。    Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit);    // 创建一个工作线程。    Worker createWorker();    // 启动调度器    void start();    // 以下两个方法可以暂时忽略    void dispose();    long now(TimeUnit unit)    // 一个Worker代表调度器可调度的一个工作线程,在一个Worker内,遵循FIFO(先进先出)的任务执行策略    interface Worker extends Disposable {        // 调度执行Runnable任务task。        Disposable schedule(Runnable task);        // 延迟一段指定的时间后执行。        Disposable schedule(Runnable task, long delay, TimeUnit unit);        // 周期性地执行任务。        Disposable schedulePeriodically(Runnable task, long initialDelay, long period, TimeUnit unit);    }}

, Scheduler is the leader, Worker is the employee, each Scheduler hand has several Worker . After receiving the task, Scheduler responsible for assigning, Worker responsible for work.

In Scheduler , each Worker is a ScheduledExecutorService , or a wrapped ScheduledExecutorService object. So, Scheduler instead of a thread pool, you have a self-sustaining ScheduledExecutorService pool.

The so-called "self-maintenance", there are mainly three points:

    1. Available for dispatch Worker . Schedulers.newParallel()returned, for example ParallelScheduler , maintains a fixed-size array, ScheduledExecutorService[] and is ElasticScheduler maintained by a executorservice Queue .
    2. Task dispatch policy. ElasticSchedulerand ParallelScheduler both have a pick() way to pick the right one Worker .
    3. For the task to be processed, it is wrapped Callable so that it can be returned asynchronously to the Future caller.
2.4.2 Switching the execution environment

Again back publishOn and subscribeOn method.

In reactor, the processing of data flows is actually a series of method invocations and event-based callbacks, including,,,, and, subscribe onSubscribe request onNext onError onComplete . Take out the 2.1-verse diagram to help understand:

When the .subscribe() method is called, the data flow from upstream to downstream is formed, and the elements in the data flow are onNext* (onError|onComplete) carried "downstream". At the same time, what reactor users cannot see is that there is also a "subscription chain" upstream from downstream, where request is being fed back to the demand along the chain.

The publishOn method can onNext be onError onComplete Scheduler Worker executed, dispatched to the given . So if you add one more to the middle of the scene, the .map .filter filtering processing of the publisheOn(Schedulers.elastic()) .filter action will be performed on onNext ElasticScheduler one of the Worker above.

subscribeOn method is capable of subscribe executing (calling onSubscribe ), request dispatching to the Scheduler given Worker . So if you add one at any location, you can use the subscribeOn(Schedulers.elastic()) bottom-up subscription chain to subscribe() pass the thread execution environment to the "source" and execute it on the way Flux.just ElasticScheduler . The subsequent operator is affected until the publishOn execution environment is changed.

In addition, some operators themselves will require the scheduler for multi-threaded processing, when you do not explicitly specify the scheduler, those operators will use the built-in Singleton Scheduler to execute. For example, a Flux.delayElements(Duration) Schedulers.parallel() Scheduler object is used:

    @Test    public void testDelayElements() {        Flux.range(0, 10)                .delayElements(Duration.ofMillis(10))                .log()                .blockLast();    }

From the output you can see that it onNext is running on a different thread:

[ INFO] (main) onSubscribe(FluxConcatMap.ConcatMapImmediate)[ INFO] (main) request(unbounded)[ INFO] (parallel-1) onNext(0)[ INFO] (parallel-2) onNext(1)[ INFO] (parallel-3) onNext(2)[ INFO] (parallel-4) onNext(3)...
2.4.3 Configuring the context for a data flow

In reactor, thread-based Scheduler scheduling is really simple and easy to use, but there is a problem that needs to be addressed.

When we were writing multithreaded code, we might use wrappers if we were involved in a value that was only used internally within the thread ThreadLocal .

But in responsive programming, this usage loses its usefulness and even brings bugs because the threading environment is constantly changing. This is the case, for example, using the Logback MDC to store the ID of the Log Association.

Since version 3.1.0,reactor introduced an advanced feature similar to ThreadLocal: Context. It acts on a Flux or a Mono, not on a thread. That is, its life cycle accompanies the entire data stream, not the thread.

Relatively speaking, users use the context is not much, interested in or have this need, please see my translation of the relevant documents, can be reactor internal implementation, especially Subscription have a deeper understanding.

2.4.4 Parallel execution

Nowadays multicore architectures are already popular and it is important to be able to do parallel processing conveniently.

For some tasks that can be processed sequentially in a thread, even when dispatched to Parallelscheduler, it is usually performed by only one worker, such as:

    @Test    public void testParallelFlux() throws InterruptedException {        Flux.range(1, 10)                .publishOn(Schedulers.parallel())                .log().subscribe();        TimeUnit.MILLISECONDS.sleep(10);    }

The output is as follows:

[ INFO] (main) | onSubscribe([Fuseable] FluxPublishOn.PublishOnSubscriber)[ INFO] (main) | request(unbounded)[ INFO] (parallel-1) | onNext(1)[ INFO] (parallel-1) | onNext(2)[ INFO] (parallel-1) | onNext(3)[ INFO] (parallel-1) | onNext(4)[ INFO] (parallel-1) | onNext(5)[ INFO] (parallel-1) | onNext(6)[ INFO] (parallel-1) | onNext(7)[ INFO] (parallel-1) | onNext(8)[ INFO] (parallel-1) | onNext(9)[ INFO] (parallel-1) | onNext(10)[ INFO] (parallel-1) | onComplete()

Sometimes we do need tasks that are "evenly" distributed across different worker threads ParallelFlux .

You can use the operator for any flux to parallel() get one ParallelFlux . However, this operator does not do parallel processing, but only divides the load into multiple execution tracks (by default, the number of tracks is equal to the number of CPU cores).

In order to configure ParallelFlux how each track is executed in parallel, it needs to be used runOn(Scheduler) , here, Schedulers.parallel () is the more recommended scheduler specifically for parallel processing.

    @Test    public void testParallelFlux() throws InterruptedException {        Flux.range(1, 10)                .parallel(2)                .runOn(Schedulers.parallel())//                .publishOn(Schedulers.parallel())                .log()                .subscribe();        TimeUnit.MILLISECONDS.sleep(10);    }

The output is as follows:

[ INFO] (main) onSubscribe([Fuseable] FluxPublishOn.PublishOnSubscriber)[ INFO] (main) request(unbounded)[ INFO] (main) onSubscribe([Fuseable] FluxPublishOn.PublishOnSubscriber)[ INFO] (main) request(unbounded)[ INFO] (parallel-1) onNext(1)[ INFO] (parallel-2) onNext(2)[ INFO] (parallel-1) onNext(3)[ INFO] (parallel-2) onNext(4)[ INFO] (parallel-1) onNext(5)[ INFO] (parallel-2) onNext(6)[ INFO] (parallel-1) onNext(7)[ INFO] (parallel-2) onNext(8)[ INFO] (parallel-1) onNext(9)[ INFO] (parallel-2) onNext(10)[ INFO] (parallel-1) onComplete()[ INFO] (parallel-2) onComplete()

As you can see, the OnNext "uniform" distribution of individual elements is performed on two threads, with separate events on each thread, onComplete which publishOn differs from scheduling to Parallelscheduler.

Reactor Scheduler and threading model--Response Spring's Path wizard

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.