本文由ELab團隊公眾號授權發布,原題《Rust語言在IM客戶端的實踐》,來自抖音電商前端團隊的分享,本文有修訂和改動。
1、引言
本文將介紹飛鴿IM前端團隊如何結合Rust對飛鴿客戶端接待能力進行的技術提升,一步步從概念驗證、路徑分解到分工開發,再到最后上線收益論證,并分享了其中遇到的技術挑戰與經驗總結等。
本項目是一個長周期的復雜項目,相信本項目落地的經驗對其他同學及團隊能有所借鑒。
2、技術背景
飛鴿是在抖音電商業務上面向商家和用戶的聊天工具,其拉通售前、售中、售后渠道,為商家履約提供重要支撐。
對于飛鴿桌面端IM而言,我們會面臨很多基礎挑戰,比如做好會話穩定性、操作流暢性、冷啟動速度等,而在滿足98%以上的用戶需求且業務趨于穩定后,一些在沖刺后遺留的性能天花板問題暴露在我們面前,其中 高并發接待 & 多開是兩個重要的挑戰,是舊賬與難啃的硬骨頭。
為何持續會有這些挑戰存在?
1)歷史技術選型,包含者成本、人力、效率等考量,飛鴿客戶端使用的技術棧是react + electron:
* im sdk與業務渲染代碼都由 js 編寫,im sdk同時是cpu密集型 & io 密集型的組件,在高并發場景下,渲染頻率也比較高,業務與sdk相互搶占cpu資源與io資源,導致收發消息慢、操作卡頓(高并發限制)。
* 由于im sdk運行在webview中,所以收發消息依賴webview存活,故多開賬號 = 多個webview,內存成本線性增長。
2)im頁面在web層面多次優化后已接近架構上限,無法基于現有架構做更多天花板的突破。
對于以上這些挑戰,我們給出的解法是:對現有架構進行調整,使用Rust語言對im sdk進行重寫,徹底解除這一塊的性能瓶頸!
3、為什么選Rust語言?
飛鴿im sdk是一個對運行穩定性要求高的組件,其工程量大、邏輯復雜,對于異步特性使用非常頻繁,其對于內存安全、 線程安全有著比較嚴格的要求。
假如使用C++,作為新手并沒有把握能夠將復雜的IM SDK少bug的編寫下來(團隊限制)。
Rust學習曲線雖然陡峭,但是其為安全設計的各類語言特性、強大的編譯器,能夠將新人編寫代碼的問題數降到最低(邏輯問題除外)。
并且飛書團隊提供了客戶端的rust生態庫,幫助我們解決很多的基建問題,所以這里使用Rust是相當合適的。
Rust學習成長曲線:
4、飛鴿IM客戶端歷史架構的問題
如背景中所描述,歷史架構存在這兩個問題:
- 1)IM SDK 與 業務JS代碼共用Weview資源,接待密集的時候,sdk與業務,互相搶占cpu與io資源,導致容易卡頓、消息延遲;
- 2)多開的賬號必須依賴IM Webview存活(否則無法收到消息),內存線性增長。
5、飛鴿IM客戶端新架構與預期目標
具體是:
- 1)Rust獨立進程承擔所有的im sdk的計算壓力,可以大幅減輕js線程壓力,可提升壓力場景接待體驗;
- 2)Rust im SDK 解除瀏覽器中的IO限制(如同域名并發數限制);
- 3)解除Webview存活依賴,依靠rust進程也可收消息,為更多賬號的多開能力提供了鋪墊。
6、先用Rust進行技術可行性驗證
為了驗證推測切實可行,我們提前做了完備的POC驗證。
在POC中,我們針對“單進程單線程模型”、“多進程模型”、“多線程模型”,這三種模型搭建了mvp demo,即簡易的客服聊天模型,并進行壓力測試,并監測其內存、cpu等指標。
通過POC,我們得出的結論是:
具體就是:
- 1)rust 整體優于 js,計算占比越重,優勢越明顯(高壓時cpu差別能到達3倍以上);
- 2)架構選型上,rust進程獨立是最好的方案,穩定性更優、性能損耗相差較小。
7、新架構開始實施
路要一步步走,整個項目粗估下來會有上百的工作日,作為業務團隊,我們無法在短期內投入大量的資源去做這個項目,所以需要一步一步拆解、驗證、拿收益。
團隊內native開發資源有限,這件事情的進行也需要團隊進行學習、成長。下面我們將詳細分享這個過程 。
8、新架構實施階段1:Rust SDK工程基建
造房子先得有一個地基 —— Rust工程的基礎建設,是Native業務的前置條件!
桌面端同學牽頭搭建了整個RustSDK地基,地基解決的問題如下圖所示:
需要做的工作:
- 1)業務容器:有規律的組織代碼結構,進行業務隔離、資源隔離;
- 2)跨進程調用封裝:降低業務調用難度;
- 3)建設日志系統、日志回撈:降低排查問題的難度;
- 4)構建跨平臺異步執行環境:簡化異步代碼編寫,底層封裝,便于跨平臺代碼遷移;
- 5)跨平臺編譯,跨平臺集成;
- 6)... ...
9、新架構實施階段2:IM基礎能力夯實
在擁有一部分地基后,我們開始針對IM SDK的基礎能力進行實現和驗證。
因為只有完成基礎能力驗證之后,我們才會有信心在新的架構上疊加更多的功能。
這階段我們關注以下指標( 希望其存在優化,至少不劣化):
- 1)長鏈在線率;
- 2)消息發送成功率;
- 3)卡頓率;
- 4)Rust進程崩潰率、無響應率。
僅實現長鏈能力下沉,驗證&提升其穩定:
本階段論證結果如下:
- 1)Rust Crash率, 達成預期;
- 2)Rust無響應率 - 未達預期,可優化;
- 3)長鏈在線率 - 達成預期,但是存在優化空間;
- 4)卡頓率 - 不劣化 達成預期;
- 5)消息發送成功率 - 不劣化,達成預期。
這階段的工作是考驗耐心的,因為這個階段并不能帶來實質性的用戶體驗提升、也無法拿到明顯的提升數據,只是作為中間階段,它有存在的必要性。
這階段后,在穩定性治理、基礎能力驗證、 Rust 語言經驗、指標制定合理性這幾方面,我們踩上了一個更結實的臺階,更有信心去進行更復雜的下一階段。
10、 新架構實施階段3:使用Rust實現IM SDK全部能力
夯實基礎后,我們開始發力沖刺,大刀闊斧的對IM SDK進行重新設計、實現、聯調以及上線。
此階段要實現im sdk的全部能力、 并對線上運行的js im sdk進行替換。
由于飛鴿im對于通信模塊的穩定程度要求是很高的,替換過程就像是在高速行駛的車輛上替換輪胎,如果出現問題也容易導致大量的客服負面反饋。
因此,新rust sdk的穩定性、異常問題時的兜底方案、灰度時的監控觀察、對新增反饋的留意都很重要,放量過程會存在一定精神壓力。
工作內容大致如下。
1)多實例的Rust IM SDK設計(商家單聊、群聊、平臺客服)、Js -> Rust IMSDK跨端調用協議設計:
- a)分析、拆解所有Js Im SDK至今具有的能力,并以貼合Rust的方式重新設計;
- b)需要在協議設計中,盡可能的合并 & 簡化 Js -> Rust的調用,以減少IPC通信成本。
2)開發:
- a)Rust IM SDK核心實現;
- b)Rust\Js適配同學緊密合作,根據協議進行業務實現、業務適配;
- c)密切溝通,發現問題及時糾偏;
- d)編寫單測;
3)測試:
4)異常兜底方案實現:
設計數據冗余,當Rust進程出現崩潰、無響應、不可恢復的網絡錯誤時,識別并fallback到 web版本,使用冗余數據快速恢復im sdk正常運行狀態,確保用戶體驗。
5)穩妥的上線方案 & 穩定性治理。
6)調用&適配優化,結合Native能力進一步性能優化。
7)結果回收。
8)其中各個步驟都會存在一些挑戰,在后后面的內容會提到。
調用簡化模型:
IM Core簡化模型:
11、新架構實施階段4:基于穩定的RustIM SDK實現形態升級
最后的階段,我們基于完善的Rust IM SDK的能力進行形態的升級。
本階段正在進行中,完成后會做更多的分享。
1)多窗口改造:銷毀后臺的多開賬號,讓多開賬號數量突破到25個。

2)消息提醒、通知流程改造。
3)消息本地化能力:加快消息上屏。
12、技術挑戰與實踐總結
12.1編程語言 & IM領域知識突破
一個有戰斗力的團隊,一定是持續學習、進步的。
比如:
- 1)獲取學習的純粹快樂:當沉浸在學習中,并感受到自己在進步的時候,會是一個快樂的狀態;
- 2)逐步克服小挑戰,及時獲得正反饋;
- 3)在同事中找到伙伴和老師,詢問與探討:建立團隊中的學習氛圍。

12.2長周期技術項目,如何持續保持信心 ?
比如 :
- 1)Leader與同事認可與支持 — 團隊基礎、價值觀鼓勵;
- 2)關注長期收益,訓練自己延遲滿足感;
- 3)做好階段性分解與驗證,縮短單個周期(如本文的一二階段拆解,可逐步累積信心);
- 4)增強自身實力,做好問題把控,及時發現&解決問題。

12.3高效合作
團隊Native開發同學少,且各自并行業務需求,需合理的安排開發路線,減少總開發時長。

- 1)合理的設計開發并行路線,減少串行依賴
- 2)協議與接口先行;
- 3)各同學負責其相近&擅長的部分;
- 4)聯調時縮短彼此距離,高效溝通。

12.4保障用戶體驗的灰度上線
1)編寫模塊的健康自檢,檢測到異常時用最小的代價切換備用老方案。
2)完善業務監控&技術指標監控:crash率、無響應率、長鏈在線率、發消息成功率、請求成功率、卡頓率等。
3)對真實用戶使用體驗進行跟蹤:
- a)書反饋群組維護,及時獲得用戶反饋;
- b)與商家客服保持線下聯系,獲取一手體驗情況。
4)放量節奏的把控:
- a)大型改動可以先給白名單用戶試用,收集反饋;
- b)放出能夠識別問題的量,解決問題后再繼續放量;
- c)放量期間主動查詢用戶實時反饋數據,有問題及時解決。

12.5如何減少IPC通信成本帶來的開銷
頻率過高的IPC通信可能使得CPU優化適得其反,因為老版本都運行在Js中,所以調用頻率是沒有節制的(循環讀取數據也經常出現),必須要在設計上降低下來——降低業務JS線程的壓力。
以下措施可以將本場景通信成本降低90%以上。
1)更高效的數據協議 protobuf:相較于json,數據更小、解析和序列化性能更高、跨語言生成代碼工具。
2)Rust push to js:使用數據收集去重 + debounce批量更新的策略,合并多個數據回調接口,減少通信頻率。
3)Js call rust(單次基礎耗時4ms):
- a)適當緩存數據,不用每次都回源查詢;
- b)需要頻繁調用的邏輯下沉Rust,Rust邏輯自完善。
12.6結果回收:極端場景下的優化大盤數據體現不明顯

針對某種場景做的優化工作不容易在大盤數據中得到體現(尤其在灰度階段),我們應該針對特殊場景建立新指標。
即編寫策略,識別并收集極端場景下的數據:為了衡量極端場景的的卡頓優化,建立了忙碌與卡頓指標,可以衡量出用戶接待忙碌程度與卡頓率的關系,并且通過此指標將優化清晰的衡量出來。
12.7Rust SDK的問題治理
具體是:
- 1)前期的問題不穩定,需更多信息輔助排查,日志盡量完整;
- 2)與真實用戶群體保持聯系,可加快問題驗證、問題發現的過程;
- 3)需要建設便捷的日志回撈 & 日志分析工具(幫助快速找到日志還原現場)。
13、新架構帶來的收益
壓力評測:
數據表現:
解讀一下:
- 1)客服發送消息,大盤端到端耗時降低 40%;
- 2)消息發送成功率三個9 -> 四個9;
- 3)im頁面大盤卡頓率降低 15%;
- 4)密集接待場景,卡頓率降低 50%。
全量至今,再無大量進線導致卡頓的反饋。回訪歷史反饋用戶,皆無因大量接待導致的卡頓現象
14、相關文章
[1] 阿里技術分享:閑魚IM基于Flutter的移動端跨端改造實踐
[2] IM開發干貨分享:有贊移動端IM的組件化SDK架構設計實踐
[3] IM開發干貨分享:我是如何解決大量離線消息導致客戶端卡頓的
[4] 如約而至:微信自用的移動端IM網絡層跨平臺組件庫Mars已正式開源
[5] 從客戶端的角度來談談移動端IM的消息可靠性和送達機制
[6] IM開發干貨分享:IM客戶端不同版本兼容運行的技術思路和實踐總結
[7] IM全文檢索技術專題(四):微信iOS端的最新全文檢索技術優化實踐
[8] 從游擊隊到正規軍(二):馬蜂窩旅游網的IM客戶端架構演進和實踐總結
[9] IM跨平臺技術學習(九):全面解密新QQ桌面版的Electron內存優化實踐
[10] IM跨平臺技術學習(五):融云基于Electron的IM跨平臺SDK改造實踐總結
[11] 抖音技術分享:抖音Android端手機功耗問題的全面分析和詳細優化實踐
[12] 社交軟件紅包技術解密(十二):解密抖音春節紅包背后的技術設計與實踐
(本文已同步發布于:http://www.52im.net/thread-4620-1-1.html)