作者:閒魚技術——鹿緣
閒魚社群主要頁面採用Native實現,部分使用Flutter和Weex承接。帖子、話題等固定資料結構的處理,點贊、評論等使用者互動和狀態同步,這些資料邏輯大部分是重複的,而且在多技術棧實現性價極低。由此我們想,能否在端上實現這樣的一套工具,解放勞動力的同時,擺脫對服務端BFF層的依賴,保證研發效能。 抽象地看,實際上我們需要的正是一個邏輯跨平臺工具。邏輯跨平臺概念由來已久,也有一些很優秀的方案可以參考:
•C++語言在Objective-C/Java等Native語言都有成熟介面可以呼叫,這使得C++有天然的跨平臺優勢。同時不可否認的,C++入門門檻會比較高,導致後期維護成本大。
•KMM是JetBrains推出的用於跨平臺移動開發SDK,提供了一個語言級別的邏輯跨平臺解決方案,可以將直接程式碼編譯為與目標平臺完全相同的格式。
結合團隊內現有的一些技術基建,最終我們使用Dart完成了這一設計,在不同平臺保證資料和邏輯處理的一致性,節約人力資源的同時保證使用者體驗。取名Flutter Worker。
整體架構設計
在進行整體的設計之前,首先限定worker的業務場景:目標是提供一個多端可複用的邏輯處理中心,端上發起資料請求,所有的邏輯處理在FlutterWorker收口,執行完成返回回撥給端上。設想中,在worker寫Handler只需要關注邏輯處理,對於資料的傳入和在端上的接收,開發者只需要指定型別,worker會自動把資料轉化成該型別。
由此,在整體的架構設計中,我們主要考慮一下幾個方面:
1.FlutterWorker需要一個穩定的執行環境,由於涉及到資料處理等可能會比較耗時的操作,需要保證不影響UI介面的繪製。2.處理後的資料要供給多端使用,資料結構必須對齊。3.提供足夠的切面,方便接入方擴充。4.為了保證執行時穩定可靠,需要新增監控及時發現問題,並且在上線前時,對效能進行測試評估。
整體的架構圖如下:
執行容器層:容器是worker執行的基礎,在保證效能的前提下,要儘可能的利用閒魚現有的技術基建。
狀態管理層:worker在這裡完成資料的儲存和狀態的同步。端上儲存狀態資料,通過訂閱狀態訊息來接收資料的變動。考慮到狀態管理層在目前沒有業務的強需求,這部分僅停留在Demo階段還未落地,後面不再進行闡述。
資料處理層:包含了worker所有的中間層資料處理部分,主要包括Model物件同步、資料轉換和資料型別同步。基礎資料型別在Dart語言中天然支援,在iOS端Androiddr端都有對應的型別,因此,worker工作主要在後兩者。
監控層:worker線上上執行,需要有監控來保障,及時發現問題及時響應。
下面對各個模組進行詳細的介紹。
執行容器層
FlutterWorker的執行環境強依賴FlutterEngine和Isolate,從這個角度來考慮,對以下三種方案做了梳理和對比。
值得一提的是,隨著Flutter2.0推出,我們在更新日誌中注意到有一項對多開Flutter Engine例項記憶體消耗的優化。據官方文件稱,額外Flutter引擎的靜態記憶體佔用量降低了約 99%,使每個例項的佔用量大約為180KB。結合原始碼來看,這裡是新提供了FlutterEngineGroup類來建立Flutter Engine。
相較於原來的API,FlutterEngineGroup 生成的FlutterEngine具有常用共享資源(例如 GPU 上下文、字型度量和隔離執行緒的快照)的效能優勢,從而加快首次渲染的速度、降低延遲並降低記憶體佔用。
同時 EngineGroup 本身和其生成的 FlutterEngine不需要持續保活,只要有 1 個可用的FlutterEngine,就可以隨時在各個 FlutterEngine 之間共享資源。EngineGroup銷燬後,已生成的FlutterEngine也不受影響,只是無法繼續在現有共享的基礎上建立新引擎。
雖然現在FlutterEngineGroup還不是一個穩定API,但是這為FlutterWorker提供了另一種可能。綜合考慮,目前實現選擇了方案3來實現。
資料處理層
從使用者呼叫開始,worker的資料流圖如下圖所示:
資料在三端的流通,是通過呼叫NatiaveWorker,執行到對應的WorkerHandler,處理完成後再以非同步的方式回撥到Native方法。中間主要涉及Model物件同步和資料轉換過程,worker對這裡分別實現了處理庫。
Model物件同步
worker首先要實現的是,是OringinDataModel ==> TargetDataModel。TargetDataModel的定義在 iOS/Android/Flutter 三端的表現形式要保持一致,直接用程式碼生成是最好的選擇。這裡參考一些硬體協議,指定IDL格式來生成三端Model並匯入工程。封裝的dartGen庫可以解析這些yaml節點,生成我們需要的胖Model。
方便開發者使用,dartGen打包成了cli工具。模仿GsonFormat外掛,json資料在IDE中可以方便轉成對應的dartGen可以解析的yaml協議。對於其他生成Model的需求,可以以很小的代價修改模板,定製化高。
資料轉換
通過Flutter的MethodChannel向Native傳輸資料,需要經過一層messager編碼,而且編碼資料為基礎型別。所以在BinaryMessager的基礎上,封裝了WorkerBinaryMessenger方便做定製化處理。
iOS端和Android端有成熟的model轉換庫(YYModel和FastJson)。在Dart這裡,基於fish_serializable同樣做了一些定製處理,在生成的Model裡提供toJson和fromJson方法等,保證各種型別資料,包括Map和List等內包泛型類,能夠很方便地進行轉換。
監控方案設計
每個監控方案都有其業務場景的侷限性,在目前階段,主要考慮邏輯一致性。worker內單次的邏輯閉環類似tcp握手和揮手的過程,應用內的資料流轉類似訊息佇列,其傳遞過程如圖:
由此我們確定workerMonitor執行的流程圖如下:
效能監控
在該模式下的主流監控方案,會根據時間切片來觀察 發起方/接收方的佇列資料情況,時間切片有兩種方式,第一種是設定定時器,第二種是在每次有資料呼叫時處理。
切片方案
優點
缺點
定時器切片
切片實現簡單,切片內資料精準
效能損耗大,會有很多的無效統計
每次呼叫切片
效能損耗小
切片內資料為平均值
丟失應用內最後一段切片資料
首段資料阻塞時無法監控
另外上報的方式也分為實時上報和聚合上報。考慮到目前worker的使用場景有閒時和忙時的明顯區分,並且端上的長駐的頻繁的定時上報任務對ui繪製會有一定影響,最終採用每次資料呼叫時統計佇列內資料,同時在每次呼叫後增加超時定時器,如果有下次呼叫則取消超時定時器。
指標
線上上監控指標建設方面,參考rabbitmq監控,先定義初期需要觀測的指標。初期核心關注訊息丟失的情況和訊息時延長的情況。由此我們確定指標如下:
Exception監控
參考ui引擎對crash的監控,會在入口函式處統一接受異常資訊以及異常堆疊,然後統一上報處理。這裡在閒魚公眾號Flutter高可用相關文章中有詳細介紹。
可行性驗證
最後實現完成,呼叫程式碼及對應的Handler示例如下:
•iOS端:
•Android端:
•Handler:
為了測試我們的方案能否達到上線標準,在會玩首頁場景下做暴力測試來驗證。對比正常Native程式碼和worker處理資料,測試多次併發訪問/有序訪問條件下CPU、記憶體以及時延等表現。
通過多次對比試驗,FlutterWorker方案在常見和高壓的請求場景下短期記憶體和cpu水位增量少於30M,長期記憶體和cpu水位增量小於5M,並且時延低於原有方案的5%。
三端接入後,資料處理只需要投入一個人力就可供多端使用,大量減少了重複工作,提升了研發效能。
總結和展望
當前跨平臺是移動開發的趨勢之一,各個公司、團隊各有建樹,百家爭鳴。究其根本目的都是想提升研發效率,降低維護成本。各個方案都各有自己的獨特優勢,應該理性分析團隊和業務的現狀,選擇適合的方案。
Flutter Worker從邏輯跨平臺的視角切入,取得了一些不錯的效果。不過我們目前只完成了小部分的工作,仍有很多未完善的地方,在未來主要會從下面幾個方面繼續深入研究:
1.MethodChannel優化。資料通道的通訊強依賴MethodChannel,從目前測試結果來其效能表現可以cover目前的業務場景,但長遠來看,通道會成為worker效能瓶頸。從dart_native庫得到啟發,以型別對映指標的方式來替代原生的通道是一條可行的路。2.單元測試。邏輯處理函式有明確的輸入和輸出,寫單測的效率很高。而且快速迭代的產品,很多是在原有的邏輯基礎上新增新的邏輯,寫單測的價效比較高。3.前端容器支援。先行版本只支援了iOS、Android和Flutter,對於Weex和H5前端頁面並沒有很好的支援,未來期望能夠提供優雅的方式接入前端容器。
這是閒魚在Flutter探索道路上做的一點小嚐試,歡迎大家保持關注。