微信技術分享:微信的海量IM聊天訊息序列號生成實踐(演算法原理篇)
1、點評
對於IM系統來說,如何做到IM聊天訊息離線差異拉取(差異拉取是為了節省流量)、訊息多端同步、訊息順序保證等,是典型的IM技術難點。
就像即時通訊網整理的以下IM開發乾貨系列一樣:
《IM訊息送達保證機制實現(一):保證線上實時訊息的可靠投遞》
《IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?
上面這些文章所涉及的IM聊天訊息的省流量、可靠投遞、離線拉取、時序性、一致性、多端同步等等問題,總結下來其實就是要解決好一個問題:即如何保證聊天訊息的唯一性判定和順序判定。
很多群友在討論這個問題的時候,普遍考慮的是使用整型自增序列號作為訊息ID(即MsgId):這樣既能保證訊息的唯一性又方便保證順序性,但問題是在分散式情況下是很難保證訊息id的唯一性且順序遞增的,維護id生成的一致性難度太大了(網路延遲、調試出錯等等都可能導致不同的機器取到的訊息id存在碰撞的可能)。
不過,通過本文中微信團隊分享的微信訊息序列號生成思路,實際上要解決訊息的唯一性、順序性問題,可以將一個技術點分解成兩個:即將原先每條訊息一個自增且唯一的訊息ID分拆成兩個關鍵屬性——訊息ID(msgId)、訊息序列號(seqId),即訊息ID只要保證唯一性而不需要兼顧順序性(比如直接用UUID)、訊息序列號只要保證順序性而不需要兼顧唯一性(就像本文中微信的思路一樣),這樣的技術分解就能很好的解決原本一個訊息ID既要保證唯一性又要保證順序性的難題。
那麼,如何優雅地解決“訊息序列號只要保證順序性而不需要兼顧唯一性”的問題呢?這就是本文所要分享的內容,強烈建議深入理解和閱讀。
本文因篇幅較長,分為上下兩篇,敬請點選閱讀:
上篇:《微信技術分享:微信的海量IM聊天訊息序列號生成實踐(演算法原理篇)》(本文)
學習交流:
- 即時通訊開發交流3群:185926912[推薦]
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
2、正文引言
微信在立項之初,就已確立了利用資料版本號(注:具體的實現也就是本文要分享的訊息序列號)實現終端與後臺的資料增量同步機制,確保發訊息時訊息可靠送達對方手機,避免了大量潛在的家庭糾紛。時至今日,微信已經走過第五個年頭,這套同步機制仍然在訊息收發、朋友圈通知、好友資料更新等需要資料同步的地方發揮著核心的作用。
而在這同步機制的背後,需要一個高可用、高可靠的訊息序列號生成器來產生同步資料用的版本號(注:因為序列號天生的遞增特性,完全可以當版本號來使用,但又不僅限於版本號的用途)。這個訊息序列號生成器我們微信內部稱之為 seqsvr ,目前已經發展為一個每天萬億級呼叫的重量級系統,其中每次申請序列號平時呼叫耗時1ms,99.9%的呼叫耗時小於3ms,服務部署於數百臺4核 CPU 伺服器上。
本篇將重點介紹微信的訊息序列號生成器 seqsvr 的演算法原理、架構核心思想,以及 seqsvr 隨著業務量快速上漲所做的架構演變(下篇《微信技術分享:微信的海量IM聊天訊息序列號生成實踐(容災方案篇)》會著重討論分散式容災方案,敬請關注)。
3、關於作者
曾欽鬆:微信高階工程師,負責過微信基礎架構、微信翻譯引擎、微信圍棋PhoenixGo,致力於高可用高效能後臺系統的設計與研發。2011年畢業於西安電子科技大學,早先曾在騰訊搜搜從事檢索架構、分散式資料庫方面的工作。
4、技術思路
微信伺服器端為每一份需要與客戶端同步的資料(例如聊天訊息)都會賦予一個唯一的、遞增的序列號(後文稱為 sequence ),作為這份資料的版本號(這是利用了序列號遞增的特性)。在客戶端與伺服器端同步的時候,客戶端會帶上已經同步下去資料的最大版本號,後臺會根據客戶端最大版本號與伺服器端的最大版本號,計算出需要同步的增量資料,返回給客戶端。這樣不僅保證了客戶端與伺服器端的資料同步的可靠性,同時也大幅減少了同步時的冗餘資料(就像這篇文章中討論的一樣:《如何保證IM實時訊息的“時序性”與“一致性”?》)。
這裡不用樂觀鎖機制來生成版本號,而是使用了一個獨立的 seqsvr 來處理序列號操作:
1)一方面因為業務有大量的 sequence 查詢需求——查詢已經分配出去的最後一個 sequence ,而基於 seqsvr 的查詢操作可以做到非常輕量級,避免對儲存層的大量 IO 查詢操作;
2)另一方面微信使用者的不同種類的資料存在不同的 Key-Value 系統中,使用統一的序列號有助於避免重複開發,同時業務邏輯可以很方便地判斷一個使用者的各類資料是否有更新。
從 seqsvr 申請的、用作資料版本號的 sequence ,具有兩種基本的性質:
1)遞增的64位整型變數;
2)每個使用者都有自己獨立的64位 sequence 空間。
舉個例子,小明當前申請的 sequence 為100,那麼他下一次申請的 sequence ,可能為101,也可能是110,總之一定大於之前申請的100。而小紅呢,她的 sequence 與小明的 sequence 是獨立開的,假如她當前申請到的 sequence 為50,然後期間不管小明申請多少次 sequence 怎麼折騰,都不會影響到她下一次申請到的值(很可能是51)。
這裡用了每個使用者獨立的64位 sequence 的體系,而不是用一個全域性的64位(或更高位) sequence ,很大原因是全域性唯一的 sequence 會有非常嚴重的申請互斥問題,不容易去實現一個高效能高可靠的架構。對微信業務來說,每個使用者獨立的64位 sequence 空間已經滿足業務要求。
目前 sequence 用在終端與後臺的資料同步外,同時也廣泛用於微信後臺邏輯層的基礎資料一致性cache中,大幅減少邏輯層對儲存層的訪問。雖然一個用於終端——後臺資料同步,一個用於後臺cache的一致性保證,場景大不相同。
但我們仔細分析就會發現,兩個場景都是利用 sequence 可靠遞增的性質來實現資料的一致性保證,這就要求我們的 seqsvr 保證分配出去的 sequence 是穩定遞增的,一旦出現回退必然導致各種資料錯亂、訊息消失;另外,這兩個場景都非常普遍,我們在使用微信的時候會不知不覺地對應到這兩個場景:小明給小紅髮訊息、小紅拉黑小明、小明發一條失戀狀態的朋友圈,一次簡單的分手背後可能申請了無數次 sequence。
微信目前擁有數億的活躍使用者,每時每刻都會有海量 sequence 申請,這對 seqsvr 的設計也是個極大的挑戰。那麼,既要 sequence 可靠遞增,又要能頂住海量的訪問,要如何設計 seqsvr 的架構?我們先從 seqsvr 的架構原型說起。
5、具體的技術架構原型
不考慮 seqsvr 的具體架構的話,它應該是一個巨大的64位陣列,而我們每一個微信使用者,都在這個大數組裡獨佔一格8 bytes 的空間,這個格子就放著使用者已經分配出去的最後一個 sequence:cur_seq。每個使用者來申請sequence的時候,只需要將使用者的cur_seq+=1,儲存回陣列,並返回給使用者。
▲ 圖1:小明申請了一個sequence,返回101
5.1 預分配中間層
任何一件看起來很簡單的事,在海量的訪問量下都會變得不簡單。前文提到,seqsvr 需要保證分配出去的sequence 遞增(資料可靠),還需要滿足海量的訪問量(每天接近萬億級別的訪問)。滿足資料可靠的話,我們很容易想到把資料持久化到硬碟,但是按照目前每秒千萬級的訪問量(~10^7 QPS),基本沒有任何硬碟系統能扛住。
後臺架構設計很多時候是一門關於權衡的哲學,針對不同的場景去考慮能不能降低某方面的要求,以換取其它方面的提升。仔細考慮我們的需求,我們只要求遞增,並沒有要求連續,也就是說出現一大段跳躍是允許的(例如分配出的sequence序列:1,2,3,10,100,101)。
於是我們實現了一個簡單優雅的策略:
1)記憶體中儲存最近一個分配出去的sequence:cur_seq,以及分配上限:max_seq;
2)分配sequence時,將cur_seq++,同時與分配上限max_seq比較:如果cur_seq > max_seq,將分配上限提升一個步長max_seq += step,並持久化max_seq;
3)重啟時,讀出持久化的max_seq,賦值給cur_seq。
▲ 圖2:小明、小紅、小白都各自申請了一個sequence,但只有小白的max_seq增加了步長100
這樣通過增加一個預分配 sequence 的中間層,在保證 sequence 不回退的前提下,大幅地提升了分配 sequence 的效能。實際應用中每次提升的步長為10000,那麼持久化的硬碟IO次數從之前~10^7 QPS降低到~10^3 QPS,處於可接受範圍。在正常運作時分配出去的sequence是順序遞增的,只有在機器重啟後,第一次分配的 sequence 會產生一個比較大的跳躍,跳躍大小取決於步長大小。
5.2 分號段共享儲存
請求帶來的硬碟IO問題解決了,可以支援服務平穩執行,但該模型還是存在一個問題:重啟時要讀取大量的max_seq資料載入到記憶體中。
我們可以簡單計算下,以目前 uid(使用者唯一ID)上限2^32個、一個 max_seq 8bytes 的空間,資料大小一共為32GB,從硬碟載入需要不少時間。另一方面,出於資料可靠性的考慮,必然需要一個可靠儲存系統來儲存max_seq資料,重啟時通過網路從該可靠儲存系統載入資料。如果max_seq資料過大的話,會導致重啟時在資料傳輸花費大量時間,造成一段時間不可服務。
為了解決這個問題,我們引入號段 Section 的概念,uid 相鄰的一段使用者屬於一個號段,而同個號段內的使用者共享一個 max_seq,這樣大幅減少了max_seq 資料的大小,同時也降低了IO次數。
▲ 圖3:小明、小紅、小白屬於同個Section,他們共用一個max_seq。在每個人都申請一個sequence的時候,只有小白突破了max_seq上限,需要更新max_seq並持久化
目前 seqsvr 一個 Section 包含10萬個 uid,max_seq 資料只有300+KB,為我們實現從可靠儲存系統讀取max_seq 資料重啟打下基礎。
5.3 工程實現
工程實現在上面兩個策略上做了一些調整,主要是出於資料可靠性及災難隔離考慮:
1)把儲存層和快取中間層分成兩個模組 StoreSvr 及 AllocSvr 。StoreSvr 為儲存層,利用了多機 NRW 策略來保證資料持久化後不丟失; AllocSvr 則是快取中間層,部署於多臺機器,每臺 AllocSvr 負責若干號段的 sequence 分配,分攤海量的 sequence 申請請求。
2)整個系統又按 uid 範圍進行分 Set,每個 Set 都是一個完整的、獨立的 StoreSvr+AllocSvr 子系統。分 Set 設計目的是為了做災難隔離,一個 Set 出現故障只會影響該 Set 內的使用者,而不會影響到其它使用者。
▲ 圖4:原型架構圖
6、本篇小結
寫到這裡把 seqsvr 基本原型講完了,正是如此簡單優雅的模型,可靠、穩定地支撐著微信五年來的高速發展。五年裡訪問量一倍又一倍地上漲,seqsvr 本身也做過大大小小的重構,但 seqsvr 的分層架構一直沒有改變過,並且在可預見的未來裡也會一直保持不變。
原型跟生產環境的版本存在一定差距,最主要的差距在於容災上。像微信的 IM 類應用,對系統可用性非常敏感,而 seqsvr 又處於收發訊息、朋友圈等功能的關鍵路徑上,對可用性要求非常高,出現長時間不可服務是分分鐘寫故障報告的節奏。
本文的下篇《微信技術分享:微信的海量IM聊天訊息序列號生成實踐(容災方案篇)會講講 seqsvr 的容災方案演變。
附錄:更多QQ、微信團隊原創技術文章
《騰訊技術分享:騰訊是如何大幅降低頻寬和網路流量的(圖片壓縮篇)》
《騰訊技術分享:騰訊是如何大幅降低頻寬和網路流量的(音視訊技術篇)》
《騰訊技術分享:Android版手機QQ的快取監控與優化實踐》
《微信團隊分享:iOS版微信的高效能通用key-value元件技術實踐》
《微信團隊分享:iOS版微信是如何防止特殊字元導致的炸群、APP崩潰的?》
《騰訊技術分享:Android手Q的執行緒死鎖監控系統技術實踐》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(上篇)》
《QQ音樂團隊分享:Android中的圖片壓縮技術詳解(下篇)》
《騰訊團隊分享 :一次手Q聊天介面中圖片顯示bug的追蹤過程分享》
《微信團隊分享:微信Android版小視訊編碼填過的那些坑》
《微信團隊披露:微信介面卡死超級bug“15。。。。”的來龍去脈》
《月活8.89億的超級IM微信是如何進行Android端相容測試的》
《微信客戶端團隊負責人技術訪談:如何著手客戶端效能監控和優化》
《微信團隊原創分享:Android版微信的臃腫之困與模組化實踐之路》
《微信團隊原創分享:微信客戶端SQLite資料庫損壞修復實踐》
《騰訊原創分享(一):如何大幅提升行動網路下手機QQ的圖片傳輸速度和成功率》
《騰訊原創分享(二):如何大幅壓縮行動網路下APP的流量消耗(下篇)》
《騰訊原創分享(三):如何大幅壓縮行動網路下APP的流量消耗(上篇)》
《如約而至:微信自用的移動端IM網路層跨平臺元件庫Mars已正式開源》
《開源libco庫:單機千萬連線、支撐微信8億使用者的後臺框架基石 [原始碼下載]》
《微信新一代通訊安全解決方案:基於TLS1.3的MMTLS詳解》
《微信團隊原創分享:Android版微信後臺保活實戰分享(程序保活篇)》
《微信團隊原創分享:Android版微信後臺保活實戰分享(網路保活篇)》
《Android版微信從300KB到30MB的技術演進(PPT講稿) [附件下載]》
《微信團隊原創分享:Android版微信從300KB到30MB的技術演進》
《微信技術總監談架構:微信之道——大道至簡(PPT講稿) [附件下載]》
《微信海量使用者背後的後臺系統儲存架構(視訊+PPT) [附件下載]》
《微信非同步化改造實踐:8億月活、單機千萬連線背後的後臺解決方案》
《架構之道:3個程式設計師成就微信朋友圈日均10億釋出量[有視訊]》
《微信團隊原創分享:Android記憶體洩漏監控和優化技巧總結》
《微信團隊原創Android資源混淆工具:AndResGuard [有原始碼]》
《移動端IM實踐:Android版微信如何大幅提升互動效能(一)》
《移動端IM實踐:Android版微信如何大幅提升互動效能(二)》
《移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》
《移動端IM實踐:谷歌訊息推送服務(GCM)研究(來自微信)》
《信鴿團隊原創:一起走過 iOS10 上訊息推送(APNS)的坑》
《騰訊TEG團隊原創:基於MySQL的分散式資料庫TDSQL十年鍛造經驗分享》
《微信多媒體團隊訪談:音視訊開發的學習、微信的音視訊技術和挑戰等》
《瞭解iOS訊息推送一文就夠:史上最全iOS Push技術詳解》
《騰訊資深架構師乾貨總結:一文讀懂大型分散式系統設計的方方面面》
《騰訊音視訊實驗室:使用AI黑科技實現超低位元速率的高清實時視訊聊天》
《騰訊技術分享:微信小程式音視訊與WebRTC互通的技術思路和實踐》
《手把手教你讀取Android版微信和手Q的聊天記錄(僅作技術研究學習)》
《微信技術分享:微信的海量IM聊天訊息序列號生成實踐(演算法原理篇)》
>> 更多同類文章 ……
(本文同步釋出於:http://www.52im.net/thread-1998-1-1.html)