網上看到關于微信官方的跨平台跨業務的終端基礎元件Mars的介紹文章,轉載這這裡。
作者:男人
連結:https://zhuanlan.zhihu.com/p/24614843
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
之前無論是微信團隊還是手機QQ團隊,都以騰訊公司的名義在Github開源了數個工程,但這些工程所受的關注度遠不及Mars。之是以Mars廣受關注的原因,其實搞移動端IM或推送技術的開發者同行都明白,因為移動網絡實在太不可靠、太複雜,以至于寫出一個能用于大規模使用者環境的穩定、省流量、省電、資料傳輸流暢、弱網絡健壯、背景自動保活等技術名額的IM或推送是相當困難的。
更為重要的原因是畢竟微信Mars經過微信團隊多年積累并經過海量使用者的測試和使用,是經受的住各種複雜移動端網絡環境、各種亂七八糟型号智能手機的真實考驗的。若Mars開源,必将為IM及相關技術應用領域的同行帶來很多有價值的實踐成果,畢竟微信的體量和應用規模決定了技術的高度,确實是值得同行學習和關注。
我們簡要的概括一下,微信Mars解決了如下問題:
[1] 提供長連、短連兩種網絡通道; [2] 正常的網絡能力,例如 DNS 防劫持、動态 IP 下發、就近接入、容災恢複等; [3] 貼合移動網際網路的網絡層解決方案; [4] 貼合移動終端的平台特性:前背景、活躍态、休眠、省電、省流量等。
那麼微信Mars到底有什麼用呢?毫無疑問,微信Mars存在的前提就是為了更好的服務微信這個超級IM而存在,最适合幹的活就是開發移動端IM了,當然由于提煉的很好,相信移動端推送技術等都是可以使用微信Mars作為網絡層lib來使用,進而以微信的成果為起點開發出擁有更加優秀網絡體驗的移動端富網絡應用。
2012 年中,微信支援包括 Android、iOS、Symbian 三個平台。但在各個平台上,微信用戶端沒有任何統一的基礎子產品。2012 年的微信正處于高速發展時期,各平台的疊代速度不一、使用的程式設計語言各異,背景架構也處在不斷探索的過程中。多種因素使得各個平台基礎子產品的實作出現了差異,導緻出現多次需要伺服器做相容的善後工作。網絡作為微信的基礎,重要性不言而喻。任何網絡實作的 bug 都可能導緻重大事故。例如微信的容災實作,如果因為版本的實作差異,導緻某些版本上無法進行容災恢複,将會嚴重的影響使用者體驗,甚至造成使用者的流失。我們亟需一套統一的網絡基礎庫,為微信的高速發展保駕護航。
恰好,這個時候塞班漸入日暮,微信對塞班的支援也逐漸減弱。老大從塞班組抽調人力,組成一個三人小 team 的初始團隊,開始着手做通用的基礎元件。這個基礎元件最初就定位為:跨平台、跨業務的基礎元件。現在看,這個元件除了解決了已有問題,還給微信的高速發展帶來了很多優勢,例如:
基礎元件友善了開展專項的網絡基礎研究與優化。 基礎元件為多平台的快速實作提供了有力的支援。
經過四年多的發展,跨平台的基礎元件已經包含了網絡元件、日志元件在内的多個元件。回頭看,這是一條開荒路。
在基礎子產品的開發中,設計尤為重要。在設計上,微信基礎元件以跨平台、跨業務為前提,遵從高可用,高性能,負載均衡的設計原則。
可用是一個即時通訊類 App 的立身之本。高可用又展現在多個層面上:網絡的可用性、 App 的可用性、系統的可用性等。
網絡的可用性:
移動網際網路有着丢包率高、帶寬受限、延遲波動、第三方影響等特點,使得網絡的可用性,尤其是弱網絡下的可用性變得尤為關鍵。Mars 的 STN 元件作為基于 socket 層的網絡解決方案,在很多細節設計上會充分考慮弱網絡下的可用性。
App 的可用性:
App 的可用性包含穩定性、運作性能等多個方面。文章高性能日志子產品 xlog 描述了 xlog 在不影響 App 運作性能的前提下進行的大量設計思考。
系統的可用性:
除了考慮正常的使用場景,APP的設計還需要從整個系統的角度進行設計思考。例如在容災設計上,Mars 不僅使用了伺服器容災方案,也設計了用戶端的本地容災。當部分伺服器出災時,目前微信可以做到,15min 内把95%以上的使用者轉移到可用伺服器上。
保障高可用并不代表可以犧牲性能,對于一個使用者使用最頻繁的應用,反而更要對使用的資源精打細算。例如在 Mars 信令傳輸逾時設計中,多級逾時的設計充分的考慮了可用性與高性能之間的平衡取舍。
如果說高可用高性能隻是用戶端本身的考慮的話,負載均衡就需要結合伺服器端來考慮了,做一個用戶端網絡永遠不能隻把眼光放在用戶端上。任何有關網絡通路的決策都要考慮給伺服器所帶來的額外壓力是多大。為了選用品質較好的 IP,曾經寫了完整的用戶端測速代碼,後來删掉,其中一個原因是因為不想給伺服器帶來額外的負擔。Mars 的代碼中,選擇 IP 時用了大量的随機函數也是為了規避大量的使用者同時通路同一台伺服器而做的。
voidStartTask(...); intOnTaskEnd(...); voidOnPush(...); boolReq2Buf(...); intBuf2Resp(...); boolMakeSureAuthed();
線上程模型的選擇上,最早使用的是多線程模型。當需要異步做一個工作,就起一個線程。多線程勢必少不了鎖。但當灰階幾次之後發現,想要規避死鎖的四個必要條件并沒有想象中的那麼容易。使用者使用場景複雜,用戶端的時序、狀态的影響因素多,例如網絡切換事件、前背景事件、定時器事件、網絡事件、任務事件等,導緻了不少的死鎖現象和對象析構時序錯亂導緻的記憶體非法通路問題。
這時,我們開始思考,多線程确實有它的優點:可以并發甚至并行提高運作速度。但是對于網絡子產品來說,性能瓶頸主要是在網絡耗時上,并不在于本地程式執行速度上。那為何不把大部分程式執行改成串行的,這樣就不會存在多線程臨界區的問題,無鎖自然就不會死鎖。
在其它技術選型上,有時甚至需要細節到API 的使用,比如考慮平台相容性問題,舍棄了一些函數的線程安全版本,使用了 asctime、localtime、rand 等非線程安全的版本。
在多次的灰階驗證、資料比對下,微信各平台的網絡邏輯順利的過渡到了統一基礎元件。為了有效的驗證元件的效果,我們開發了 smc 的統計監控元件,開始關注網絡的各項名額,進行網絡基礎研究與優化,尤其是關注移動網絡的特征。
移動特性優化:微信的使用場景大部分是在手機端進行使用,在元件的設計過程中,我們也會研究移動裝置的特性,并進行結合優化。例如,結合移動裝置的無線電資源控制器(RRC)的狀态切換,對一些性能要求特别特别敏感的請求,進行提前激活的優化處理等。
做移動用戶端更避不開手機廠商。一次遇到了一個百思不得其解的 crash,堆棧如下:
#00pc0x43e50/system/lib/libc.so (???) #01pc0x3143/system/vendor/lib/libvendorconn.so (handleDpmIpcReq+154) #02pc0x2f6d/system/vendor/lib/libvendorconn.so (send_ipc_req+276) #03pc0x30ff/system/vendor/lib/libcneconn.so (connect+438)
營運商和手機廠商對我們來說已經是一個黑盒,但其實也遇到過更黑的黑盒。當手機長時間不重新開機,有極小機率不能繼續使用微信,重新開機手機會恢複。但因為一直找不到一個願意配合我們又滿足條件的使用者,導緻這個問題很長一段時間内都沒有任何進展,最終偶然一個機會,在一台測試機器上重制了該問題,tcpdump 發現在三步握手階段,伺服器帶回的用戶端帶過去的 tsval 字段被篡改,導緻三步握手直接失敗,而且這個篡改發生在離開伺服器之後到達用戶端之前。
這個問題是微信網絡子產品中排查時間最長也是花費精力最多的一個問題,不僅因為很長一段時間内無案例可分析,也因為在重制後,聯系了大量的同僚和外部有關人的幫忙,想排查出罪魁禍首。但因為中間涉及的環節和營運商相關部門過多,無法繼續排查下去,最終也沒找到根本原因。 解決辦法:伺服器更改 net.ipv4.tcp_timestamps = 0。
這段時間是痛并快樂着,見識到了各種極差的網絡,才切膚感受到移動網絡環境的惡劣程度,但看着我們的網絡性能資料在穩步提升又有種滿足感。截止到今天,已經很少有真正的網絡問題需要跟進了。這也是我們能有時間開始把這些代碼開源出去的很大的一個原因。
大概一年前(大約2015年10月),我們開始有想法把基礎元件開源出去,當時大家都在糾結叫什麼名字好呢?此時恰逢《火星救援》正在熱映,一位同僚說幹脆叫 Mars 吧,于是就定下來叫了 Mars。看了看代碼,發現想要開源出去可能還是需要做一些其他工作的。
首先,代碼風格方面,因為最初我們使用檔案名、函數名、變量名的規則是内部定義的規則,為了能讓其他人讀起來更舒心,我們決定把代碼風格改為谷歌風格,比如:變量名一律小寫, 單詞之間用下劃線連接配接;左大括号不換行等等。但是為了更好的區分通路空間,我們又在谷歌代碼風格進行了一些變通,比如:私有函數全部是”__”開頭;函數參數全部以”_”開頭 等等。
其次,雖然最初的設計一直是秉承着業務性無關的設計,但在實際開發過程中仍然難免帶上了微信的業務性相關代碼,比較典型的就是 newdns 。為了 Mars 以後的維護以及保證開源出去代碼的同源,在開源出去之前必須把這些業務性有關的代碼抽離出來,抽離後的結構如下:
mars-open 也就是要開源出去的代碼,獨立 git repo。 mars-private 是可能開源出去的代碼,依賴 mars-open。 mars-wechat 是微信業務性相關的代碼,依賴 mars-open 和 mars-private。
最後,為了接口更易用,對調用接口以及回調接口的參數也進行了反複思考與修改。
在 Mars之前,是直接給 Android 提供動态庫(.so),因為代碼邏輯都已經固定,不需要有可定制的部分。給 Apple 系平台提供靜态庫(.a),因為對外暴露的函數幾乎不會改變,直接把相應的頭檔案放到相應的項目裡就行。但對外開源就完全不一樣了:日志的加密算法可能别人需要自己實作;長連或者短連的標頭有人需要自己定制;對外接口的頭檔案我們可能會修改……
為了讓使用者可定制代碼,對于編譯 Android 平台我們提供了兩種選擇:
1. 動态庫。有些可能需要定制的代碼都提供了預設實作。 2. 先編譯靜态庫,再編譯動态庫。
編譯出來靜态庫後,實作自己需要定制的代碼後,執行 ndk-build 後即可編譯出來動态庫。 對于 Apple 系平台,把頭檔案全部收攏為 Mars 維護,直接編譯出 Framework。
▲ 成型的 Mars 結構圖如上圖所示
我們做的一直都不是滿足所有需求的元件,隻是做了一個更适合我們使用的元件,這裡也列了下和同類型的開源代碼的對比。
可以看出:
Mars 中包括一個完整的高性能的日志元件 xlog; Mars 中 STN 是一個跨平台的 socket 層解決方案,并不支援完整的 HTTP 協定; Mars 中 STN 子產品是更加貼合“移動網際網路”、“移動平台”特性的網絡解決方案,尤其針對弱網絡、平台特性等有很多的相關優化政策。
總的來說,Mars 是一個結合移動 App 所設計的基于 socket 層的解決方案,在網絡調優方面有更好的可控性,對于 HTTP 完整協定的支援,已經考慮後續版本會加入。
經常有朋友和我說:發現網絡信号差的時候或者其他應用不能用的時候,微信仍然能發出去消息。不知不覺我們好像什麼都沒做,回頭看,原來我們已經做了這麼多。
我想,并不是任何一行代碼都可以經曆日活躍5億使用者的考驗,感謝微信給我們提供了這麼一個平台。現在我們想把這些代碼和你們分享,營運方式上 Mars 所開源出去的代碼會和微信所用的代碼保持同源,所有開源出去的代碼也首先會在微信上驗證通過後再公開。
開源并不是結束,隻是開始。我們後續仍然會繼續探索在移動網際網路下的網絡優化。Talk is cheap,show you our code。
[1] 網絡程式設計基礎資料: [2] 有關IM/推送的通信格式、協定的選擇: [3] 有關IM/推送的心跳保活處理: [4] 有關WEB端即時通訊開發: [5] 有關IM架構設計: [6] 有關IM安全的文章: [7] 有關實時音視訊開發: [8] IM開發綜合文章: [9] 開源移動端IM技術架構資料: [10] 有關推送技術的文章: [11] 更多即時通訊技術好文分類: <a target="_blank" href="https://link.zhihu.com/?target=http%3A//www.52im.net/forum.php%3Fmod%3Dcollection%26op%3Dall">淘帖 - 即時通訊開發者社群!</a>