Project Reactor 核心原理解析

一、開篇

本文將解析 Spring 的 Reactor 項目的源碼。主要目的是讓自己能深入理解 Reactor 這個項目,以及 Spring 5 和 Spring Boot 2。

Project Reactor 項目地址:https://github.com/reactor

Reactor 項目主要包含 Reactor Core 和 Reactor Netty 兩部分。Reactor Core 實現了反應式編程的核心功能,Reactor Netty 則是 Spring WebFlux 等技術的基礎。本文將介紹 Core 模塊的核心原理。

本文從一個例子開始:

public static void main(String[] args) {
    Flux.just("tom", "jack", "allen")
        .filter(s -> s.length() > 3)
        .map(s -> s.concat("@qq.com"))
        .subscribe(System.out::println);
}

整個 Project Reactor 的核心調用分爲下面幾個階段:

  • 聲明階段
  • subscribe 階段
  • onSubscribe 階段
  • request 階段
  • 調用階段

接下來對每個階段詳細介紹。

二、聲明階段

簡介

之前的文章介紹過,反應式編程和傳統風格的編程一個最大的不同點在於,開發人員編寫的大部分代碼都是在聲明處理過程。即這些代碼並不會被實際的執行,直到開始被訂閱。這便是爲何第一階段是聲明階段的原因。

詳解

先來看第一個方法 Flux.just 的源碼:

public static <T> Flux<T> just(T... data) {
    return fromArray(data);
}

public static <T> Flux<T> fromArray(T[] array) {
    // 省略不重要的部分
    return onAssembly(new FluxArray<>(array));
}

just 方法只是創建反應式流的衆多方式的一個。在實際工作中,更常見的通過反應式 Repository 將數據庫查詢結果,或通過 Spring 5 的 WebClient 將 HTTP 調用結果最爲流的開始。

onAssembly 是一個擴展機制,因爲實例中並沒有使用,所以可簡單理解爲將入參直接返回。

接下來 new FluxArray<>(array) 做的事情也很簡單,僅僅是把入參賦給成員變量。

再接下來 filter(s -> s.length() > 3) 做的事情是把上一個 Flux,即 FluxArray,以及 s -> s.length() > 3 所表示的 Predicate 保存起來。

看一下源碼

public final Flux<T> filter(Predicate<? super T> p) {
    if (this instanceof Fuseable) {
        return onAssembly(new FluxFilterFuseable<>(this, p));
    }
    return onAssembly(new FluxFilter<>(this, p));
}

FluxFilterFuseable(Flux<? extends T> source, Predicate<? super T> predicate) {
    super(source);
    this.predicate = Objects.requireNonNull(predicate, "predicate");
}

這裏有個邏輯判斷,就是返回的值分爲了 Fuseable 和非 Fuseable 兩種。簡單介紹一下,Fuseable 的意思就是可融合的。我理解就是 Flux 表示的是一個類似集合的概念,有一些集合類型可以將多個元素融合在一起,打包處理,而不必每個元素都一步步的處理,從而提高了效率。因爲 FluxArray 中的數據一開始就都準備好了,因此可以打包處理,因此就是 Fuseable

而接下來的 map 操作也對應一個 FluxMapFusable。原理與 filter 操作,這裏不再重複。

三、subscribe 階段

簡介

subscribe 階段同行會觸發數據發送。在本例中,後面可以看到,對於 FluxArray,數據發送很簡單,就是循環發送。而對於像數據庫、RPC 這樣的長久,則會觸發請求的發送。

詳解

當調用 subscribe(System.out::println) 時,整個執行過程便進入 subscribe 階段。經過一系列的調用之後,subscribe 動作會代理給具體的 Flux 來實現。就本例來說,會代理給 FluxMapFuseable,方法實現如下(經過簡化):

public void subscribe(CoreSubscriber<? super R> actual) {
    source.subscribe(new MapFuseableSubscriber<>(actual, mapper));
}

其中的 source 變量對應的是當前 Flux 的上一個。本例中,FluxMapFuseable 上一個是 FluxFilterFuseable

new MapFuseableSubscriber<>(actual, mapper) 則是將訂閱了 FluxMapFuseable 的 Subscriber 和映射器封裝在一起,組成一個新的 Subscriber。然後再訂閱 source,即 FluxArraysource 是在上一個階段被保存下來的。

這裏強調一下 Publisher 接口中的 subscribe 方法語義上有些奇特,它表示的不是訂閱關係,而是被訂閱關係。即 aPublisher.subscribe(aSubscriber) 表示的是 aPublisheraSubscriber 訂閱。

接下來調用的就是 FluxFilterFuseable 中的 subscribe 方法,類似於 FluxMapFuseable,源碼如下:

public void subscribe(CoreSubscriber<? super T> actual) {
    source.subscribe(new FilterFuseableSubscriber<>(actual, predicate));
}

這時 source 就成了 FluxArray。於是,接下來是調用 FluxArraysubscribe 方法。

public static <T> void subscribe(CoreSubscriber<? super T> s, T[] array) {
    if (array.length == 0) {
        Operators.complete(s);
        return;
    }
    if (s instanceof ConditionalSubscriber) {
        s.onSubscribe(new ArrayConditionalSubscription<>((ConditionalSubscriber<? super T>) s, array));
    }
    else {
        s.onSubscribe(new ArraySubscription<>(s, array));
    }
}

最重要的部分還是 s.onSubscribe(new ArraySubscription<>(s, array))。不同於 FluxMapFuseableFluxFilterFuseableFluxArray 沒有再調用 subscribe 方法,因爲它是數據源頭。而 FluxMapFuseableFluxFilterFuseable 則是中間過程。

可以這樣簡單理解,對於中間過程的 Mono/Flux,subscribe 階段是訂閱上一個 Mono/Flux;而對於源 Mono/Flux,則是要執行 Subscriber.onSubscribe(Subscription s) 方法。

四、onSubscribe 階段

簡介

在調用 FluxArraysubscribe 方法之後,執行過程便進入了 onSubscribe 階段。onSubscribe 階段指的是 Subscriber#onSubscribe 方法被依次調用的階段。這個階段會讓各 Subscriber 知道 subscribe 方法已被觸發,真正的處理流程馬上就要開始。所以這一階段的工作相對簡單。

詳解

s.onSubscribe(new ArraySubscription<>(s, array));

s 是 FilterFuseableSubscriber,看一下 FilterFuseableSubscriberonSubscribe(Subscription s) 源碼:

public void onSubscribe(Subscription s) {
    if (Operators.validate(this.s, s)) {
        this.s = (QueueSubscription<T>) s;
        actual.onSubscribe(this);
    }
}

actual 對應 MapFuseableSubscriberMapFuseableSubscriberonSubscribe 方法也是這樣,但 actual 對於的則是代表 System.out::printlnLambdaSubscriber

調用過程:

FluxArray.subscribe -> FilterFuseableSubscriber.onSubscribe -> MapFuseableSubscriber.onSubscribe -> LambdaSubscriber.onSubscribe

五、request 階段

含義

onSubscribe 階段是表示訂閱動作的方式,讓各 Subscriber 知悉,準備開始處理數據。當最終的 Subscriber 做好處理數據的準備之後,它便會調用 Subscriptionrequest 方法請求數據。

詳解

下面是 LambdaSubscriber onSubscribe 方法的源碼:

public final void onSubscribe(Subscription s) {
    if (Operators.validate(subscription, s)) {
        this.subscription = s;
        if (subscriptionConsumer != null) {
            try {
                subscriptionConsumer.accept(s);
            }
            catch (Throwable t) {
                Exceptions.throwIfFatal(t);
                s.cancel();
                onError(t);
            }
        }
        else {
            s.request(Long.MAX_VALUE);
        }
    }
}

由上可見 request 方法被調用。在本例中,這裏的 sMapFuseableSubscriber

這裏需要說明,如 MapFuseableSubscriberFilterFuseableSubscriber,它們都有兩個角色。一個角色是 Subscriber,另一個角色是 Subscription。因爲它們都位於調用鏈的中間,本身並不產生數據,也不需要對數據暫存,但是需要對數據做各式處理。因此,在 onSubscribe、request 階段,以及後面要講到的調用階段都需要起到代理的作用。這就解釋了 actual.onSubscribe(this) onSubscribe 自己的原因。

下面是 FilterFuseableSubscriberrequest 方法的源碼。充分可見其代理的角色。

public void request(long n) {
    s.request(n);
}

最後 ArraySubscriptionrequest 方法將被調用。

與 map、filter 等操作不同,flatMap 有比較大的差異,感興趣的同學可以自己研究一下。本文先不詳細介紹。

六、調用階段

含義解釋

這一階段將會通過調用 SubscriberonNext 方法,從而進行真正的反應式的數據處理。

流程分解

ArraySubscriptionrequest 方法被調用之後,執行流程便開始了最後的調用階段。

public void request(long n) {
    if (Operators.validate(n)) {
        if (Operators.addCap(REQUESTED, this, n) == 0) {
            if (n == Long.MAX_VALUE) {
                fastPath();
            }
            else {
                slowPath(n);
            }
        }
    }
}

void fastPath() {
    final T[] a = array;
    final int len = a.length;
    final Subscriber<? super T> s = actual;
    for (int i = index; i != len; i++) {
        if (cancelled) {
            return;
        }

        T t = a[i];

        if (t == null) {
            s.onError(new NullPointerException("The " + i + "th array element was null"));
            return;
        }

        s.onNext(t);
    }
    if (cancelled) {
        return;
    }
    s.onComplete();
}

ArraySubscription 會循環數據中的所有元素,然後調用 SubscriberonNext 方法,將元素交由 Subscriber 鏈處理。

於是接下來由 FilterFuseableSubscriber 處理。下面是其 onNext 方法(做了一些簡化)

public void onNext(T t) {
   boolean b;

   try {
       b = predicate.test(t);
   }
   catch (Throwable e) {
       Throwable e_ = Operators.onNextError(t, e, this.ctx, s);
       if (e_ != null) {
           onError(e_);
       }
       else {
           s.request(1);
       }
       Operators.onDiscard(t, this.ctx);
       return;
   }
   if (b) {
       actual.onNext(t);
   }
   else {
       s.request(1);
       Operators.onDiscard(t, this.ctx);
   }
}

其最要部分是通過 predicate 進行判斷,如果滿足條件,則交由下一個 Subscriber 處理。

下一個要處理的是 MapFuseableSubscriber,原理類似,不再重複。

最終要處理數據的是 LambdaSubscriber,下面是它的 onNext 方法源碼:

public final void onNext(T x) {
    try {
        if (consumer != null) {
            consumer.accept(x);
        }
    }
    catch (Throwable t) {
        Exceptions.throwIfFatal(t);
        this.subscription.cancel();
        onError(t);
    }
}

consumer.accept(x) 會使數據打印在命令行中。

整個流程的解釋到這裏基本結束。

七、總結

接下來我們用一個圖來回顧一下整體的流程。

參考

需要感謝下面這篇文章,幫助我很好理解了 Project Reactor 的核心處理流程。

由表及裏學 ProjectReactor:http://blog.yannxia.top/2018/06/26/java/spring/projectreactor/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章