微信終端跨平臺組件 Mars 正式開源
時間:2022-08-07 17:54:01 | 來源:網(wǎng)站運營
時間:2022-08-07 17:54:01 來源:網(wǎng)站運營
導(dǎo)語
昨天上午,微信在廣州舉辦了微信公開課Pro。于是,精神哥這兩天的朋友圈被小龍的“八不做”刷屏了。小伙伴們可能不知道,下午,微信公開課專門開設(shè)了技術(shù)分論壇。在分論壇中,微信開源了跨平臺的網(wǎng)絡(luò)組件Mars。
之前Bugly也為大家介紹過Mars的一些相關(guān)模塊的內(nèi)容:
- 微信終端跨平臺組件 mars 系列(一) - 高性能日志模塊xlog
- 微信終端跨平臺組件 mars 系列(二) - 信令傳輸超時設(shè)計
今天,微信的小伙伴和大家一起聊聊Mars的設(shè)計原則以及發(fā)展史。
背景
2012 年中,微信支持包括 Android、iOS、Symbian 等三個平臺。但在各個平臺上,微信客戶端沒有任何統(tǒng)一的基礎(chǔ)模塊。2012 年的微信正處于高速發(fā)展時期,各平臺的迭代速度不一、使用的編程語言各異,后臺架構(gòu)也處在不斷探索的過程中。多種因素使得各個平臺基礎(chǔ)模塊的實現(xiàn)出現(xiàn)了差異,導(dǎo)致出現(xiàn)多次需要服務(wù)器做兼容的善后工作。網(wǎng)絡(luò)作為微信的基礎(chǔ),重要性不言而喻。任何網(wǎng)絡(luò)實現(xiàn)的 bug 都可能導(dǎo)致重大事故。例如微信的容災(zāi)實現(xiàn),如果因為版本的實現(xiàn)差異,導(dǎo)致某些版本上無法進(jìn)行容災(zāi)恢復(fù),將會嚴(yán)重的影響用戶體驗,甚至造成用戶的流失。我們急需一套統(tǒng)一的網(wǎng)絡(luò)基礎(chǔ)庫,為微信的高速發(fā)展保駕護航。
恰好,這個時候塞班漸入日暮,微信對塞班的支持也逐漸減弱。老大從塞班組抽調(diào)人力,組成一個三人小 team 的初始團隊,開始著手做通用的基礎(chǔ)組件。這個基礎(chǔ)組件最初就定位為:跨平臺、跨業(yè)務(wù)的基礎(chǔ)組件?,F(xiàn)在看,這個組件除了解決了已有問題,還給微信的高速發(fā)展帶來了很多優(yōu)勢,例如:
- 基礎(chǔ)組件方便了開展專項的網(wǎng)絡(luò)基礎(chǔ)研究與優(yōu)化。
- 基礎(chǔ)組件為多平臺的快速實現(xiàn)提供了有力的支持。
經(jīng)過四年多的發(fā)展,跨平臺的基礎(chǔ)組件已經(jīng)包含了網(wǎng)絡(luò)組件、日志組件在內(nèi)的多個組件?;仡^看,這是一條開荒路。
設(shè)計原則
在基礎(chǔ)模塊的開發(fā)中,設(shè)計尤為重要。在設(shè)計上,微信基礎(chǔ)組件以跨平臺、跨業(yè)務(wù)為前提,遵從
高可用,高性能,負(fù)載均衡的設(shè)計原則。
可用是一個即時通訊類 App 的立身之本。高可用又體現(xiàn)在多個層面上:網(wǎng)絡(luò)的可用性、 App 的可用性、系統(tǒng)的可用性等。
- 網(wǎng)絡(luò)的可用性
移動互聯(lián)網(wǎng)有著丟包率高、帶寬受限、延遲波動、第三方影響等特點,使得網(wǎng)絡(luò)的可用性,尤其是弱網(wǎng)絡(luò)下的可用性變得尤為關(guān)鍵。Mars 的 STN 組件作為基于 socket 層的網(wǎng)絡(luò)解決方案,在很多細(xì)節(jié)設(shè)計上會充分考慮弱網(wǎng)絡(luò)下的可用性。
- App 的可用性
App 的可用性包含穩(wěn)定性、運行性能等多個方面。文章高性能日志模塊 xlog 描述了 xlog 在不影響 App 運行性能的前提下進(jìn)行的大量設(shè)計思考。
- 系統(tǒng)的可用性
除了考慮正常的使用場景,APP的設(shè)計還需要從整個系統(tǒng)的角度進(jìn)行設(shè)計思考。例如在容災(zāi)設(shè)計上,Mars 不僅使用了服務(wù)器容災(zāi)方案,也設(shè)計了客戶端的本地容災(zāi)。當(dāng)部分服務(wù)器出災(zāi)時,目前微信可以做到,15min 內(nèi)把95%以上的用戶轉(zhuǎn)移到可用服務(wù)器上。
保障高可用并不代表可以犧牲性能,對于一個用戶使用最頻繁的應(yīng)用,反而更要對使用的資源精打細(xì)算。例如在 Mars 信令傳輸超時設(shè)計 - 信令傳輸超時設(shè)計)中,多級超時的設(shè)計充分的考慮了可用性與高性能之間的平衡取舍。
如果說高可用高性能只是客戶端本身的考慮的話,負(fù)載均衡就需要結(jié)合服務(wù)器端來考慮了,做一個客戶端網(wǎng)絡(luò)永遠(yuǎn)不能只把眼光放在客戶端上。任何有關(guān)網(wǎng)絡(luò)訪問的決策都要考慮給服務(wù)器所帶來的額外壓力是多大。為了選用質(zhì)量較好的 IP,曾經(jīng)寫了完整的客戶端測速代碼,后來刪掉,其中一個原因是因為不想給服務(wù)器帶來額外的負(fù)擔(dān)。Mars 的代碼中,選擇 IP 時用了大量的隨機函數(shù)也是為了規(guī)避大量的用戶同時訪問同一臺服務(wù)器而做的。
在這四年,我學(xué)到最多的就是簡單和平衡。 把方案做的盡可能簡單,這樣才不容易出錯。設(shè)計方案時大多數(shù)時候都不可能滿足所有想達(dá)到的條件,這個時候就需要去平衡各個因素。在組件中一個很好的例子就是長連接的連接頻率(具體實現(xiàn)見
http://longlink_connect_monitor.cc),這個連接頻率就是綜合耗電量,流量,網(wǎng)絡(luò)高可用,用戶行為等因素進(jìn)行綜合考慮的。
Mars 的發(fā)展歷程
階段一:讓微信跑起來
跨平臺基礎(chǔ)組件的需求起源于微信,首要目標(biāo)當(dāng)然是先承載起微信業(yè)務(wù)。為了不局限于微信,滿足跨平臺、跨業(yè)務(wù)的設(shè)計目標(biāo),在設(shè)計上,網(wǎng)絡(luò)組件定位為客戶端與服務(wù)端之間的無狀態(tài)網(wǎng)絡(luò)信令通道,即交互方式主要包含一來一回、主動push兩種方式。這使得基礎(chǔ)組件無需考慮請求間的關(guān)聯(lián)性、時序性,核心接口得到了極大的簡化。同時,簡潔的交互也使得業(yè)務(wù)邏輯的耦合極少。目前基礎(chǔ)組件與業(yè)務(wù)的交互只包括:編解碼、auth狀態(tài)查詢兩部分。核心接口如下:(具體見stn_logic.h)。
void StartTask(...); int OnTaskEnd(...); void OnPush(...); bool Req2Buf(...); int Buf2Resp(...); bool MakeSureAuthed();
在線程模型的選擇上,最早使用的是多線程模型。當(dāng)需要異步做一個工作,就起一個線程。多線程勢必少不了鎖。但當(dāng)灰度幾次之后發(fā)現(xiàn),想要規(guī)避死鎖的四個必要條件并沒有想象中的那么容易。用戶使用場景復(fù)雜,客戶端的時序、狀態(tài)的影響因素多,例如網(wǎng)絡(luò)切換事件、前后臺事件、定時器事件、網(wǎng)絡(luò)事件、任務(wù)事件等,導(dǎo)致了不少的死鎖現(xiàn)象和對象析構(gòu)時序錯亂導(dǎo)致的內(nèi)存非法訪問問題。
這時,我們開始思考,多線程確實有它的優(yōu)點:可以并發(fā)甚至并行提高運行速度。但是對于網(wǎng)絡(luò)模塊來說,性能瓶頸主要是在網(wǎng)絡(luò)耗時上,并不在于本地程序執(zhí)行速度上。那為何不把大部分程序執(zhí)行改成串行的,這樣就不會存在多線程臨界區(qū)的問題,無鎖自然就不會死鎖。
因此,我們目前使用了消息隊列的方案(具體實現(xiàn)見 comm/messagequeue 目錄),把絕大多數(shù)非阻塞操作放到消息隊列里執(zhí)行。并且規(guī)定,基礎(chǔ)組件與調(diào)用方之間的交互必須1. 盡快完成,不進(jìn)行任何阻塞操作;2. 單向調(diào)用,避免形成環(huán)狀的復(fù)雜時序。消息隊列的引入很好的改善了死鎖問題,但消息隊列的線程模型中,我們還是不能避免存在需要阻塞的調(diào)用,例如網(wǎng)絡(luò)操作。在未來的嘗試中,我們計劃引入?yún)f(xié)程的方式,將線程模型盡可能的簡化。
在其它技術(shù)選型上,有時甚至需要細(xì)節(jié)到API 的使用,比如考慮平臺兼容性問題,舍棄了一些函數(shù)的線程安全版本,使用了 asctime、localtime、rand 等非線程安全的版本。
階段二:修煉內(nèi)功
在多次的灰度驗證、數(shù)據(jù)比對下,微信各平臺的網(wǎng)絡(luò)邏輯順利的過渡到了統(tǒng)一基礎(chǔ)組件。為了有效的驗證組件的效果,我們開發(fā)了 smc 的統(tǒng)計監(jiān)控組件,開始關(guān)注網(wǎng)絡(luò)的各項指標(biāo),進(jìn)行網(wǎng)絡(luò)基礎(chǔ)研究與優(yōu)化,尤其是關(guān)注移動網(wǎng)絡(luò)的特征。
- 基礎(chǔ)網(wǎng)絡(luò)優(yōu)化。
常規(guī)的網(wǎng)絡(luò)能力,例如 DNS 防劫持、動態(tài) IP 下發(fā)、就近接入、容災(zāi)恢復(fù)等,在這一階段得到逐步的建設(shè)與完善。除此之外,Mars 的網(wǎng)絡(luò)模塊是基于 socket 層的網(wǎng)絡(luò)解決方案,在缺失大而全的 HTTP 能力的同時,卻可以將優(yōu)化做到更細(xì)致,細(xì)致到連接策略、連接超時、多級讀寫超時、收發(fā)策略等每個網(wǎng)絡(luò)過程中。例如,當(dāng)遇到弱網(wǎng)絡(luò)下連通率較低,或者某些連通率不好的的服務(wù)器影響使用時,我們使用了復(fù)合連接(代碼見complexconnect.inl)和 IP 排序(代碼見http://simple_ipport_sort.cc)的方案很好的應(yīng)對這兩個問題。
- 平臺特性優(yōu)化。雖然 Mars 是跨平臺的基礎(chǔ)組件,但在很多設(shè)計上是需要結(jié)合各平臺的特性的。例如為了盡量減少頻繁的喚醒手機,引入了智能心跳,并且在智能心跳中考慮了 Android 的 alarm 對齊特性(具體實現(xiàn)見smart_heartbeat.cc)。再如在網(wǎng)絡(luò)切換時,為了平滑切換的過程,使用了 iOS 中網(wǎng)絡(luò)的特性,在 iOS 中做了延遲處理等。
- 移動特性優(yōu)化。微信的使用場景大部分是在手機端進(jìn)行使用,在組件的設(shè)計過程中,我們也會研究移動設(shè)備的特性,并進(jìn)行結(jié)合優(yōu)化。例如,結(jié)合移動設(shè)備的無線電資源控制器(RRC)的狀態(tài)切換,對一些性能要求特別特別敏感的請求,進(jìn)行提前激活的優(yōu)化處理等。
階段三:“抓妖記”
基礎(chǔ)組件全量上線微信后,以微信的用戶量,當(dāng)然也會遇到各種各樣的“妖”。例如,寫網(wǎng)絡(luò)程序躲不開運營商。印象比較深刻的某地的用戶反饋連接 WiFi 時,微信不可用,后來 tcpdump 發(fā)現(xiàn),當(dāng)包的大小超過一定大小后就發(fā)不出去。解決方案:在 WiFi 網(wǎng)絡(luò)下強制把 MSS 改為1400(代碼見
http://unix_socket.cc)。
做移動客戶端更避不開手機廠商。一次遇到了一個百思不得其解的 crash,堆棧如下:
#00 pc 0x43e50 /system/lib/libc.so (???) #01 pc 0x3143 /system/vendor/lib/libvendorconn.so (handleDpmIpcReq+154) #02 pc 0x2f6d /system/vendor/lib/libvendorconn.so (send_ipc_req+276) #03 pc 0x30ff /system/vendor/lib/libcneconn.so (connect+438)
看堆棧結(jié)合程序 xlog 分析,非阻塞 socket 卡在了 connect 函數(shù)里超過了6 min, 被我們自帶的 anr 檢測(代碼見
http://anr.cc)發(fā)現(xiàn)然后自殺。最后實在束手無策,聯(lián)系廠商一起排查,最終查明原因:為了省電,當(dāng)手機鎖屏?xí)r連的不是 WiFi 且又沒有下行網(wǎng)絡(luò)數(shù)據(jù)時,芯片 gate 會關(guān)閉,block 住所有網(wǎng)絡(luò)請求,直到有下行數(shù)據(jù)或者超過 20min 才會放開。當(dāng)手機有網(wǎng)絡(luò)即使是手機網(wǎng)絡(luò)的情況下,很難沒有下行數(shù)據(jù),所以基本不會觸發(fā)組件自帶的 anr 檢測,但當(dāng)手機沒連接任何網(wǎng)絡(luò)時,就很容易觸發(fā)。解決方案:廠商修改代碼邏輯,當(dāng)沒有任何網(wǎng)絡(luò)時不 block 網(wǎng)絡(luò)請求。
運營商和手機廠商對我們來說已經(jīng)是一個黑盒,但其實也遇到過更黑的黑盒。當(dāng)手機長時間不重啟,有極小概率不能繼續(xù)使用微信,重啟手機會恢復(fù)。但因為一直找不到一個愿意配合我們又滿足條件的用戶,導(dǎo)致這個問題很長一段時間內(nèi)都沒有任何進(jìn)展,最終偶然一個機會,在一臺測試機器上重現(xiàn)了該問題,tcpdump 發(fā)現(xiàn)在三步握手階段,服務(wù)器帶回的客戶端帶過去的 tsval 字段被篡改,導(dǎo)致三步握手直接失敗,而且這個篡改發(fā)生在離開服務(wù)器之后到達(dá)客戶端之前。
這個問題是微信網(wǎng)絡(luò)模塊中排查時間最長也是花費精力最多的一個問題,不僅因為很長一段時間內(nèi)無案例可分析,也因為在重現(xiàn)后,聯(lián)系了大量的同事和外部有關(guān)人的幫忙,想排查出罪魁禍?zhǔn)?。但因為中間涉及的環(huán)節(jié)和運營商相關(guān)部門過多,無法繼續(xù)排查下去,最終也沒找到根本原因。 解決辦法:服務(wù)器更改 net.ipv4.tcp_timestamps = 0。
這段時間是痛并快樂著,見識到了各種極差的網(wǎng)絡(luò),才切膚感受到移動網(wǎng)絡(luò)環(huán)境的惡劣程度,但看著我們的網(wǎng)絡(luò)性能數(shù)據(jù)在穩(wěn)步提升又有種滿足感。截止到今天,已經(jīng)很少有真正的網(wǎng)絡(luò)問題需要跟進(jìn)了。這也是我們能有時間開始把這些代碼開源出去的很大的一個原因。
Mars 介紹
講述了一大堆 Mars 的發(fā)展歷程,終于來到主角的介紹了。大概一年前,我們開始有想法把基礎(chǔ)組件開源出去,當(dāng)時大家都在糾結(jié)叫什么名字好呢?此時恰逢《火星救援》正在熱映,一位同事說干脆叫 Mars 吧,于是就定下來叫了 Mars??戳丝创a,發(fā)現(xiàn)想要開源出去可能還是需要做一些其他工作的。
代碼重構(gòu)
首先,代碼風(fēng)格方面,因為最初我們使用文件名、函數(shù)名、變量名的規(guī)則是內(nèi)部定義的規(guī)則,為了能讓其他人讀起來更舒心,我們決定把代碼風(fēng)格改為谷歌風(fēng)格,比如:變量名一律小寫, 單詞之間用下劃線連接;左大括號不換行等等。但是為了更好的區(qū)分訪問空間,我們又在谷歌代碼風(fēng)格進(jìn)行了一些變通,比如:私有函數(shù)全部是”
_”開頭;函數(shù)參數(shù)全部以”“開頭 等等。
其次,雖然最初的設(shè)計一直是秉承著業(yè)務(wù)性無關(guān)的設(shè)計,但在實際開發(fā)過程中仍然難免帶上了微信的業(yè)務(wù)性相關(guān)代碼,比較典型的就是 newdns 。為了 Mars 以后的維護以及保證開源出去代碼的同源,在開源出去之前必須把這些業(yè)務(wù)性有關(guān)的代碼抽離出來,抽離后的結(jié)構(gòu)如下:
- mars-open 也就是要開源出去的代碼,獨立 git repo。
- mars-private 是可能開源出去的代碼,依賴 mars-open。
- mars-wechat 是微信業(yè)務(wù)性相關(guān)的代碼,依賴 mars-open 和 mars-private。
最后,為了接口更易用,對調(diào)用接口以及回調(diào)接口的參數(shù)也進(jìn)行了反復(fù)思考與修改。
編譯優(yōu)化
在 Mars之前,是直接給 Android 提供動態(tài)庫(.so),因為代碼邏輯都已經(jīng)固定,不需要有可定制的部分。給 Apple 系平臺提供靜態(tài)庫(.a),因為對外暴露的函數(shù)幾乎不會改變,直接把相應(yīng)的頭文件放到相應(yīng)的項目里就行。但對外開源就完全不一樣了:日志的加密算法可能別人需要自己實現(xiàn);長連或者短連的包頭有人需要自己定制;對外接口的頭文件我們可能會修改……
為了讓使用者可定制代碼,對于編譯 Android 平臺我們提供了兩種選擇:1. 動態(tài)庫。有些可能需要定制的代碼都提供了默認(rèn)實現(xiàn)。2. 先編譯靜態(tài)庫,再編譯動態(tài)庫。編譯出來靜態(tài)庫后,實現(xiàn)自己需要定制的代碼后,執(zhí)行 ndk-build 后即可編譯出來動態(tài)庫。 對于 Apple 系平臺,把頭文件全部收攏為 Mars 維護,直接編譯出 Framework。
為了能讓開發(fā)者快速的入門,我們提供了 Android、iOS、OS X 平臺的 demo,其他平臺的編譯和 demo 會在不久就加上支持。
成型的 Mars 結(jié)構(gòu)圖如下:
業(yè)界對比
我們做的一直都不是滿足所有需求的組件,只是做了一個更適合我們使用的組件,這里也列了下和同類型的開源代碼的對比。
可以看出:
- Mars 中包括一個完整的高性能的日志組件 xlog;
- Mars 中 STN 是一個跨平臺的 socket 層解決方案,并不支持完整的 HTTP 協(xié)議;
- Mars 中 STN 模塊是更加貼合“移動互聯(lián)網(wǎng)”、“移動平臺”特性的網(wǎng)絡(luò)解決方案,尤其針對弱網(wǎng)絡(luò)、平臺特性等有很多的相關(guān)優(yōu)化策略。
總的來說,Mars 是一個結(jié)合移動 App 所設(shè)計的基于 socket 層的解決方案,在網(wǎng)絡(luò)調(diào)優(yōu)方面有更好的可控性,對于 HTTP 完整協(xié)議的支持,已經(jīng)考慮后續(xù)版本會加入。
總結(jié)
經(jīng)常有朋友和我說:發(fā)現(xiàn)網(wǎng)絡(luò)信號差的時候或者其他應(yīng)用不能用的時候,微信仍然能發(fā)出去消息。不知不覺我們好像什么都沒做,回頭看,原來我們已經(jīng)做了這么多。我想,并不是任何一行代碼都可以經(jīng)歷日活躍5億用戶的考驗,感謝微信給我們提供了這么一個平臺?,F(xiàn)在我們想把這些代碼和你們分享,運營方式上 Mars 所開源出去的代碼會和微信所用的代碼保持同源,所有開源出去的代碼也首先會在微信上驗證通過后再公開。開源并不是結(jié)束,只是開始。我們后續(xù)仍然會繼續(xù)探索在移動互聯(lián)網(wǎng)下的網(wǎng)絡(luò)優(yōu)化。Talk is cheap, show you our code.
關(guān)注 Mars , 來 Github 給我們 star 吧
Tencent/mars
更多精彩內(nèi)容歡迎關(guān)注騰訊 Bugly的微信公眾賬號:
騰訊 Bugly是一款專為移動開發(fā)者打造的質(zhì)量監(jiān)控工具,幫助開發(fā)者快速,便捷的定位線上應(yīng)用崩潰的情況以及解決方案。智能合并功能幫助開發(fā)同學(xué)把每天上報的數(shù)千條 Crash 根據(jù)根因合并分類,每日日報會列出影響用戶數(shù)最多的崩潰,精準(zhǔn)定位功能幫助開發(fā)同學(xué)定位到出問題的代碼行,實時上報可以在發(fā)布后快速的了解應(yīng)用的質(zhì)量情況,適配最新的 iOS, Android 官方操作系統(tǒng),鵝廠的工程師都在使用,快來加入我們吧!
?