閒魚是如何利用RxJava提升異步編程能力的

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RxJava是Java對於反應式編程的一個實現框架,是一個基於事件的、提供實現強大且優雅的異步調用程序的代碼庫。18年以來,由淘寶技術部發起的應用架構升級項目,希望通過反應式架構、全異步化的改造,提升系統整體性能和機器資源利用率,減少網絡延時,資源的重複使用,併爲業務快速創新提供敏捷的架構支撐。在閒魚的基礎鏈路諸如商品批量更新、訂單批量查詢等,都利用了RxJava的異步編程能力。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不過,RxJava是入門容易精通難,一不小心遍地坑。今天來一起看下RxJava的使用方式、基本原理、注意事項。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"開始之前"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"讓我們先看下,使用RxJava之前,我們曾經寫過的回調代碼存在的痛點。當我們的應用需要處理用戶事件、異步調用時,隨着流式事件的複雜性和處理邏輯的複雜性的增加,代碼的實現難度將爆炸式增長。比如我們有時需要處理多個事件流的組合、處理事件流的異常或超時、在事件流結束後做清理工作等,如果需要我們從零實現,勢必要小心翼翼地處理回調、監聽、併發等很多棘手問題。還有一個被稱作“回調地獄”的問題,描述的是代碼的不可讀性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Code 1.1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/3e\/3e487f99993e9fbb9dff9fb40a9a0936.jpeg","alt":"圖片","title":"null","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上js代碼有兩個明顯槽點:1.由於傳入的層層回調方法,代碼結尾出現一大堆的 }) ;2. 代碼書寫的順序與代碼執行的順序相反:後面出現回調函數會先於之前行的代碼先執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"而如果使用了RxJava,我們處理回調、異常等將得心應手。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"引入RxJava"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"假設現在要異步地獲得一個用戶列表,然後將結果進行處理,比如展示到ui或者寫到緩存,我們使用RxJava後代碼如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Code 2.1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Observable observable = Observable.create(new ObservableOnSubscribe() {\n @Override\n public void subscribe(@NotNull ObservableEmitter emitter) throws Exception {\n System.out.println(Thread.currentThread().getName() + \"----TestRx.subscribe\");\n List result = userService.getAllUser();\n for (UserDo st : result) {emitter.onNext(st);}\n }\n});\nObservable map = observable.map(s -> s.toString());\n\/\/ 創建訂閱關係\nmap.subscribe(o -> System.out.println(Thread.currentThread().getName() + \"----sub1 = \" + o)\/*更新到ui*\/);\nmap.subscribe(o -> System.out.println(Thread.currentThread().getName() + \"----sub2 = \" + o)\/*寫緩存*\/,\n e-> System.out.println(\"e = \" + e)),\n ()->System.out.println(\"finish\")));"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"userService.getAllUser()是一個普通的同步方法,但是我們把它包到了一個Observable中,當有結果返回時,將user逐個發送至監聽者。第一個監聽者更新ui,第二個監聽者寫到緩存。並且當上遊發生異常時,進行打印;在事件流結束時,打印finish。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另外還可以很方便的配置上游超時時間、調用線程池、fallback結果等,是不是非常強大。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要注意的是,RxJava代碼就像上面例子中看起來很容易上手,可讀性也很強,但是如果理解不充分,很容易出現意想不到的bug:初學者可能會認爲,上面的代碼中,一個user列表返回後,每個元素會被異步地發送給兩個下游的觀察者,這兩個觀察者在各自的線程內打印結果。但事實卻不是這樣:userService.getAllUser()會被調用兩次(每當建立訂閱關係時方法getAllUser()都會被重新調用),而user列表被查詢出後,會"},{"type":"text","marks":[{"type":"strong"}],"text":"同步"},{"type":"text","text":"的發送給兩個觀察者,觀察者也是"},{"type":"text","marks":[{"type":"strong"}],"text":"同步"},{"type":"text","text":"地打印出每個元素。即sub1 = user1,sub1 = user2,sub1 = user3,sub2 = user1,sub2 = user2,sub2 = user3。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可見,如果沒有其他配置,RxJava"},{"type":"text","marks":[{"type":"strong"}],"text":"默認是同步阻塞"},{"type":"text","text":"的!!!那麼,我們如何使用它的異步非阻塞能力呢,我們接着往下看。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Code 2.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Observable\n .fromCallable(() -> {\n System.out.println(Thread.currentThread().getName() + \"----observable fromCallable\");\n Thread.sleep(1000); \/\/ imitate expensive computation\n return \"event\";\n })\n .subscribeOn(Schedulers.io())\n .observeOn(Schedulers.single())\n .map(i->{\n System.out.println(Thread.currentThread().getName() + \"----observable map\");\n return i;\n })\n .observeOn(Schedulers.newThread())\n .subscribe(str -> System.out.println(Thread.currentThread().getName() + \"----inputStr=\" + str));\n\nSystem.out.println(Thread.currentThread().getName() + \"----end\");\n\nThread.sleep(2000); \/\/ even = Observable.interval(1000, TimeUnit.MILLISECONDS).map(i -> i * 2L);\n\/\/第二個流每3秒輸出一個奇數\nObservable odd = Observable.interval(3000, TimeUnit.MILLISECONDS).map(i -> i * 2L + 1);\n\/\/zip也可以傳入多個流,這裏只傳入了兩個\nObservable.zip(even, odd, (e, o) -> e + \",\" + o).forEach(x -> {\n System.out.println(\"observer = \" + x);\n});\n\n\/* 輸出如下,可以看到,當某個流有元素到來時,會等待其他所有流都有元素到達時,纔會合併處理然後發給下游\nobserver = 0,1\nobserver = 2,3\nobserver = 4,5\nobserver = 6,7\n...\n*\/"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼code 3.1看起來沒什麼問題,兩個流併發執行,最後用zip等待他們的結果。但是卻隱藏了一個很重要的問題:RxJava默認是同步、阻塞的!!當我們想去仿照上面的方式併發發送多個請求,最後用zip監聽所有結果時,很容易發先一個詭異的現象, code 3.2的代碼中,ob2的代碼總是在ob1執行之後纔會執行,並不是我們預期的兩個請求併發執行。而打印出來的線程名也可以看到,兩個Single是在同一個線程中順序執行的!"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code 3.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ Single是隻返回一個元素的Observable的實現類\nSingle ob1 = Single.fromCallable(() -> {\n System.out.println(Thread.currentThread().getName() + \"----observable 1\");\n TimeUnit.SECONDS.sleep(3);\n return userService.queryById(1).getName();\n });\n\nSingle ob2 = Single.fromCallable(() -> {\n System.out.println(Thread.currentThread().getName() + \"----observable 2\");\n TimeUnit.SECONDS.sleep(1);\n return userService.queryById(1).getName();\n });\n\nString s = Single.zip(ob1, ob2, \n (e, o) -> {System.out.println(e + \"++++\" + o);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那爲什麼code 3.1的兩個流能夠併發執行呢?閱讀源碼可以發現zip的實現其實就是先訂閱第一個流,再訂閱第二個流,那麼默認當然是順序執行。但是通過Observable.interval創建的流,默認會被提交到 Schedulers.computation()提供的線程池中。關於線程池,本文後面會講解。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"創建API"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"create :最原始的create和subscribe,其他創建方法都基於此"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code 3.3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ 返回的子類是ObservableCreate\nObservable observable = Observable.create(new ObservableOnSubscribe() {\n @Override\n public void subscribe(ObservableEmitter emitter) throws Exception {\n emitter.onNext(\"event\");\n emitter.onNext(\"event2\");\n emitter.onComplete();\n }\n});\n\/\/ 訂閱observable\nobservable.subscribe(new Observer() {\n @Override\n public void onSubscribe(Disposable d) {\n System.out.println(Thread.currentThread().getName() + \" ,TestRx.onSubscribe\");\n }\n @Override\n public void onNext(String s) {\n System.out.println(Thread.currentThread().getName() + \" ,s = \" + s);\n }\n @Override\n public void onError(Throwable e) {}\n @Override\n public void onComplete() {\n System.out.println(Thread.currentThread().getName() + \" ,TestRx.onComplete\");\n }\n});"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"just :Observable.just(\"e1\",\"e2\"); 簡單的創建一個Observable,發出指定的n個元素。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"interval:code 3.1已給出示例,創建一個按一定間隔不斷產生元素的Observable,默認執行在Schedulers.comutation()提供的線程池中"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"defer:產生一個延遲創建的Observable。有點繞:Observable.create等創建出來的被觀察者雖然是延遲執行的,只有有人訂閱的時候纔會真正開始生成數據。但是創建Observable的方法卻是立即執行的。而 Observable.defer方法會在有人訂閱的時候纔開始創建Observable。如代碼Code3.4"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public String myFun() {\n String now = new Date().toString();\n System.out.println(\"myFun = \" + now);\n return now;\n}\n\npublic void testDefer(){\n \/\/ 該代碼會立即執行myFun()\n Observable ob1 = Observable.just(myFun());\n \/\/ 該代碼會在產生訂閱時,纔會調用myFun(), 可類比Java8的Supplier接口\n Observable ob2 = Observable.defer(() -> Observable.just(myFun()) ); \n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"fromCallable :產生一個延遲創建的Observable,簡化的defer方法。Observable.fromCallable(() -> myFun()) 等同於Observable.defer(() -> Observable.just(myFun()) );"}]}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"基本原理"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RxJava的代碼,就是觀察者模式+裝飾器模式的體現。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Observable.create"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"見代碼code 3.3,create方法接收一個ObserverableOnSubscribe接口對象,我們定義了了發送元素的代碼,create方法返回一個ObserverableCreate類型對象(繼承自Observerable抽象類)。跟進create方法原碼,直接返回new出來的ObserverableCreate,它包裝了一個source對象,即傳入的ObserverableOnSubscribe。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" public static Observable create(ObservableOnSubscribe source) {\n ObjectHelper.requireNonNull(source, \"source is null\");\n \/\/onAssembly默認直接返回ObservableCreate\n return RxJavaPlugins.onAssembly(new ObservableCreate(source)); "}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Create方法就這麼簡單,只需要記住它返回了一個包裝了source的Observerble。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.2 Observerable.subscribe(observer)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看下code3.3中創建訂閱關係時(observalbe.subscribe)發生了什麼:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" public final void subscribe(Observer super T> observer) {\n ObjectHelper.requireNonNull(observer, \"observer is null\");\n try {\n observer = RxJavaPlugins.onSubscribe(this, observer);\n ObjectHelper.requireNonNull(observer, \"Plugin returned null Observer\");\n subscribeActual(observer);\n } catch (NullPointerException e) {... } catch (Throwable e) {... }\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Observable是一個抽象類,定義了subscribe這個final方法,最終會調用subscribeActual(observer);而subscribeActual是由子類實現的方法,自然我們需要看ObserverableCreate實現的該方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.3"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ObserverableCreate實現的subscribeActual方法\nprotected void subscribeActual(Observer super T> observer) {\n CreateEmitter parent = new CreateEmitter(observer);\n observer.onSubscribe(parent);\n\n try {\n source.subscribe(parent); \/\/source是ObservableOnSubscribe,即我們寫的生產元素的代碼\n } catch (Throwable ex) {...}\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.將觀察者observer包裝到一個CreateEmitter裏。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.調用observer的onSubscribe方法,傳入這個emitter。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.調用source(即生產代碼接口)的subscribe方法,傳入這個emitter。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步中,直接調用了我們寫的消費者的onSubscribe方法,很好理解,即創建訂閱關係的回調方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"重點在第三步,source.subscribe(parent); 這個parent是包裝了observer的emitter。還記得source就是我們寫的發送事件的代碼。其中手動調用了emitter.onNext()來發送數據。那麼我們CreateEmitter.onNext()做了什麼"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.4"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public void onNext(T t) {\n if (t == null) {...}\n if (!isDisposed()) { observer.onNext(t); }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"!isDisposed()判斷若訂閱關係還沒取消,則調用observer.onNext(t);這個observer就是我們寫的消費者,code 3.3中我們重寫了它的onNext方法來print接收到的元素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"以上就是RxJava最基本的原理,其實邏輯很簡單,就是在創建訂閱關係的時候,直接調用生產邏輯代碼,然後再生產邏輯的onNext中,調用了觀察者observer.onNext。時序圖如下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/0e\/0e9be10749ecf12521ee2e9c696dcebe.png","alt":"圖片","title":"null","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"顯然,最基本的原理,完全解耦了和異步回調、多線程的關係。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Observable.map"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過最簡答的map方法,看下轉換api做了什麼。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如Code2.1中,調用map方法,傳入一個轉換函數,可以一對一地將上游的元素轉換成另一種類型的元素。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.5"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" public final Observable map(Function super T, ? extends R> mapper) {\n ObjectHelper.requireNonNull(mapper, \"mapper is null\");\n return RxJavaPlugins.onAssembly(new ObservableMap(this, mapper));\n }"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.5是Observable定義的final的map方法,可見map方法將this(即原始的observer)和轉換函數mapper包裝到一個ObservableMap中(ObservableMap也繼承Observable),然後返回這個ObservableMap(onAssembly默認什麼都不做)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於ObservableMap也是一個Observable,所以他的subscribe方法會在創建訂閱者時被層層調用到,subscribe是Observable定義的final方法,最終會調用到他實現的subscribeAcutal方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.6"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ObservableMap的subscribeActual\npublic void subscribeActual(Observer super U> t) {\n source.subscribe(new MapObserver(t, function));\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到ObservableMap的subscribeActual中,將原始的觀察者t和變換函數function包裝到了一個新的觀察者MapObserver中,並將它訂閱到被觀察者source上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們知道,發送數據的時候,觀察者的onNext會被調用,所以看下MapObserver的onNext方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.7"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"@Override\npublic void onNext(T t) {\n if (done) {return; }\n if (sourceMode != NONE) { actual.onNext(null);return;}\n U v;\n try {\n v = ObjectHelper.requireNonNull(mapper.apply(t), \"The mapper function returned a null value.\");\n } catch (Throwable ex) {...}\n actual.onNext(v);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.7中可以看到mapper.apply(t)將變換函數mapper施加到每個元素t上,變換後得到v,最後調用actual.onNext(v)將v發送給下游觀察者actual(actual爲code4.6中創建MapObserver時傳入的t)。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"總結一下例如map之類的變換api的原理:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.map方法返回一個ObservableMap,包裝了原始的觀察者t和變換函數function"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.ObservableMap繼承自AbstractObservableWithUpstream(它繼承自Observable)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.訂閱發生時,observable的final方法subscribe()會調用實現類的subscribeActual"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.ObservableMap.subscribeActual中創建MapObserver(包裝了原observer),訂閱到原Observable"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"5.發送數據onNext被調用時,先apply變換操作,再調用原observer的onNext,即傳給下游觀察者"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"線程調度"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代碼Code 2.2中給出了線程調度的示例。subscribeOn(Schedulers.io())指定了被觀察者執行的線程池。observeOn(Schedulers.single())指定了下游觀察者執行的線程池。經過了上面的學習,很自然的能夠明白,原理還是通過裝飾器模式,將Observable和Observer層層包裝,丟到線程池裏執行。我們以observeOn()爲例,見code4.8。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public final Observable observeOn(Scheduler scheduler, boolean delayError, int bufferSize) {\n ObjectHelper.requireNonNull(scheduler, \"scheduler is null\");\n ObjectHelper.verifyPositive(bufferSize, \"bufferSize\");\n \/\/observeOn(Scheduler) 返回ObservableObserveOn(繼承自Observable)\n return RxJavaPlugins.onAssembly(new ObservableObserveOn(this, scheduler, delayError, bufferSize));\n}\n\n\/\/ Observable的subscribe方法最終會調用到ObservableObserveOn.subscribeActual方法\nprotected void subscribeActual(Observer super T> observer) {\n if (scheduler instanceof TrampolineScheduler) {\n source.subscribe(observer);\n } else {\n Scheduler.Worker w = scheduler.createWorker();\n \/\/創建一個ObserveOnObserver包裝了原觀察者、worker,把它訂閱到source(原observable)\n source.subscribe(new ObserveOnObserver(observer, w, delayError, bufferSize));\n }\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.observeOn(Scheduler) 返回ObservableObserveOn"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.ObservableObserveOn繼承自Observable"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.所以subscribe方法最終會調用到ObservableObserveOn重寫的subscribeActual方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4.subscribeActual返回一個ObserveOnObserver(是一個Observer)包裝了真實的observer和worker"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據Observer的邏輯,發送數據時onNext方法會被調用,所以要看下ObserveOnObserver的onNext方法:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code4.9"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"public void onNext(T t) {\n if (done) { return; }\n if (sourceMode != QueueDisposable.ASYNC) { queue.offer(t);}\n schedule();\n}\n\nvoid schedule() {\n if (getAndIncrement() == 0) {\n worker.schedule(this); \/\/this是ObserveOnObserver,他同樣實現了Runable\n }\n}\n\npublic void run() {\n if (outputFused) {\n drainFused();\n } else {\n drainNormal(); \/\/最終會調用actual.onNext(v) , 即調用被封裝的下游觀察者,v是emmiter\n }\n}\n\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1.最終生產者代碼中調用onNext時,會調用schedule方法"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.schedule方法中,會提交自身(ObserveOnObserver)到線程池"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3.而run方法會調用onNext(emmiter)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可見,RxJava線程調度的機制就是通過observeOn(Scheduler)將發送元素的代碼onNext(emmiter)提交到線程池裏執行。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"使用注意"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後,給出幾個我們在開發中總結的注意事項,避免大家踩坑。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"適用場景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"並不是所有的IO操作、異步回調都需要使用RxJava來解決,比如如果我們只是一兩個RPC服務的調用組合,或者每個請求都是獨立的處理邏輯,那麼引入RxJava並不會帶來多大的收益。下面給出幾個最佳的適用場景。"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"處理UI事件"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"異步響應和處理IO結果"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"事件或數據 是由無法控制的生產者推送過來的"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"組合接收到的事件"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面給一個閒魚商品批量補數據的使用場景:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"背景:"},{"type":"text","text":"算法推薦了用戶的一些商品,目前只有基礎信息,需要調用多個業務接口,補充用戶和商品的附加業務信息,如用戶頭像、商品視頻連接、商品首圖等。並且根據商品的類型不同,填充不同的垂直業務信息。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"難點:"},{"type":"text","text":"1. 多個接口存在前後依賴甚至交叉依賴;2. 每個接口都有可能超時或者報錯,繼而影響後續邏輯;3.根據不同的依賴接口特點,需要單獨控制超時和fallback。整個接口也需要設置整體的超時和fallback。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"方案:"},{"type":"text","text":"如果只是多個接口獨立的異步查詢,那麼完全可以使用CompletableFuture。但基於它對組合、超時、fallback支持不友好,並不適用於此場景。我們最終採用RxJava來實現。下面是大致的代碼邏輯。代碼中的HsfInvoker是阿里內部將普通HSF接口轉爲Rx接口的工具類,默認運行到單獨的線程池中,所以能實現併發調用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/98\/98c59b3209bbf5975c0d113086de7a2d.jpeg","alt":"圖片","title":"null","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,通過引入RxJava,對於超時控制、兜底策略、請求回調、結果組合都能更方便的支持。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"Scheduler線程池"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RxJava2 內置多個 Scheduler 的實現,但是我們建議使用Schedulers.from(executor)指定線程池,這樣可以避免使用框架提供的默認公共線程池,防止單個長尾任務block其他線程執行,或者創建了過多的線程導致OOM。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"CompletableFuture"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當我們的邏輯比較簡單,只想異步調用一兩個RPC服務的時,完全可以考慮使用Java8提供的CompletableFuture實現,它相較於Future是異步執行的,也可以實現簡單的組合邏輯。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"併發"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"單個Observable始終是順序執行的,不允許併發地調用onNext()。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code5.1"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Observable.create(emitter->{\n new Thread(()->emitter.onNext(\"a1\")).start();\n new Thread(()->emitter.onNext(\"a2\")).start();\n})\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但是,每個Observable可以獨立的併發執行。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"code5.2"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"Observable ob1 = Observable.create(e->new Thread(()->e.onNext(\"a1\")).start());\nObservable ob2 = Observable.create(e->new Thread(()->e.onNext(\"a2\")).start());\nObservable ob3 = Observable.merge(ob1,ob2);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ob3中組合了ob1和ob2兩個流,每個流是獨立的。(這裏需要注意,這兩個流能併發執行,還有一個條件是他們的發送代碼運行在不同線程,就如果code3.1和code3.2中的示例一樣,雖然兩個流是獨立的,但是如果不提交到不同的線程中,還是順序執行的)。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"背壓"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 RxJava 2.x 中,只有 Flowable 類型支持背壓。當然,Observable 能解決的問題,對於 Flowable 也都能解決。但是,其爲了支持背壓而新增的額外邏輯導致 Flowable 運行性能要比 Observable 慢得多,因此,只有在需要處理背壓場景時,才建議使用 Flowable。如果能夠確定上下游在同一個線程中工作,或者上下游工作在不同的線程中,而下游處理數據的速度高於上游發射數據的速度,則不會產生背壓問題,就沒有必要使用Flowable。關於Flowable的使用,由於篇幅原因,就不在本文闡述。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"超時"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"強烈建議設置異步調用的超時時間,用timeout和onErrorReturn方法設置超時的兜底邏輯,否則這個請求將一直佔用一個Observable線程,當大量請求到來時,也會導致OOM。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"結語"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前,閒魚的多個業務場景都採用RxJava做異步化,大大降低了開發同學的異步開發成本。同時在多請求響應組合、併發處理都有很好的性能表現。自帶的超時邏輯和兜底策略,在批量業務數據處理中能保證可靠性,是用戶流暢體驗的強力支撐。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文轉載自:閒魚技術(ID:XYtech_Alibaba)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/tiBld-a_irgp4vIPNVDDTw","title":"xxx","type":null},"content":[{"type":"text","text":"閒魚是如何利用RxJava提升異步編程能力的"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章