在闲鱼,我们如何用Dart做高效后端开发?
背景
像阿里其他技術團隊以及業界的做法一樣,閑魚的大多數后端應用都是全部使用java來實現的。java易用、豐富的庫、結構容易設計的特性決定了它是進行業務開發的最好語言之一。后端應用中數據的存儲、訪問、轉換、輸出雖然都屬于后端的范疇,但是其中變更的頻率是不同的。通常領域對象確定之后,它的變化是很少的,但是客戶端展示的變化很多,導致接口層(或者叫粘連前臺和后臺的膠水層)的變化非常快。大多數web應用采用統一的技術棧來實現后端,膠水層跟領域層使用統一技術,這樣的做法仍然有可以優化的地方:
- 在預發環境中驗證調試比較困難:一方面,每次提交代碼、構建、部署、驗證的總時間相對較長;另一方面,多人共用一個部署環境,相互干擾(代碼沖突和部署沖突),增加了成本。后端開發人員都渴望有一個獨立、高效的開發環境,就像開發一個前端頁面那樣
- 前臺(java、object-c,javascript)和后臺(java)的技術不同,導致前臺同學很難開發后端程序,閑魚技術團隊為了追求更高的開發效率,希望能夠跨越服務端開發與客戶端、前端的界限,讓前臺開發人員也能夠寫后端代碼
- 膠水層通常依賴很多后端服務,計算比較簡單,是IO密集型的任務。我們理想中的編程框架是能夠像寫同步代碼一樣簡單,但是享受異步的好處。目前的方案還無法完全做到這一點。
為什么選擇dart
閑魚技術團隊選擇使用dart作為膠水層的實現語言。
- dart是一種靜態類型語言,在編譯器就能完全確定變量的類型。它是支持泛型的面向對象語言,任何變量都是對象,不存在java中的原始類型。跟javascript類似,它是一種單線程語言,對異步的支持非常好(async/await)。dart的語法與主流開發語言(java,python,c/c++,javascript)很類似, 在主流的語言語法基礎上,dart增加了很多語法結構,getter/setter、方法級聯、函數式編程、閉包,這些語法讓允許開發人員更加容易地寫出簡潔的代碼;全面易用的類庫也是dart能夠作為flutter開發語言的重要原因。
- flutter證明了dart在客戶端開發上的成功,閑魚不僅走在flutter開發的前列,也正在嘗試使用dart開發后端應用;語法跟javascript,java相近,有人形容這門語言是傻瓜式的簡單(stupid-simple to learn),無論是java后端開發人員,還是客戶端開發同學,亦或是前端開發同學,都能夠快速上手寫出生產級的代碼。所有技術同學都能夠開發后端接口在閑魚是可以做到的。
- dart對異步化的良好支持對業務開發是強大助力。后端應用膠水層代碼大多數IO密集型的任務,使用異步化技術可以把多個IO請求的總RT,從所有請求RT之和,降低為所有請求中最高RT。dart對異步有良好的支持,開發同學使用dart可以以近乎同步的代碼風格取得異步的性能。我們以閑魚寶貝詳情頁的代碼舉例,對比不同的編碼方式。
在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在流量大的時候,內存消耗較大,也需要我們前期合理評估需求和分配系統資源。
dart后端開發實戰
為了提高開發效率,我們利用dart的特性構建了一套高效的隔離開發環境。在業務開發實踐中,我們總結出基本的開發架構和代碼模式。在這些技術基礎上,開發了閑魚寶貝詳情頁的主干業務。下面逐一介紹。
高效的隔離開發環境
我們以往的開發場景是:提交代碼 -> 代碼沖突(多人共用一個部署環境) -> 構建/部署 -> 通過接口驗證 -> 提交fix -> 構建/部署 -> 驗證 的迭代。在這個過程中,開發人員有可能需要親自解決代碼沖突,或者依賴別人解決代碼沖突,需要等待構建/部署的時間(少則5分鐘,多則十幾分鐘)。而且這個過程可能需要迭代多次,時間成本很高,如果因為其他開發人員的代碼分支的問題導致部署失敗,那么等待驗證的時間成倍增加。這樣的開發效率顯然不是特別理想。
在閑魚的dart應用中,這種問題會得到緩解。每個開發人員使用自己獨立的開發環境,開發環境使用每個人的工號唯一識別。在不需要提交代碼的情況下,開發人員把代碼部署到遠程預發環境中,并在本地調用預發服務,查看服務的輸出,做到本地驗證調試的效果,極大地提高了開發效率。因為只會有開發自己單一分支的代碼部署,不會牽扯到代碼沖突。整個過程,部署、服務調用過程十分快速,可以在10秒內完成。驗證和調試的效率非常高。
每個開發人員的獨立開發環境對應預發機器上的一個isolate。dart的isolate相當于一個線程,但是不會和其他isolate共享內存,isolate之間的通信通過發送、接收消息完成。閑魚技術團隊使用每個開發人員的代碼創建一個isolate,使用工號作為標識,代碼可以全量替換掉運行中的isolate,也可以使用熱部署增量替換掉isolate中更改的功能。整個過程非???。在早期使用dart原生的編譯器,發現速度較慢(10多秒)后,我們對dart編譯器做了裁剪和優化,把編譯時間從10多秒降低到幾百毫秒(簡單來說就是,把dart原生的編譯器的附加功能,重新封裝,然后通過JIT/AOT生成新的編譯工具)。經過我們對dart開發環境的增強,現在開發dart膠水層接口,只需要點擊開發工具上的一個按鈕,就可以把修改的代碼,在幾秒內部署到遠程的預發環境,并調用當前的開發接口,在本地查看輸出。獲得和在預發環境上驗證一樣的效果,但是體驗就像在開發一個完全不依賴外部的本地應用程序。
業務開發架構
業務開發中最重要的部分是分離出變化和不變的部分,變化的部分用最靈活、快捷的方式實現(變的最多的地方當然用最快的方式處理),不變的部分使用穩定、高效的方式實現。我們已經把dart建設成為一種能夠高效開發,并且適合客戶端、前端、后端技術人員共同使用的技術。這種技術最適合應用于發生快速變化的接口層,也就是客戶端和后端交互的地方,業務需求的變化導致這里的數據結構快速變化,也稱之為膠水層。對于相對穩定的數據服務,我們使用java實現為領域服務。
上圖是服務之間的交互圖,實現方式如下圖所示:
膠水層dart應用以HTTP協議方式作為MTOP接口提供給客戶端調用,往下使用HSF從Java應用中獲取數據。
通常先定義并開發好領域服務,然后再與客戶端對接開發出接口,領域服務提供的接口,包含了獲取基礎數據的所有方法,開發好之后,很少發生變化;膠水層獲取領域服務提供的數據,對數據進行加工、裁剪、組裝,輸出為客戶端能夠解析的視圖數據,客戶端解析、渲染、展示為頁面。膠水層的代碼大致可以分為:獲取數據,然后數據處理和組裝。抽象出代碼模式如下所示:
為了使用java的領域服務,我們首先解決了dart和java之間數據交互問題,主要是通過序列化對java類文件和dart類文件進行合理的轉換,保證dart能夠透明、簡潔地使用java的數據結構,調用java的遠程服務;在調用鏈路上設置全局唯一的上下文id,跨越dart和java調用棧,支持全鏈路排查;對所有的服務的成功率,rt和額外業務參數有詳細的日志,可以配置以日志為數據源的監控告警等等(后續的文章將詳細介紹我們對這些問題的詳細解決方案,請持續關注哦)。
服務化詳情頁主干開發
閑魚寶貝詳情頁是我們使用dart開發的一個重要項目。最早的閑魚寶貝詳情頁把各個業務的代碼邏輯耦合在一起,導致維護和變更困難,穩定性也難以保證。我們設計的swak框架(更多細節請查看文章swak框架),能夠分離垂直業務的共性和差異性,把閑魚寶貝詳情頁的實現分割成主干實現和垂直業務實現兩塊。我們使用自己開發的dart后端開發框架,對swak框架做了最小實現。項目完成了詳情頁主干的完整功能和基礎優化:
- 垂直業務路由:我們使用dart中的zone存儲每個閑魚商品的業務標識,代碼生成的靜態代理類依據業務標識調用相應的服務,在主干數據里填充各個業務的獨有數據。zone是dart異步代碼的執行環境,能夠緩存一些可重用數據(業務代碼里除非非此不可,盡量不要多用)
- 作為遠程服務的提供方:在hsfcpp對hessian協議的實現基礎上做開發,dart也能成為遠程服務的提供方
- 服務調用的優化: 對java遠程服務的代理做了優化,隔離業務層面對框架層的感知,做到透明調用
-
解決緩存調用的差異性:我們依賴緩存的c++接口訪問緩存,但是仍然需要處理java/c++緩存讀寫不兼容問題完成dart和java對同一緩存的同時讀寫

項目流程圖可見下圖:
實際效果
目前該項目已經上線超過6周,qps最高可達400,成功率在99.5%以上。整個調用鏈路的RT與同樣功能的java應用持平。由于前期的精心設計,領域服務很少改動,大部分變更發生在dart膠水層。從上線后經歷的若干次變更來看,dart膠水層從修改代碼結束到提供給客戶端使用總耗時不超過2分鐘,而相同功能的java應用需要10分鐘以上。
總結
dart是一門簡潔、容易上手、對異步支持良好的編程語言,在flutter的開發中大放異彩。在我們的努力下,dart用于后端開發的支持逐漸完善,前臺開發同學和后端開發人員快速高效地開發膠水層接口。我們在很多生產項目中使用了dart用于后端開發,性能、穩定性良好,開發效率大大提高。未來我們會著力于進一步改善dart開發體驗、與java項目的兼容性、提升dart遠程服務的性能,挖掘dart在后端開發中更大的潛力。
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的在闲鱼,我们如何用Dart做高效后端开发?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于Alluxio系统的Spark Da
- 下一篇: 阿里专家杜万:Java响应式编程,一文全