像阿里其餘技術團隊以及業界的作法同樣,閒魚的大多數後端應用都是所有使用java來實現的。java易用、豐富的庫、結構容易設計的特性決定了它是進行業務開發的最好語言之一。後端應用中數據的存儲、訪問、轉換、輸出雖然都屬於後端的範疇,可是其中變動的頻率是不一樣的。一般領域對象肯定以後,它的變化是不多的,可是客戶端展現的變化不少,致使接口層(或者叫粘連前臺和後臺的膠水層)的變化很是快。大多數web應用採用統一的技術棧來實現後端,膠水層跟領域層使用統一技術,這樣的作法仍然有能夠優化的地方:javascript
閒魚技術團隊選擇使用dart做爲膠水層的實現語言。前端
// java同步代碼 ItemDetailDO queryItemDetail(Long itemId) { ItemDetailDO data = new ItemDetailDO(); data.setBrowseCount(IdleItemBrowseService.count(itemId));// 多少人看過 data.setFavorCount(IdleItemFavorService.count(itemId));// 多少人點贊 return data; } // dart異步代碼 ItemDetailDO queryItemDetail(int itemId) async { var data = new ItemDetailDO(); await Future.wait([ IdleItemBrowseService.count(itemId).then((count) => data.browseCount = count) .catchError((exp, stackTrace) => logError('$exp $stackTrace')), IdleItemFavorService.count(itemId).then((count) => data.favorCount = count) .catchError((exp, stackTrace) => logError('$exp $stackTrace')) ]); return data; } // rxjava異步代碼 ItemDetailDO queryItemDetail(Long itemId) { ItemDetailDO data = new ItemDetailDO(); Flowable<Long> browseCountFlow = Flowable.fromCallable( () => IdleItemBrowseService.count(itemId) ).onErrorReturn(t -> 0).subscribeOn(Schedulers.io()); Flowable<Long> favorCountFlow = Flowable.fromCallable( () => IdleItemFavorService.count(itemId) ).onErrorReturn(t -> 0).subscribeOn(Schedulers.io()); Flowable.zip(browseCountFlow, favorCountFlow, (browseCount, favorCount) -> { data.setBrowseCount(browseCount); data.setFavorCount(favorCount); }).blockingFirst(); }
在java中咱們也普遍使用RxJava這種強大的響應式擴展實施異步操做:RxJava做爲java的響應式編程擴展,功能很是強大全面,它使用流的概念封裝全部的異步操做。須要注意的是這裏的兩個服務調用都被放到一個IO線程池中運行, 這個線程池是無界的,容易消耗線程這種系統稀缺的資源。這意味着當流量很是大的時候,系統的線程池很容易被打滿,須要設置合理的背壓策略。
從上面的代碼中能夠看到「數據獲取」,「數據組裝」的邏輯很是清晰,不像同步代碼分散在各處;相比於同步操做,dart的異步操做容許咱們同時等待多個IO事件,下降總的響應時間。dart的異步代碼擁有同步代碼的簡潔容易理解的優勢,又具備異步編程的性能優點。
dart異步的原理也是容易理解的。做爲單線程語言,dart依靠事件循環運行代碼。dart從main函數開始執行,咱們在main函數裏面建立Future,至關於在一個dart內部維護的事件隊列(event queue)中添加計劃任務(添加的任務並不會當即執行)。main中的代碼執行完以後,dart事件循環開始從事件隊列中依次獲取任務執行。async/await是dart的語法糖,它容許開發人員可以以書寫同步代碼的方式來實施異步編程(在C#、javascript中也有相似實現)。被async修飾的方法返回一個Future,調用這樣的方法,至關於建立一個Future。await一個Future,至關於把await以後的代碼打包放在Future.then()的代碼塊裏,這樣就保證以後的代碼在Future以後執行。因爲任務存儲於事件隊列,dart在流量大的時候,內存消耗較大,也須要咱們前期合理評估需求和分配系統資源。java
爲了提升開發效率,咱們利用dart的特性構建了一套高效的隔離開發環境。在業務開發實踐中,咱們總結出基本的開發架構和代碼模式。在這些技術基礎上,開發了閒魚寶貝詳情頁的主幹業務。下面逐一介紹。python
咱們以往的開發場景是:提交代碼 -> 代碼衝突(多人共用一個部署環境) -> 構建/部署 -> 經過接口驗證 -> 提交fix -> 構建/部署 -> 驗證 的迭代。在這個過程當中,開發人員有可能須要親自解決代碼衝突,或者依賴別人解決代碼衝突,須要等待構建/部署的時間(少則5分鐘,多則十幾分鍾)。並且這個過程可能須要迭代屢次,時間成本很高,若是由於其餘開發人員的代碼分支的問題致使部署失敗,那麼等待驗證的時間成倍增長。這樣的開發效率顯然不是特別理想。c++
在閒魚的dart應用中,這種問題會獲得緩解。每一個開發人員使用本身獨立的開發環境,開發環境使用每一個人的工號惟一識別。在不須要提交代碼的狀況下,開發人員把代碼部署到遠程預發環境中,並在本地調用預發服務,查看服務的輸出,作到本地驗證調試的效果,極大地提升了開發效率。由於只會有開發本身單一分支的代碼部署,不會牽扯到代碼衝突。整個過程,部署、服務調用過程十分快速,能夠在10秒內完成。驗證和調試的效率很是高。web
每一個開發人員的獨立開發環境對應預發機器上的一個isolate。dart的isolate至關於一個線程,可是不會和其餘isolate共享內存,isolate之間的通訊經過發送、接收消息完成。閒魚技術團隊使用每一個開發人員的代碼建立一個isolate,使用工號做爲標識,代碼能夠全量替換掉運行中的isolate,也可使用熱部署增量替換掉isolate中更改的功能。整個過程很是快。在早期使用dart原生的編譯器,發現速度較慢(10多秒)後,咱們對dart編譯器作了裁剪和優化,把編譯時間從10多秒下降到幾百毫秒(簡單來講就是,把dart原生的編譯器的附加功能,從新封裝,而後經過JIT/AOT生成新的編譯工具)。通過咱們對dart開發環境的加強,如今開發dart膠水層接口,只須要點擊開發工具上的一個按鈕,就能夠把修改的代碼,在幾秒內部署到遠程的預發環境,並調用當前的開發接口,在本地查看輸出。得到和在預發環境上驗證同樣的效果,可是體驗就像在開發一個徹底不依賴外部的本地應用程序。編程
業務開發中最重要的部分是分離出變化和不變的部分,變化的部分用最靈活、快捷的方式實現(變的最多的地方固然用最快的方式處理),不變的部分使用穩定、高效的方式實現。咱們已經把dart建設成爲一種可以高效開發,而且適合客戶端、前端、後端技術人員共同使用的技術。這種技術最適合應用於發生快速變化的接口層,也就是客戶端和後端交互的地方,業務需求的變化致使這裏的數據結構快速變化,也稱之爲膠水層。對於相對穩定的數據服務,咱們使用java實現爲領域服務。
上圖是服務之間的交互圖,實現方式以下圖所示:
膠水層dart應用以HTTP協議方式做爲MTOP接口提供給客戶端調用,往下使用HSF從Java應用中獲取數據。
一般先定義並開發好領域服務,而後再與客戶端對接開發出接口,領域服務提供的接口,包含了獲取基礎數據的全部方法,開發好以後,不多發生變化;膠水層獲取領域服務提供的數據,對數據進行加工、裁剪、組裝,輸出爲客戶端可以解析的視圖數據,客戶端解析、渲染、展現爲頁面。膠水層的代碼大體能夠分爲:獲取數據,而後數據處理和組裝。抽象出代碼模式以下所示:後端
// 數據處理和組裝 void putTiger(Zoo zoo, Tiger tiger) => zoo.tiger = tiger; void putDophin(Zoo zoo, Dophin dophin) => zoo.dophin = dophin; void putRatel(Zoo zoo, Ratel ratel) => zoo.ratel = ratel; // 發起多個異步請求,全部請求完成後返回全部數據 Future<T> catchError<T>(Future<T> future) { return future.catchError((e, st) => LogUtils.error('$e $st')); } Future<List<T>> waitFutures<T>(List<Future<T>> futures) { Future<List<T>> future = Future.wait(futures.map(catchError)); return catchError(future); } // 服務接口 Future<Zoo> process(Parameter param) async { var zoo = new Zoo(); // 數據獲取 await waitFutures( Service1.invoke(param).then((animal) -> putTiger(zoo, animal)), Service2.invoke(param).then((animal) -> putLion(zoo, dophin)), Service3.invoke(param).then((animal) -> putRatel(zoo, animal)) ); return finalData; }
爲了使用java的領域服務,咱們首先解決了dart和java之間數據交互問題,主要是經過序列化對java類文件和dart類文件進行合理的轉換,保證dart可以透明、簡潔地使用java的數據結構,調用java的遠程服務;在調用鏈路上設置全局惟一的上下文id,跨越dart和java調用棧,支持全鏈路排查;對全部的服務的成功率,rt和額外業務參數有詳細的日誌,能夠配置以日誌爲數據源的監控告警等等(後續的文章將詳細介紹咱們對這些問題的詳細解決方案,請持續關注哦)。緩存
閒魚寶貝詳情頁是咱們使用dart開發的一個重要項目。最先的閒魚寶貝詳情頁把各個業務的代碼邏輯耦合在一塊兒,致使維護和變動困難,穩定性也難以保證。咱們設計的swak框架(更多細節請查看文章swak框架),可以分離垂直業務的共性和差別性,把閒魚寶貝詳情頁的實現分割成主幹實現和垂直業務實現兩塊。咱們使用本身開發的dart後端開發框架,對swak框架作了最小實現。項目完成了詳情頁主幹的完整功能和基礎優化:數據結構
解決緩存調用的差別性:咱們依賴緩存的c++接口訪問緩存,可是仍然須要處理java/c++緩存讀寫不兼容問題完成dart和java對同一緩存的同時讀寫
項目流程圖可見下圖:
![dart-detail-flow.png](http://gw.alicdn.com/mt/TB1Pyv6V9zqK1RjSZFjXXblCFXa-558-561.png)
目前該項目已經上線超過6周,qps最高可達400,成功率在99.5%以上。整個調用鏈路的RT與一樣功能的java應用持平。因爲前期的精心設計,領域服務不多改動,大部分變動發生在dart膠水層。從上線後經歷的若干次變動來看,dart膠水層從修改代碼結束到提供給客戶端使用總耗時不超過2分鐘,而相同功能的java應用須要10分鐘以上。
dart是一門簡潔、容易上手、對異步支持良好的編程語言,在flutter的開發中大放異彩。在咱們的努力下,dart用於後端開發的支持逐漸完善,前臺開發同窗和後端開發人員快速高效地開發膠水層接口。咱們在不少生產項目中使用了dart用於後端開發,性能、穩定性良好,開發效率大大提升。將來咱們會着力於進一步改善dart開發體驗、與java項目的兼容性、提高dart遠程服務的性能,挖掘dart在後端開發中更大的潛力。
原文連接 本文爲雲棲社區原創內容,未經容許不得轉載。