微信終端跨平臺組件Mars正式開源!

jopen 7年前發布 | 42K 次閱讀 Mars 微信

微信終端跨平臺組件Mars正式開源!

今天,2017 微信公開課 PRO 版在廣州亞運城拉開了序幕。微信作為一個工具,以最高效率、最短時間的方法來幫助用戶完成任務,在現場,我們甚至于可以看到“掃一掃”的各種場景“想象力”。而除了張小龍對于小程序的解讀、小程序場景案例等之外,我們也格外關注微信在開源方面的卓然成果。今天,微信終端跨平臺組件 Mars 正式宣布開源。從移動互聯網的興起到現如今“平臺無關”的跨端流行,在 IM 方面,弱網絡一直是橫亙在應用開發者面前的一大問題,Mars 團隊成員基于微信業務需求,進行了大量的優化工作。在這篇文章中,作者回顧了 Mars 的起源及研發歷程,希望能夠給正在探索網絡優化的朋友帶來啟發。 

背景 

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 時用了大量的隨機函數也是為了規避大量的用戶同時訪問同一臺服務器而做的。 

在這四年,我學到最多的就是簡單和平衡。 把方案做的盡可能簡單,這樣才不容易出錯。設計方案時大多數時候都不可能滿足所有想達到的條件,這個時候就需要去平衡各個因素。在組件中一個很好的例子就是長連接的連接頻率(具體實現見 longlink_connect_monitor.cc),這個連接頻率就是綜合耗電量,流量,網絡高可用,用戶行為等因素進行綜合考慮的。 

Mars 的發展歷程 

階段一:讓微信跑起來 

跨平臺基礎組件的需求起源于微信,首要目標當然是先承載起微信業務。為了不局限于微信,滿足跨平臺、跨業務的設計目標,在設計上,網絡組件定位為客戶端與服務端之間的無狀態網絡信令通道,即交互方式主要包含一來一回、主動 push 兩種方式。這使得基礎組件無需考慮請求間的關聯性、時序性,核心接口得到了極大的簡化。同時,簡潔的交互也使得業務邏輯的耦合極少。目前基礎組件與業務的交互只包括:編解碼、auth 狀態查詢兩部分。核心接口如下:(具體見 stn_logic.h)。 

微信終端跨平臺組件Mars正式開源!

在線程模型的選擇上,最早使用的是多線程模型。當需要異步做一個工作,就起一個線程。多線程勢必少不了鎖。但當灰度幾次之后發現,想要規避死鎖的四個必要條件并沒有想象中的那么容易。用戶使用場景復雜,客戶端的時序、狀態的影響因素多,例如網絡切換事件、前后臺事件、定時器事件、網絡事件、任務事件等,導致了不少的死鎖現象和對象析構時序錯亂導致的內存非法訪問問題。 

這時,我們開始思考,多線程確實有它的優點:可以并發甚至并行提高運行速度。但是對于網絡模塊來說,性能瓶頸主要是在網絡耗時上,并不在于本地程序執行速度上。那為何不把大部分程序執行改成串行的,這樣就不會存在多線程臨界區的問題,無鎖自然就不會死鎖。 

因此,我們目前使用了消息隊列的方案(具體實現見 comm/messagequeue 目錄),把絕大多數非阻塞操作放到消息隊列里執行。并且規定,基礎組件與調用方之間的交互必須: 

1.盡快完成,不進行任何阻塞操作; 

2.單向調用,避免形成環狀的復雜時序。 

消息隊列的引入很好的改善了死鎖問題,但消息隊列的線程模型中,我們還是不能避免存在需要阻塞的調用,例如網絡操作。在未來的嘗試中,我們計劃引入協程的方式,將線程模型盡可能的簡化。 

在其他技術選型上,有時甚至需要細節到 API 的使用,比如考慮平臺兼容性問題,舍棄了一些函數的線程安全版本,使用了 asctime、localtime、rand 等非線程安全的版本。 

階段二:修煉內功 

在多次的灰度驗證、數據比對下,微信各平臺的網絡邏輯順利的過渡到了統一基礎組件。為了有效的驗證組件的效果,我們開發了 smc 的統計監控組件,開始關注網絡的各項指標,進行網絡基礎研究與優化,尤其是關注移動網絡的特征。 

  • 基礎網絡優化。常規的網絡能力,例如 DNS 防劫持、動態 IP 下發、就近接入、容災恢復等,在這一階段得到逐步的建設與完善。除此之外,Mars 的網絡模塊是基于 socket 層的網絡解決方案,在缺失大而全的 HTTP 能力的同時,卻可以將優化做到更細致,細致到連接策略、連接超時、多級讀寫超時、收發策略等每個網絡過程中。例如,當遇到弱網絡下連通率較低,或者某些連通率不好的的服務器影響使用時,我們使用了復合連接(代碼見 complexconnect.inl)和 IP 排序(代碼見 simple_ipport_sort.cc)的方案很好的應對這兩個問題。
  • 平臺特性優化。雖然 Mars 是跨平臺的基礎組件,但在很多設計上是需要結合各平臺的特性的。例如為了盡量減少頻繁的喚醒手機,引入了智能心跳,并且在智能心跳中考慮了 Android 的 alarm 對齊特性(具體實現見 smart_heartbeat.cc)。再如在網絡切換時,為了平滑切換的過程,使用了 iOS 中網絡的特性,在 iOS 中做了延遲處理等。
  • 移動特性優化。微信的使用場景大部分是在手機端進行使用,在組件的設計過程中,我們也會研究移動設備的特性,并進行結合優化。例如,結合移動設備的無線電資源控制器(RRC)的狀態切換,對一些性能要求特別特別敏感的請求,進行提前激活的優化處理等。

階段三:“抓妖記” 

基礎組件全量上線微信后,以微信的用戶量,當然也會遇到各種各樣的“妖”。例如,寫網絡程序躲不開運營商。印象比較深刻的某地的用戶反饋連接 WiFi 時,微信不可用,后來 tcpdump 發現,當包的大小超過一定大小后就發不出去。解決方案:在 WiFi 網絡下強制把 MSS 改為 1400(代碼見 unix_socket.cc)。 

做移動客戶端更避不開手機廠商。一次遇到了一個百思不得其解的 crash,堆棧如下: 

微信終端跨平臺組件Mars正式開源!

看堆棧結合程序 xlog 分析,非阻塞 socket 卡在了 connect 函數里超過了 6 min, 被我們自帶的 anr 檢測(代碼見 anr.cc)發現然后自殺。最后實在束手無策,聯系廠商一起排查,最終查明原因:為了省電,當手機鎖屏時連的不是 WiFi 且又沒有下行網絡數據時,芯片 gate 會關閉,block 住所有網絡請求,直到有下行數據或者超過 20min 才會放開。當手機有網絡即使是手機網絡的情況下,很難沒有下行數據,所以基本不會觸發組件自帶的 anr 檢測,但當手機沒連接任何網絡時,就很容易觸發。解決方案:廠商修改代碼邏輯,當沒有任何網絡時不 block 網絡請求。 

運營商和手機廠商對我們來說已經是一個黑盒,但其實也遇到過更黑的黑盒。當手機長時間不重啟,有極小概率不能繼續使用微信,重啟手機會恢復。但因為一直找不到一個愿意配合我們又滿足條件的用戶,導致這個問題很長一段時間內都沒有任何進展,最終偶然一個機會,在一臺測試機器上重現了該問題,tcpdump 發現在三步握手階段,服務器帶回的客戶端帶過去的 tsval 字段被篡改,導致三步握手直接失敗,而且這個篡改發生在離開服務器之后到達客戶端之前。 

微信終端跨平臺組件Mars正式開源!

這個問題是微信網絡模塊中排查時間最長也是花費精力最多的一個問題,不僅因為很長一段時間內無案例可分析,也因為在重現后,聯系了大量的同事和外部有關人的幫忙,想排查出罪魁禍首。但因為中間涉及的環節和運營商相關部門過多,無法繼續排查下去,最終也沒找到根本原因。 

微信終端跨平臺組件Mars正式開源!

這段時間是痛并快樂著,見識到了各種極差的網絡,才切膚感受到移動網絡環境的惡劣程度,但看著我們的網絡性能數據在穩步提升又有種滿足感。截止到今天,已經很少有真正的網絡問題需要跟進了。這也是我們能有時間開始把這些代碼開源出去的很大的一個原因。 

Mars 介紹 

講述了一大堆 Mars 的發展歷程,終于來到主角的介紹了。大概一年前,我們開始有想法把基礎組件開源出去,當時大家都在糾結叫什么名字好呢?此時恰逢《火星救援》正在熱映,一位同事說干脆叫 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。 

為了能讓開發者快速的入門,我們提供了 Android、iOS、OS X 平臺的 demo,其他平臺的編譯和 demo 會在不久就加上支持。 

成型的 Mars 結構圖如下: 

微信終端跨平臺組件Mars正式開源!

業界對比 

我們做的一直都不是滿足所有需求的組件,只是做了一個更適合我們使用的組件,這里也列了下和同類型的開源代碼的對比。 

微信終端跨平臺組件Mars正式開源!

可以看出: 

  • Mars 中包括一個完整的高性能的日志組件 xlog;
  • Mars 中 STN 是一個跨平臺的 socket 層解決方案,并不支持完整的 HTTP 協議;
  • Mars 中 STN 模塊是更加貼合“移動互聯網”、“移動平臺”特性的網絡解決方案,尤其針對弱網絡、平臺特性等有很多的相關優化策略。

總的來說,Mars 是一個結合移動 App 所設計的基于 socket 層的解決方案,在網絡調優方面有更好的可控性,對于 HTTP 完整協議的支持,已經考慮后續版本會加入。 

總結 

經常有朋友和我說:發現網絡信號差的時候或者其他應用不能用的時候,微信仍然能發出去消息。不知不覺我們好像什么都沒做,回頭看,原來我們已經做了這么多。我想,并不是任何一行代碼都可以經歷日活躍 5 億用戶的考驗,感謝微信給我們提供了這么一個平臺。現在我們想把這些代碼和你們分享,運營方式上 Mars 所開源出去的代碼會和微信所用的代碼保持同源,所有開源出去的代碼也首先會在微信上驗證通過后再公開。開源并不是結束,只是開始。我們后續仍然會繼續探索在移動互聯網下的網絡優化。

來自: www.iteye.com

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!