rxjava 中最具有挑戰性的設計就是 backpresure 。例如 zip
操作符,合并兩個 Observable
A 和 B 。如果 B 的產生速度比 A 快,那么就需要不停的緩存 B 多余生成出來的數據,這樣內存就無限增長了。 backpressure 的機制就是讓 B 生成慢一點。
目前為止,我看到 rxjava 的設計是很丑陋的。這種機制是沒有強制性的。更糟糕的是, rxjava 暴露了 Observable.create(OnSubscribe<?> onSubscribe)
這個函數,如果不了解這個機制,上來"想當然" 的實現一個 OnSubscribe
,而不管 backpressure 機制,很容易產生 MissingBackpressureException
。
“想當然” 不是使用者的錯,而是庫的設計者的錯誤。可惜的是,太多用戶重度使用這個 Observable.create(OnSubscribe<?> onSubscribe)
函數,為了保證現有程序能夠繼續運行,就不能隱藏這個函數。于是,我們在注釋中,可以看到下面一段話
<strong>This method requires advanced knowledge about building operators and data sources; please consider other standard methods first; </strong>
本文試圖得到 “advanced knowledge” 。
下面是 backpressure 的協議是如何建立的。
someObservable.subscribe(new Subscriber<T>() {
@Override
public void onStart() {
request(1);
}
@Override
public void onCompleted() {
// gracefully handle sequence-complete
}
@Override
public void onError(Throwable e) {
// gracefully handle error
}
@Override
public void onNext(T n) {
// do something with the emitted item "n"
// request another item:
request(1);
}
});
可見底層 subscriber
在剛剛啟動的時候,發起流控請求 onStart
, request(1)
。告訴樓上的,哥們,別整太多,一個數據就夠了,多了處理不了。
onNext
中,先處理數據,處理完了,告訴樓上的,接著往下放數據,別多,就一個。
這里需要注意的是,不能再 request(n)
函數里面產生數據,否則遞歸調用 onNext
,可能導致爆棧了。
我們看看 Subscriber
是如何實現這個協議的。
public abstract class Subscriber<T> implements Observer<T>, Subscription {
// represents requested not set yet
private static final long NOT_SET = Long.MIN_VALUE;
private final SubscriptionList subscriptions;
private final Subscriber<?> subscriber;
private Producer producer;
private long requested = NOT_SET; // default to not set
}
本文重點關注 backpressure ,只看和這個相關的變量
-
NOT_SET
表示無效的請求數據量。或者說,還Subscriber
沒有提供請求的數據量時的狀態。 -
subscriber
,如果這個值不為null
,那么把 backpressure 相關的處理,交給這個subscriber
處理。有大多數很多操作符,自己并不能很好的處理這種過載,需要一層層向上傳遞,一直到數據源,只有產生數據的地方,才能比較好的處理,因為在那里,可以很容易的少產生一些數據。 -
producer
如果本subscriber
可以處理,那么代理給producer
來處理。 -
requested
,計數器,記錄樓下的請求多少數據。- 如果是
NOT_SET
,就是說樓下還不知道請求多少。 - 如果是
MAX_LONG
,就是說樓下來者不拒,不怕 overload - 如果是其他值,就是說樓下的最多能處理多少數據。
- 如果是
protected final void request(long n) {
// if producer is set then we will request from it
// otherwise we increase the requested count by n
if (producer != null) {
producer.request(n);
} else {
requested = requested + n;
}
}
這個函數被我簡化了,去掉了關于線程安全的部分。這樣代碼的可讀性好多了。
- 就是說如果有
producer
,那么計數的功能就交給producer
了。 - 如果沒有,那么
requested
用來計數。
這里簡化了代碼,去掉了 requested
溢出的處理,就是說當 requested + n
比 LONG_MAX
還要大的時候,會防止其變成負數。
public void setProducer(Producer p) {
boolean passToSubscriber = subscriber != null && requested == NOT_SET;
producer = p;
if (passToSubscriber) {
subscriber.setProducer(producer);
} else {
if (requested == NOT_SET) {
producer.request(Long.MAX_VALUE);
} else {
producer.request(requested);
}
}
}
同樣,這里去掉了關于線程安全的代碼。
個人認為,setProducer
這個函數名字起的不好,因為這個函數除了設置 producer
成員變量之外,還會調用 produce.request
函數。
再來分析一下這個 setProducer
函數
- 底層是否掉用過本層的
request(n)
。- 如果調用過,
requested != NOT_SET
,意味著底層出發了流控請求。 - 如果沒有調用過,
requested == NOT_SET
,意味著底層沒有出發了流控請求。
- 如果調用過,
-
producer
是真正處理流控的邏輯。subscriber
把流控邏輯交給producer
處理。如果沒有producer
,subscriber
也就只能簡單的計數,根本處理不了流控。 - 如果在觸發流控請求之前,
setProducer
函數被調用,那么要看本層是否愿意處理這個流控請求。- 如果成員變量
subscriber
不是空,那么表示本層 Subscriber 不愿意,或者不能夠處理好這個 backpressure ,交個上層處理subscriber.setProducer(producer)
。 - 上層如果不產生數據,本層的
OnNext
也不會觸發。從而達到了流控的目的。這樣一層一層往上傳,一直要交給數據源那一層才好處理。換句話說,如果你需要創建了一個 Observable,例如你寫了一個新的 operator ,但是不能很好地處理 backpressure ,那么最好往上傳遞。在OnSubscribe
的時候,把本層subscriber
和上層subscriber
串起來。 - 如果本層愿意處理 backpressure 請求,那么就調用
procuder.request
處理請求。
- 如果成員變量
- 如果是在觸發流控請求之后,
setProducer
被調用,那么無論本層是否愿意,都要處理這個請求。
代碼雖短,這個邏輯太復雜了。
小結
這里剛剛是一個皮毛,真正的 producer 處理流控邏輯還沒有提到。下次有時間,專門分析一個真正的流控邏輯。
同時,我們也看到,最好不要自己寫 operator 和 OnSubscribe ,而是調用現成的 from
系列函數, createSync
之類的提供流控的工廠方法,構造 Observable。