1. 程式人生 > >微信技術分享:微信的海量IM聊天訊息序列號生成實踐(演算法原理篇)

微信技術分享:微信的海量IM聊天訊息序列號生成實踐(演算法原理篇)

1、點評

對於IM系統來說,如何做到IM聊天訊息離線差異拉取(差異拉取是為了節省流量)、訊息多端同步、訊息順序保證等,是典型的IM技術難點。

就像即時通訊網整理的以下IM開發乾貨系列一樣:

IM訊息送達保證機制實現(一):保證線上實時訊息的可靠投遞

IM訊息送達保證機制實現(二):保證離線訊息的可靠投遞

如何保證IM實時訊息的“時序性”與“一致性”?

IM單聊和群聊中的線上狀態同步應該用“推”還是“拉”?

IM群聊訊息如此複雜,如何保證不丟不重?

淺談移動端IM的多點登陸和訊息漫遊原理

IM群聊訊息究竟是存1份(即擴散讀)還是存多份(即擴散寫)?

上面這些文章所涉及的IM聊天訊息的省流量、可靠投遞、離線拉取、時序性、一致性、多端同步等等問題,總結下來其實就是要解決好一個問題:即如何保證聊天訊息的唯一性判定和順序判定。

很多群友在討論這個問題的時候,普遍考慮的是使用整型自增序列號作為訊息ID(即MsgId):這樣既能保證訊息的唯一性又方便保證順序性,但問題是在分散式情況下是很難保證訊息id的唯一性且順序遞增的,維護id生成的一致性難度太大了(網路延遲、調試出錯等等都可能導致不同的機器取到的訊息id存在碰撞的可能)。

不過,通過本文中微信團隊分享的微信訊息序列號生成思路,實際上要解決訊息的唯一性、順序性問題,可以將一個技術點分解成兩個:即將原先每條訊息一個自增且唯一的訊息ID分拆成兩個關鍵屬性——訊息ID(msgId)、訊息序列號(seqId),即訊息ID只要保證唯一性而不需要兼顧順序性(比如直接用UUID)、訊息序列號只要保證順序性而不需要兼顧唯一性(就像本文中微信的思路一樣),這樣的技術分解就能很好的解決原本一個訊息ID既要保證唯一性又要保證順序性的難題。

那麼,如何優雅地解決“訊息序列號只要保證順序性而不需要兼顧唯一性”的問題呢?這就是本文所要分享的內容,強烈建議深入理解和閱讀。

本文因篇幅較長,分為上下兩篇,敬請點選閱讀:

上篇:《微信技術分享:微信的海量IM聊天訊息序列號生成實踐(演算法原理篇)》(本文)

下篇:《微信技術分享:微信的海量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的執行緒死鎖監控系統技術實踐

微信團隊原創分享:iOS版微信的記憶體監控系統技術實踐

讓網際網路更快:新一代QUIC協議在騰訊的技術實踐分享

iOS後臺喚醒實戰:微信收款到賬語音提醒技術總結

騰訊技術分享:社交網路圖片的頻寬壓縮技術演進之路

微信團隊分享:視訊影象的超解析度技術原理和應用場景

微信團隊分享:微信每日億次實時音視訊聊天背後的技術解密

QQ音樂團隊分享:Android中的圖片壓縮技術詳解(上篇)

QQ音樂團隊分享:Android中的圖片壓縮技術詳解(下篇)

騰訊團隊分享:手機QQ中的人臉識別酷炫動畫效果實現詳解

騰訊團隊分享 :一次手Q聊天介面中圖片顯示bug的追蹤過程分享

微信團隊分享:微信Android版小視訊編碼填過的那些坑》 

微信手機端的本地資料全文檢索優化之路》 

企業微信客戶端中組織架構資料的同步更新方案優化實戰

微信團隊披露:微信介面卡死超級bug“15。。。。”的來龍去脈

QQ 18年:解密8億月活的QQ後臺服務介面隔離技術

月活8.89億的超級IM微信是如何進行Android端相容測試的

以手機QQ為例探討移動端IM中的“輕應用”

一篇文章get微信開源移動端資料庫元件WCDB的一切!

微信客戶端團隊負責人技術訪談:如何著手客戶端效能監控和優化

微信後臺基於時間序的海量資料冷熱分級架構設計實踐

微信團隊原創分享:Android版微信的臃腫之困與模組化實踐之路

微信後臺團隊:微信後臺非同步訊息佇列的優化升級實踐分享

微信團隊原創分享:微信客戶端SQLite資料庫損壞修復實踐》 

騰訊原創分享(一):如何大幅提升行動網路下手機QQ的圖片傳輸速度和成功率》 

騰訊原創分享(二):如何大幅壓縮行動網路下APP的流量消耗(下篇)》 

騰訊原創分享(三):如何大幅壓縮行動網路下APP的流量消耗(上篇)》 

微信Mars:微信內部正在使用的網路層封裝庫,即將開源》 

如約而至:微信自用的移動端IM網路層跨平臺元件庫Mars已正式開源》 

開源libco庫:單機千萬連線、支撐微信8億使用者的後臺框架基石 [原始碼下載]》 

微信新一代通訊安全解決方案:基於TLS1.3的MMTLS詳解》 

微信團隊原創分享:Android版微信後臺保活實戰分享(程序保活篇)》 

微信團隊原創分享:Android版微信後臺保活實戰分享(網路保活篇)》 

Android版微信從300KB到30MB的技術演進(PPT講稿) [附件下載]》 

微信團隊原創分享:Android版微信從300KB到30MB的技術演進》 

微信技術總監談架構:微信之道——大道至簡(演講全文)

微信技術總監談架構:微信之道——大道至簡(PPT講稿) [附件下載]》 

如何解讀《微信技術總監談架構:微信之道——大道至簡》

微信海量使用者背後的後臺系統儲存架構(視訊+PPT) [附件下載]

微信非同步化改造實踐:8億月活、單機千萬連線背後的後臺解決方案》 

微信朋友圈海量技術之道PPT [附件下載]》 

微信對網路影響的技術試驗及分析(論文全文)》 

一份微信後臺技術架構的總結性筆記》 

架構之道:3個程式設計師成就微信朋友圈日均10億釋出量[有視訊]》 

快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)

快速裂變:見證微信強大後臺架構從0到1的演進歷程(二)》 

微信團隊原創分享:Android記憶體洩漏監控和優化技巧總結》 

全面總結iOS版微信升級iOS9遇到的各種“坑”》 

微信團隊原創資源混淆工具:讓你的APK立減1M》 

微信團隊原創Android資源混淆工具:AndResGuard [有原始碼]》 

Android版微信安裝包“減肥”實戰記錄》 

iOS版微信安裝包“減肥”實戰記錄》 

移動端IM實踐:iOS版微信介面卡頓監測方案》 

微信“紅包照片”背後的技術難題》 

移動端IM實踐:iOS版微信小視訊功能技術方案實錄》 

移動端IM實踐:Android版微信如何大幅提升互動效能(一)

移動端IM實踐:Android版微信如何大幅提升互動效能(二)

移動端IM實踐:實現Android版微信的智慧心跳機制》 

移動端IM實踐:WhatsApp、Line、微信的心跳策略分析》 

移動端IM實踐:谷歌訊息推送服務(GCM)研究(來自微信)

移動端IM實踐:iOS版微信的多裝置字型適配方案探討》 

信鴿團隊原創:一起走過 iOS10 上訊息推送(APNS)的坑

騰訊信鴿技術分享:百億級實時訊息推送的實戰經驗

IPv6技術詳解:基本概念、應用現狀、技術實踐(上篇)

IPv6技術詳解:基本概念、應用現狀、技術實踐(下篇)

騰訊TEG團隊原創:基於MySQL的分散式資料庫TDSQL十年鍛造經驗分享

微信多媒體團隊訪談:音視訊開發的學習、微信的音視訊技術和挑戰等

瞭解iOS訊息推送一文就夠:史上最全iOS Push技術詳解

騰訊技術分享:微信小程式音視訊技術背後的故事

騰訊資深架構師乾貨總結:一文讀懂大型分散式系統設計的方方面面

微信多媒體團隊樑俊斌訪談:聊一聊我所瞭解的音視訊技術

騰訊音視訊實驗室:使用AI黑科技實現超低位元速率的高清實時視訊聊天

騰訊技術分享:微信小程式音視訊與WebRTC互通的技術思路和實踐

手把手教你讀取Android版微信和手Q的聊天記錄(僅作技術研究學習)

微信技術分享:微信的海量IM聊天訊息序列號生成實踐(演算法原理篇)

>> 更多同類文章 ……

(本文同步釋出於:http://www.52im.net/thread-1998-1-1.html