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

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

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

12.3高效合作
團隊Native開發(fā)同學(xué)少,且各自并行業(yè)務(wù)需求,需合理的安排開發(fā)路線,減少總開發(fā)時長。

- 1)合理的設(shè)計開發(fā)并行路線,減少串行依賴
- 2)協(xié)議與接口先行;
- 3)各同學(xué)負(fù)責(zé)其相近&擅長的部分;
- 4)聯(lián)調(diào)時縮短彼此距離,高效溝通。

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

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

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