1. 程式人生 > >從無到有:微信後臺系統的演進之路

從無到有:微信後臺系統的演進之路

從無到有

2011.1.21 微信正式釋出。這一天距離微信專案啟動日約為2個月。就在這2個月裡,微信從無到有,大家可能會好奇這期間微信後臺做的最重要的事情是什麼?

我想應該是以下三件事:

1. 確定了微信的訊息模型

微信起初定位是一個通訊工具,作為通訊工具最核心的功能是收發訊息。微信團隊源於廣硏團隊,訊息模型跟郵箱的郵件模型也很有淵源,都是儲存轉發。

 微信訊息

 微信訊息模型

圖1展示了這一訊息模型,訊息被髮出後,會先在後臺臨時儲存;為使接收者能更快接收到訊息,會推送訊息通知給接收者;最後客戶端主動到伺服器收取訊息。

2. 制定了資料同步協議

由於使用者的帳戶、聯絡人和訊息等資料都在伺服器儲存,如何將資料同步到客戶端就成了很關鍵的問題。為簡化協議,我們決定通過一個統一的資料同步協議來同步使用者所有的基礎資料。

最初的方案是客戶端記錄一個本地資料的快照(Snapshot),需要同步資料時,將Snapshot帶到伺服器,伺服器通過計算Snapshot與伺服器資料的差異,將差異資料發給客戶端,客戶端再儲存差異資料完成同步。不過這個方案有兩個問題:一是Snapshot會隨著客戶端資料的增多變得越來越大,同步時流量開銷大;二是客戶端每次同步都要計算Snapshot,會帶來額外的效能開銷和實現複雜度。

幾經討論後,方案改為由服務計算Snapshot,在客戶端同步資料時跟隨資料一起下發給客戶端,客戶端無需理解Snapshot,只需儲存起來,在下次資料同步資料時帶上即可。同時,Snapshot被設計得非常精簡,是若干個Key-Value的組合,Key代表資料的型別,Value代表給到客戶端的資料的最新版本號。Key有三個,分別代表:帳戶資料、聯絡人和訊息。這個同步協議的一個額外好處是客戶端同步完資料後,不需要額外的ACK協議來確認資料收取成功,同樣可以保證不會丟資料:只要客戶端拿最新的Snapshot到伺服器做資料同步,伺服器即可確認上次資料已經成功同步完成,可以執行後續操作,例如清除暫存在服務的訊息等等。

此後,精簡方案、減少流量開銷、儘量由伺服器完成較複雜的業務邏輯、降低客戶端實現的複雜度就作為重要的指導原則,持續影響著後續的微信設計開發。記得有個比較經典的案例是:我們在微信1.2版實現了群聊功能,但為了保證新舊版客戶端間的群聊體驗,我們通過伺服器適配,讓1.0版客戶端也能參與群聊。

3. 定型了後臺架構

微信後臺使用三層架構:接入層、邏輯層和儲存層。

  • 接入層提供接入服務,包括長連線入服務和短連線入服務。長連線入服務同時支援客戶端主動發起請求和伺服器主動發起推送;短連線入服務則只支援客戶端主動發起請求。
  • 邏輯層包括業務邏輯服務和基礎邏輯服務。業務邏輯服務封裝了業務邏輯,是後臺提供給微信客戶端呼叫的API。基礎邏輯服務則抽象了更底層和通用的業務邏輯,提供給業務邏輯服務訪問。
  • 儲存層包括資料訪問服務和資料儲存服務。資料儲存服務通過MySQL和SDB(廣硏早期後臺中廣泛使用的Key-Table資料儲存系統)等底層儲存系統來持久化使用者資料。資料訪問服務適配並路由資料訪問請求到不同的底層資料儲存服務,面向邏輯層提供結構化的資料服務。比較特別的是,微信後臺每一種不同型別的資料都使用單獨的資料訪問服務和資料儲存服務,例如帳戶、訊息和聯絡人等等都是獨立的。

微信後臺主要使用C++。後臺服務使用Svrkit框架搭建,服務之間通過同步RPC進行通訊。

Svrkit 框架

 Svrkit 框架

Svrkit是另一個廣硏後臺就已經存在的高效能RPC框架,當時尚未廣泛使用,但在微信後臺卻大放異彩。作為微信後臺基礎設施中最重要的一部分,Svrkit這幾年一直不斷在進化。我們使用Svrkit構建了數以千計的服務模組,提供數萬個服務介面,每天RPC呼叫次數達幾十萬億次。

這三件事影響深遠,乃至於5年後的今天,我們仍繼續沿用最初的架構和協議,甚至還可以支援當初1.0版的微信客戶端。

這裡有一個經驗教訓——運營支撐系統真的很重要。第一個版本的微信後臺是倉促完成的,當時只是完成了基礎業務功能,並沒有配套的業務資料統計等等。我們在開放註冊後,一時間竟沒有業務監控頁面和資料曲線可以看,註冊使用者數是臨時從資料庫統計的,線上數是從日誌裡提取出來的,這些資料通過每個小時執行一次的指令碼(這個指令碼也是當天臨時加的)統計出來,然後自動發郵件到郵件組。還有其他各種業務資料也通過郵件進行釋出,可以說郵件是微信初期最重要的資料門戶。

2011.1.21 當天最高併發線上數是 491,而今天這個數字是4億。

小步慢跑

在微信釋出後的4個多月裡,我們經歷了釋出後火爆註冊的驚喜,也經歷了隨後一直不溫不火的困惑。

這一時期,微信做了很多旨在增加使用者好友量,讓使用者聊得起來的功能。打通騰訊微博私信、群聊、工作郵箱、QQ/郵箱好友推薦等等。對於後臺而言,比較重要的變化就是這些功能催生了對非同步佇列的需求。例如,微博私信需要跟外部門對接,不同系統間的處理耗時和速度不一樣,可以通過佇列進行緩衝;群聊是耗時操作,訊息發到群后,可以通過非同步佇列來非同步完成訊息的擴散寫等等。

非同步佇列在群聊中的應用。微信的群聊是寫擴散的,也就是說發到群裡的一條訊息會給群裡的每個人都存一份(訊息索引)。

為什麼不是讀擴散呢?有兩個原因:

  • 群的人數不多,群人數上限是10(後來逐步加到20、40、100,目前是500),擴散的成本不是太大,不像微博,有成千上萬的粉絲,發一條微博後,每粉絲都存一份的話,一個是效率太低,另一個儲存量也會大很多;
  • 訊息擴散寫到每個人的訊息儲存(訊息收件箱)後,接收者到後臺同步資料時,只需要檢查自己收件箱即可,同步邏輯跟單聊訊息是一致的,這樣可以統一資料同步流程,實現起來也會很輕量。

非同步佇列作為後臺資料互動的一種重要模式,成為了同步RPC服務呼叫之外的有力補充,在微信後臺被大量使用

快速成長

微信的飛速發展是從2.0版開始的,這個版本釋出了語音聊天功能。之後微信使用者量急速增長,2011.5使用者量破100萬、2011.7 使用者量破1000萬、2012.3 註冊使用者數突破1億。伴隨著喜人成績而來的,還有一堆幸福的煩惱。

  • 業務快速迭代的壓力

    微信釋出時功能很簡單,主要功能就是發訊息。不過在發語音之後的幾個版本里迅速推出了手機通訊錄、QQ離線訊息、檢視附近的人、搖一搖、漂流瓶和朋友圈等等功能。有個廣為流傳的關於朋友圈開發的傳奇——朋友圈歷經4個月,前後做了30多個版本迭代才最終成型。其實還有一個鮮為人知的故事——那時候因為人員比較短缺,朋友圈後臺長時間只有1位開發人員。

  • 後臺穩定性的要求

    使用者多了,功能也多了,後臺模組數和機器量在不斷翻番,緊跟著的還有各種故障。

幫助我們順利度過這個階段的,是以下幾個舉措:

1. 極簡設計

雖然各種需求撲面而來,但我們每個實現方案都是一絲不苟完成的。實現需求最大的困難不是設計出一個方案並實現出來,而是需要在若干個可能的方案中,甄選出最簡單實用的那個。

這中間往往需要經過幾輪思考——討論——推翻的迭代過程,謀定而後動有不少好處,一方面可以避免做出華而不實的過度設計,提升效率;另一方面,通過詳盡的討論出來的看似簡單的方案,細節考究,往往是可靠性最好的方案。

2. 大系統小做

邏輯層的業務邏輯服務最早只有一個服務模組(我們稱之為mmweb),囊括了所有提供給客戶端訪問的API,甚至還有一個完整的微信官網。這個模組架構類似Apache,由一個CGI容器(CGIHost)和若干CGI組成(每個CGI即為一個API),不同之處在於每個CGI都是一個動態庫so,由CGIHost動態載入。

在mmweb的CGI數量相對較少的時候,這個模組的架構完全能滿足要求,但當功能迭代加快,CGI量不斷增多之後,開始出現問題:

1) 每個CGI都是動態庫,在某些CGI的共用邏輯的介面定義發生變化時,不同時期更新上線的CGI可能使用了不同版本的邏輯介面定義,會導致在執行時出現詭異結果或者程序crash,而且非常難以定位;

2) 所有CGI放在一起,每次大版本釋出上線,從測試到灰度再到全面部署完畢,都是一個很漫長的過程,幾乎所有後臺開發人員都會被同時卡在這個環節,非常影響效率;

3) 新增的不太重要的CGI有時穩定性不好,某些異常分支下會crash,導致CGIHost程序無法服務,發訊息這些重要CGI受影響沒法執行。

於是我們開始嘗試使用一種新的CGI架構——Logicsvr。

CGI

除了API服務外,其他後臺服務模組也遵循“大系統小做”這一實踐準則,微信後臺服務模組數從微信釋出時的約10個模組,迅速上漲到數百個模組。

3. 業務監控

這一時期,後臺故障很多。比故障更麻煩的是,因為監控的缺失,經常有些故障我們沒法第一時間發現,造成故障影響面被放大。

監控的缺失一方面是因為在快速迭代過程中,重視功能開發,輕視了業務監控的重要性,有故障一直是兵來將擋水來土掩;另一方面是基礎設施對業務邏輯監控的支援度較弱。基礎設施提供了機器資源監控和Svrkit服務執行狀態的監控。這個是每臺機器、每個服務標配的,無需額外開發,但是業務邏輯的監控就要麻煩得多了。當時的業務邏輯監控是通過業務邏輯統計功能來做的,實現一個監控需要4步:

1) 申請日誌上報資源;

2) 在業務邏輯中加入日誌上報點,日誌會被每臺機器上的agent收集並上傳到統計中心;

3) 開發統計程式碼;

4) 實現統計監控頁面。

可以想象,這種費時費力的模式會反過來降低開發人員對加入業務監控的積極性。於是有一天,我們去公司內的標杆——即通後臺(QQ後臺)取經了,發現解決方案出乎意料地簡單且強大:

3.1) 故障報告

之前每次故障後,是由QA牽頭出一份故障報告,著重點是對故障影響的評估和故障定級。新的做法是每個故障不分大小,開發人員需要徹底覆盤故障過程,然後商定解決方案,補充出一份詳細的技術報告。這份報告側重於:如何避免同類型故障再次發生、提高故障主動發現能力、縮短故障響應和處理過程。

3.2) 基於 ID-Value 的業務無關的監控告警體系

基於 ID-Value 的監控告警體系

4. KVSvr

微信後臺每個儲存服務都有自己獨立的儲存模組,是相互獨立的。每個儲存服務都有一個業務訪問模組和一個底層儲存模組組成。業務訪問層隔離業務邏輯層和底層儲存,提供基於RPC的資料訪問介面;底層儲存有兩類:SDB和MySQL。

SDB適用於以使用者UIN(uint32_t)為Key的資料儲存,比方說訊息索引和聯絡人。優點是效能高,在可靠性上,提供基於非同步流水同步的Master-Slave模式,Master故障時,Slave可以提供讀資料服務,無法寫入新資料。

由於微信賬號為字母+數字組合,無法直接作為SDB的Key,所以微信帳號資料並非使用SDB,而是用MySQL儲存的。MySQL也使用基於非同步流水複製的Master-Slave模式。

第1版的帳號儲存服務使用Master-Slave各1臺。Master提供讀寫功能,Slave不提供服務,僅用於備份。當Master有故障時,人工切讀服務到Slave,無法提供寫服務。為提升訪問效率,我們還在業務訪問模組中加入了memcached提供Cache服務,減少對底層儲存訪問。

第2版的帳號儲存服務還是Master-Slave各1臺,區別是Slave可以提供讀服務,但有可能讀到髒資料,因此對一致性要求高的業務邏輯,例如註冊和登入邏輯只允許訪問Master。當Master有故障時,同樣只能提供讀服務,無法提供寫服務。

第3版的帳號儲存服務採用1個Master和多個Slave,解決了讀服務的水平擴充套件能力。

第4版的帳號服務底層儲存採用多個Master-Slave組,每組由1個Master和多個Slave組成,解決了寫服務能力不足時的水平擴充套件能力。

最後還有個未解決的問題:單個Master-Slave分組中,Master還是單點,無法提供實時的寫容災,也就意味著無法消除單點故障。另外Master-Slave的流水同步延時對讀服務有很大影響,流水出現較大延時會導致業務故障。於是我們尋求一個可以提供高效能、具備讀寫水平擴充套件、沒有單點故障、可同時具備讀寫容災能力、能提供強一致性保證的底層儲存解決方案,最終KVSvr應運而生。

KVSvr使用基於Quorum的分散式資料強一致性演算法,提供Key-Value/Key-Table模型的儲存服務。傳統Quorum演算法的效能不高,KVSvr創造性地將資料的版本和資料本身做了區分,將Quorum演算法應用到資料的版本的協商,再通過基於流水同步的非同步資料複製提供了資料強一致性保證和極高的資料寫入效能,另外KVSvr天然具備資料的Cache能力,可以提供高效的讀取效能。

KVSvr一舉解決了我們當時迫切需要的無單點故障的容災能力。除了第5版的帳號服務外,很快所有SDB底層儲存模組和大部分MySQL底層儲存模組都切換到KVSvr。隨著業務的發展,KVSvr也不斷在進化著,還配合業務需要衍生出了各種定製版本。現在的KVSvr仍然作為核心儲存,發揮著舉足輕重的作用。

平臺化

平臺化

這種需求越來越多,我們就開始做一個媒體平臺,這個平臺後來從微信後臺分出,演變成了微信公眾平臺,獨立發展壯大,開始了微信的平臺化之路。除微信公眾平臺外,微信後臺的外圍還陸續出現了微信支付平臺、硬體平臺等等一系列平臺。

微信平臺

走出國門

微信走出國門的嘗試開始於3.0版本。從這個版本開始,微信逐步支援繁體、英文等多種語言文字。不過,真正標誌性的事情是第一個海外資料中心的投入使用。

1. 海外資料中心

海外資料中心的定位是一個自治的系統,也就是說具備完整的功能,能夠不依賴於國內資料中心獨立運作。

1) 多資料中心架構

資料中心

多資料中心架構

系統自治對於無狀態的接入層和邏輯層來說很簡單,所有服務模組在海外資料中心部署一套就行了。

但是儲存層就有很大麻煩了——我們需要確保國內資料中心和海外資料中心能獨立運作,但不是兩套隔離的系統各自部署,各玩各的,而是一套業務功能可以完全互通的系統。因此我們的任務是需要保證兩個資料中心的資料一致性,另外Master-Master架構是個必選項,也即兩個資料中心都需要可寫。

2) Master-Master 儲存架構

儲存架構

架構

多資料中心的資料Master-Master架構

3) 資料中心間的資料一致性

這個Master-Master架構可以在不同資料中心間實現資料最終一致性。如何保證業務邏輯在這種資料弱一致性保證下不會出現問題?

這個問題可以被分解為2個子問題:

使用者訪問自己的資料

使用者可以滿世界跑,那是否允許使用者就近接入資料中心就對業務處理流程有很大影響。如果允許就近接入,同時還要保證資料一致性不影響業務,就意味著要麼使用者資料的Master需要可以動態的改變;要麼需要對所有業務邏輯進行仔細梳理,嚴格區分本資料中心和跨資料中心使用者的請求,將請求路由到正確的資料中心處理。

考慮到上述問題會帶來很高昂的實現和維護的複雜度,我們限制了每個使用者只能接入其歸屬資料中心進行操作。如果使用者發生漫遊,其漫遊到的資料中心會自動引導使用者重新連回歸屬資料中心。

這樣使用者訪問自己資料的一致性問題就迎刃而解了,因為所有操作被限制在歸屬資料中心內,其資料是有強一致性保證的。此外,還有額外的好處:使用者自己的資料(如:訊息和聯絡人等)不需要在資料中心間同步,這就大大降低了對資料同步的頻寬需求。

使用者訪問其他使用者的資料

由於不同資料中心之間業務需要互通,使用者會使用到其他資料中心使用者建立的資料。例如,參與其他資料中心使用者建立的群聊,檢視其他資料中心使用者的朋友圈等。

仔細分析後可以發現,大部分場景下對資料一致性要求其實並不高。使用者稍遲些才見到自己被加入某個其他資料中心使用者建的群、稍遲些才見到某個好友的朋友圈動態更新其實並不會帶來什麼問題。在這些場景下,業務邏輯直接訪問本資料中心的資料。

當然,還是有些場景對資料一致性要求很高。比方說給自己設定微訊號,而微訊號是需要在整個微信帳號體系裡保證唯一的。我們提供了全域性唯一的微訊號申請服務來解決這一問題,所有資料中心通過這個服務申請微訊號。這種需要特殊處置的場景極少,不會帶來太大問題。

4) 可靠的資料同步

資料

2. 網路加速

海外資料中心建設週期長,投入大,微信只在香港和加拿大有兩個海外資料中心。但世界那麼大,即便是這兩個資料中心,也還是沒法輻射全球,讓各個角落的使用者都能享受到暢快的服務體驗。

通過在海外實際對比測試發現,微信客戶端在發訊息等一些主要使用場景與主要競品有不小的差距。為此,我們跟公司的架構平臺部、網路平臺部和國際業務部等兄弟部門一起合作,圍繞海外資料中心,在世界各地精心選址建設了數十個POP點(包括信令加速點和圖片CDN網路)。另外,通過對行動網路的深入分析和研究,我們還對微信的通訊協議做了大幅優化。微信最終在對比測試中趕上並超過了主要的競品。

精耕細作

1. 三園區容災

2013.7.22 微信發生了有史以來最大規模的故障,訊息收發和朋友圈等服務出現長達5個小時的故障,故障期間訊息量跌了一半。故障的起因是上海資料中心一個園區的主光纖被挖斷,近2千臺伺服器不可用,引發整個上海資料中心(當時國內只有這一個資料中心)的服務癱瘓。

故障時,我們曾嘗試把接入到故障園區的使用者切走,但收效甚微。雖然數百個線上模組都做了容災和冗餘設計,單個服務模組看起來沒有單點故障問題;但整體上看,無數個服務例項散佈在資料中心各個機房的8千多臺伺服器內,各服務RPC呼叫複雜,呈網狀結構,再加上缺乏系統級的規劃和容災驗證,最終導致故障無法主動恢復。在此之前,我們知道單個服務出現單機故障不影響系統,但沒人知道2千臺伺服器同時不可用時,整個系統會出現什麼不可控的狀況。

其實在這個故障發生之前3個月,我們已經在著手解決這個問題。當時上海資料中心內網交換機異常,導致微信出現一個出乎意料的故障,在13分鐘的時間裡,微信訊息收發幾乎完全不可用。在對故障進行分析時,我們發現一個訊息系統裡一個核心模組三個互備的服務例項都部署在同一機房。該機房的交換機故障導致這個服務整體不可用,進而訊息跌零。這個服務模組是最早期(那個時候微信後臺規模小,大部分後臺服務都部署在一個數據園區裡)的核心模組,服務基於3機冗餘設計,年復一年可靠地執行著,以至於大家都完全忽視了這個問題。

為解決類似問題,三園區容災應運而生,目標是將上海資料中心的服務均勻部署到3個物理上隔離的資料園區,在任意單一園區整體故障時,微信仍能提供無損服務。

1) 同時服務

傳統的資料中心級災備方案是“兩地三中心”,即同城有兩個互備的資料中心,異地再建設一個災備中心,這三個資料中心平時很可能只有一個在提供線上服務,故障時再將業務流量切換到其他資料中心。這裡的主要問題是災備資料中心無實際業務流量,在主資料中心故障時未必能正常切換到災備中心,並且在平時大量的備份資源不提供服務,也會造成大量的資源浪費。

三園區容災的核心是三個資料園區同時提供服務,因此即便某個園區整體故障,那另外兩個園區的業務流量也只會各增加50%。反過來說,只需讓每個園區的伺服器資源跑在容量上限的2/3,保留1/3的容量即可提供無損的容災能力,而傳統“兩地三中心”則有多得多的伺服器資源被閒置。此外,在平時三個園區同時對外服務,因此我們在故障時,需要解決的問題是“怎樣把業務流量切到其他資料園區?”,而不是“能不能把業務流量切到其他資料園區?”,前者顯然是更容易解決的一個問題。

2) 資料強一致

三園區容災的關鍵是儲存模組需要把資料均勻分佈在3個數據園區,同一份資料要在不同園區有2個以上的一致的副本,這樣才能保證任意單一園區出災後,可以不中斷地提供無損服務。由於後臺大部分儲存模組都使用KVSvr,這樣解決方案也相對簡單高效——將KVSvr的每1組機器都均勻部署在3個園區裡。

3) 故障時自動切換

故障

4) 容災效果檢驗

容災

2. 效能優化

 效能優化

3. 防雪崩

防雪崩

我們在一番勒緊褲腰帶節省機器資源、消滅低負載機器後,所有機器的負載都上來了,服務過載變得經常發生了。解決這一問題的有力武器是Svrkit框架裡的具有QoS保障的FastReject機制,可以快速拒絕掉超過服務自身處理能力的請求,即使在過載時,也能穩定地提供有效輸出。

4. 安全加固

近年,網際網路安全事件時有發生,各種拖庫層出不窮。為保護使用者的隱私資料,我們建設了一套資料保護系統——全程票據系統。其核心方案是,使用者登入後,後臺會下發一個票據給客戶端,客戶端每次請求帶上票據,請求在後臺服務的整個處理鏈條中,所有對核心資料服務的訪問,都會被校驗票據是否合法,非法請求會被拒絕,從而保障使用者隱私資料只能使用者通過自己的客戶端發起操作來訪問。

新的挑戰

1. 資源排程系統

資源排程

2. 高可用儲存

基於Quorum演算法的KVSvr已經實現了強一致性、高可用且高效能的Key-Value/Key-Table儲存。最近,微信後臺又誕生了基於Paxos演算法的另一套儲存系統,首先落地的是PhxSQL,一個支援完整MySQL功能,又同時具備強一致性、高可用和高效能的SQL儲存。

文章來自微信公眾號:網際網路架構師