Jack Jiang

          我的最新工程MobileIMSDK:http://git.oschina.net/jackjiang/MobileIMSDK
          posts - 499, comments - 13, trackbacks - 0, articles - 1

          本文內容和編寫思路是基于鄧昀澤的“大規模并發IM服務架構設計”、“IM的弱網場景優化”兩文的提綱進行的,感謝鄧昀澤的無私分享。

          1、引言

          接上篇《一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等》,本文主要聚焦這套億級用戶的IM架構的一些比較細節但很重要的熱門問題上,比如:消息可靠性、消息有序性、數據安全性、移動端弱網問題等。

          以上這些熱門IM問題每個話題其實都可以單獨成文,但限于文章篇幅,本文不會逐個問題詳細深入地探討,主要以拋磚引玉的方式引導閱讀者理解問題的關鍵,并針對問題提供專項研究文章鏈接,方便有選擇性的深入學習。希望本文能給你的IM開發帶來一些益處。

          本文已同步發布于“即時通訊技術圈”公眾號,歡迎關注。公眾號上的鏈接是:點此進入

          2、系列文章

          為了更好以進行內容呈現,本文拆分兩了上下兩篇。

          本文是2篇文章中的第2篇:

          一套億級用戶的IM架構技術干貨(上篇):整體架構、服務拆分等

          一套億級用戶的IM架構技術干貨(下篇):可靠性、有序性、弱網優化等》(本文)

          本篇主要聚焦這套億級用戶的IM架構的一些比較細節但很重要的熱門問題上。

          3、消息可靠性問題

          消息的可靠性是IM系統的典型技術指標,對于用戶來說,消息能不能被可靠送達(不丟消息),是使用這套IM的信任前提。

          換句話說,如果這套IM系統不能保證不丟消息,那相當于發送的每一條消息都有被丟失的概率,對于用戶而言,一定會不會“放心”地使用它,即“不信任”這套IM。

          從產品經理的角度來說,有這樣的技術障礙存在,再怎么費力的推廣,最終用戶都會很快流失。所以一套IM如果不能保證消息的可靠性,那問題是很嚴重的。

          PS:如果你對IM消息可靠性的問題還沒有一個直觀的映象的話,通過《零基礎IM開發入門(三):什么是IM系統的可靠性?》這篇文章可以通俗易懂的理解它。

          如上圖所示,消息可靠性主要依賴2個邏輯來保障:

          • 1)上行消息可靠性;
          • 2)下行消息可靠性。

          1)針對上行消息的可靠性,可以這樣的思路來處理:

          用戶發送一個消息(假設協議叫PIMSendReq),用戶要給這個消息設定一個本地ID,然后等待服務器操作完成給發送者一個PIMSendAck(本地ID一致),告訴用戶發送成功了。

          如果等待一段時間,沒收到這個ACK,說明用戶發送不成功,客戶端SDK要做重試操作。

          2)針對下行消息的可靠性,可以這樣的思路來處理:

          服務收到了用戶A的消息,要把這個消息推送給B、C、D 3個人。假設B臨時掉線了,那么在線推送很可能會失敗。

          因此確保下行可靠性的核心是:在做推送前要把這個推送請求緩存起來。

          這個緩存由存儲系統來保證,MsgWriter要維護一個(離線消息列表),用戶的一條消息,要同時寫入B、C、D的離線消息列表,B、C、D收到這個消息以后,要給存儲系統一個ACK,然后存儲系統把消息ID從離線消息列表里拿掉。

          針對消息的可靠性問題,具體的解決思路還可以從另一個維度來考慮:即實時消息的可靠性和離線消息的可靠性。

          有興趣可以深入讀一讀這兩篇:

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

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

          而對于離線消息的可靠性來說,單聊和群聊又有很大區別,有關群聊的離線消息可靠投遞問題,可以深入讀一讀《IM開發干貨分享:如何優雅的實現大量離線消息的可靠投遞》。

          4、消息有序性問題

          消息的有序性問題是分布式IM系統中的另一個技術“硬骨頭”。

          因為是分布式系統,客戶端和服務器的時鐘可能是不同步的。如果簡單依賴某一方的時鐘,就會出現大量的消息亂序。

          比如只依賴客戶端的時鐘,A比B時間晚30分鐘。所有A給B發消息,然后B給A回復。

          發送順序是:

          客戶端A:“XXX”

          客戶端B:“YYY”

          接收方的排序就會變成:

          客戶端B:“YYY”

          客戶端A:“XXX”

          因為A的時間晚30分鐘,所有A的消息都會排在后面。

          如果只依賴服務器的時鐘,也會出現類似的問題,因為2個服務器時間可能也不一致。雖然客戶端A和客戶端B時鐘一致,但是A的消息由服務器S1處理,B的消息由服務器S2處理,也會導致同樣消息亂序。

          為了解決這種問題,我的思路是通過可以做這樣一系列的操作來實現。

          1)服務器時間對齊:

          這部分就是后端運維的鍋了,由系統管理員來盡量保障,沒有別的招兒。

          2)客戶端通過時間調校對齊服務器時間:

          比如:客戶端登錄以后,拿客戶端時間和服務器時間做差值計算,發送消息的時候考慮這部分差值。

          在我的im架構里,這個能把時間對齊到100ms這個級,差值再小的話就很困難了,因為協議在客戶端和服務器之間傳遞速度RTT也是不穩定的(網絡傳輸存在不可控的延遲風險嘛)。

          3)消息同時帶上本地時間和服務器時間:

          具體可以這樣的處理:排序的時候,對于同一個人的消息,按照消息本地時間來排;對于不同人的消息,按照服務器時間來排,這是插值排序算法。

          PS:關于消息有序性的問題,顯然也不是上面這三兩句話能講的清楚,如果你想更通俗一理解它,可以讀一讀《零基礎IM開發入門(四):什么是IM系統的消息時序一致性?》。

          另外:從技術實踐可行性的角度來說,《一個低成本確保IM消息時序的方法探討》、《如何保證IM實時消息的“時序性”與“一致性”?》這兩篇中的思路可以借鑒一下。

          實際上,消息的排序問題,還可以從消息ID的角度去處理(也就是通過算法讓消息ID產生順序性,從而根據消息ID就能達到消息排序的目的)。

          有關順序的消息ID算法問題,這兩篇非常值得借鑒:IM消息ID技術專題(一):微信的海量IM聊天消息序列號生成實踐(算法原理篇)》、《IM消息ID技術專題(三):解密融云IM產品的聊天消息ID生成策略》,我就不廢話了。

          5、消息已讀同步問題

          消息的已讀未讀功能,如下圖所示: 

          上圖就是釘釘中的已讀未讀消息。這在企業IM場景下非常有用(因為做領導的都喜歡,你懂的)。

          已讀未讀功能,對于一對一的單聊消息來說,還比較好理解:就是多加一條對應的回執息(當用戶閱讀這條消息時發回)。

          但對于群聊這說,這一條消息有多少人已讀、多少人未讀,想實現這個效果,那就真的有點麻煩了。對于群聊的已讀未讀功能實現邏輯,這里就不展開了,有興趣可以讀一下這篇《IM群聊消息的已讀回執功能該怎么實現?》。

          回歸到本節的主題“已讀同步”的問題,這顯示難度又進一級,因為已讀未讀回執不只是針對“賬號”,現在還要細分到“同一賬號在不同端登陸”的情況,對于已讀回執的同步邏輯來說,這就有點復雜化了。

          在這里,根據我這邊IM架構的實踐經驗,提供一些思路。

          具體來說就是:用戶可能有多個設備登錄同一個賬戶(比如:Web PC和移動端同時登陸),這種情況下的已讀未讀功能,就需要來實現已讀同步,否則在設備1看過的消息,設備2看到依然是未讀消息,從產品的角度來說,這就影響用戶體驗了。

          對于我的im架構來說,已讀同步主要依賴2個邏輯來保證:

          • 1)同步狀態維護,為用戶的每一個Session,維護一個時間戳,保存最后的讀消息時間;
          • 2)如果用戶打開了某個Session,且用戶有多個設備在線,發送一條PIMSyncRead消息,通知其它設備。

          6、數據安全問題

          6.1 基礎

          IM系統架構中的數據安全比一般系統要復雜一些,從通信的角度來說,它涉及到socket長連接通信的安全性和http短連接的兩重安全性。而隨著IM在移動端的流行,又要在安全性、性能、數據流量、用戶體驗這幾個維度上做權衡,所以想要實現一套完善的IM安全架構,要面臨的挑戰是很多的。

          IM系統架構中,所謂的數據安全,主要是通信安全和內容安全。

          6.2 通信安全

          所謂的通信安全,這就要理解IM通信的服務組成。

          目前來說,一個典型的im系統,主要由兩種通信服務組成:

          • 1)socket長連接服務:技術上也就是多數人耳熟能詳的網絡通信這一塊,再細化一點也就是tcp、udp協議這一塊;
          • 2)http短連接服務:也就是最常用的http rest接口那些。

          對于提升長連接的安全性思路,可以深入閱讀《通俗易懂:一篇掌握即時通訊的消息傳輸安全原理》。另外,微信團隊分享的《微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解》一文,也非常有參考意義。

          如果是通信安全級別更高的場景,可以參考《即時通訊安全篇(二):探討組合加密算法在IM中的應用》,文中關于組合加密算法的使用思路非常不錯。

          至于短連接安全性,大家就很熟悉了,開啟https多數情況下就夠用了。如果對于https不甚了解,可以從這幾篇開始:《一文讀懂Https的安全性原理、數字證書、單項認證、雙項認證等》、《即時通訊安全篇(七):如果這樣來理解HTTPS,一篇就夠了》。

          6.3 內容安全

          這個可能不太好理解,上面既然實現了通信安全,那為什么還要糾結“內容安全”?

          我們了解一下所謂的密碼學三大作用:加密( Encryption)、認證(Authentication),鑒定(Identification) 。

          詳細來說就是:

          加密:防止壞人獲取你的數據。

          認證:防止壞人修改了你的數據而你卻并沒有發現。

          鑒權:防止壞人假冒你的身份。

          在上節中,惡意攻擊者如果在通信環節繞開或突破了“鑒權”、“認證”,那么依賴于“鑒權”、“認證”的“加密”,實際上也有可有被破解。

          針對上述問題,那么我們需要對內容進行更加安全獨立的加密處理,就這是所謂的“端到端加密”(E2E)。

          比如,那個號稱無法被破解的IM——Telegram,實際上就是使用了端到端加密技術。

          關于端到端加密,這里就不深入探討,這里有兩篇文章有興趣地可以深入閱讀:

          移動端安全通信的利器——端到端加密(E2EE)技術詳解

          簡述實時音視頻聊天中端到端加密(E2EE)的工作原理

          7、雪崩效應問題

          在分布式的IM架構中,存在雪崩效應問題。

          我們知道,分布式的IM架構中,為了高可用性,用戶每次登陸都是根據負載均衡算法分配到不同的服務器。那么問題就來了。

          舉個例子:假設有5個機房,其中A機房故障,導致這個機房先前服務的用戶都跑去B機房。B機房不堪重負也崩潰了,A+B的用戶跑去機房C,連鎖反應會導致所有服務掛掉。

          防止雪崩效應需要在服務器架構,客戶端鏈接策略上有一些配合的解決方案。服務器需要有限流能力作為基礎,主要是限制總服務用戶數和短時間鏈接用戶數。

          在客戶端層面,發現服務斷開之后要有一個策略,防止大量用戶同一時間去鏈接某個服務器。

          通常有2種方案:

          • 1)退避:重連之間設置一個隨機的間隔;
          • 2)LBS:跟服務器申請重連的新的服務器IP,然后由LBS服務去降低短時間分配到同一個服務器的用戶量。

          這2種方案互不沖突,可以同時做。

          8、弱網問題

          8.1 弱網問題的原因

          鑒于如今IM在移動端的流行,弱網是很常態的問題。電梯、火車上、開車、地鐵等等場景,都會遇到明顯的弱網問題。

          那么為什么會出現弱網問題?

          要回答這個問題,那就需要從無線通信的原理上去尋找答案。

          因為無線通信的質量受制于很多方面的因素,比如:無線信號強弱變化快、信號干擾、通信基站分布不均、移動速度太快等等。要說清楚這個問題,那就真是三天三夜都講不完。

          有興趣的讀者,一定要仔細閱讀下面這幾篇文章,類似的跨學科科譜式文章并不多見:

          IM開發者的零基礎通信技術入門(十一):為什么WiFi信號差?一文即懂!

          IM開發者的零基礎通信技術入門(十二):上網卡頓?網絡掉線?一文即懂!

          IM開發者的零基礎通信技術入門(十三):為什么手機信號差?一文即懂!

          IM開發者的零基礎通信技術入門(十四):高鐵上無線上網有多難?一文即懂!

          弱網問題是移動端APP的必修課,下面這幾篇總結也值得借鑒:

          移動端IM開發者必讀(一):通俗易懂,理解移動網絡的“弱”和“慢”

          移動端IM開發者必讀(二):史上最全移動弱網絡優化方法總結

          現代移動端網絡短連接的優化手段總結:請求速度、弱網適應、安全保障

          百度APP移動端網絡深度優化實踐分享(三):移動端弱網優化篇

          8.2 IM針對弱網問題的處理

          對于IM來說,弱網問題并不是很復雜,核心是做好消息的重發、排序以及接收端的重試。

          為了解決好弱網引發的IM問題,通常可以通過以下手段改善:

          • 1)消息自動重發;
          • 2)離線消息接收;
          • 3)重發消息排序;
          • 4)離線指令處理。

          下面將逐一展開討論。

          8.3 消息自動重發

          坐地鐵的時候,經常遇到列車開起來以后,網絡斷開,發送消息失敗。

          這時候產品有2種表現形式:

          • a、直接告訴用戶發送失敗;
          • b、保持發送狀態,自動重試3-5次(3分鐘)以后告訴用戶發送失敗。

          顯然:自動重試失敗以后再告訴用戶發送失敗體驗要好很多。尤其是在網絡閃斷情況下,重試成功率很高,很可能用戶根本感知不到有發送失敗。

          從技術上:客戶端IMSDK要把每條消息的狀態監控起來。發送消息不能簡單的調用一下網絡發送請求,而是要有一個狀態機,管理幾個狀態:初始狀態,發送中,發送失敗,發送超時。對于失敗和超時的狀態,要啟用重試機制。

          這里還有一篇關于重試機制設計的討論帖子,有興趣可以看看:完全自已開發的IM該如何設計“失敗重試”機制?》。

          IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞》一文中關于消息超時與重傳機制的實現思路,也可以參考一下。

          8.4 離線消息接收

          現代IM是沒有“在線”這個狀態的,不需要給用戶這個信息。但是從技術的層面,用戶掉線了還是要正確的去感知的。

          感知方法有幾條:

          • a、信令長連接狀態:如果長時間沒收到到服務器的心跳反饋,說明掉線了;
          • b、網絡請求失敗次數:如果多次網絡請求失敗,說明”可能“掉線了;
          • c、設備網絡狀態檢測:直接檢測網卡狀態就好,一般Android/iOS/Windows/Mac都有相應系統API。

          正確檢測到網絡狀態以后,發現網絡從”斷開到恢復“的切換,要去主動拉取離線階段的消息,就可以做到弱網狀態不丟消息(從服務器的離線消息列表拉取)。

          上面文字中提到的網絡狀態的確定,涉及到IM里網絡連接檢查和保活機制問題,是IM里比較頭疼的問題。

          一不小心,又踩進了IM網絡保活這個坑,我就不在這里展開,有興趣一定要讀讀下面的文章:

          為何基于TCP協議的移動端IM仍然需要心跳保活機制?

          一文讀懂即時通訊應用中的網絡心跳包機制:作用、原理、實現思路等

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

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

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

          8.5 重發消息排序

          弱網邏輯的另一個坑是消息排序。

          假如有A、B、C  3條消息,A、C發送成功,B發送的時候遇到了網絡閃斷,B觸發自動重試。

          那么接收方的接收順序應該是 A B C還是A C B呢?我觀察過不同的IM產品,處理邏輯各不相同,這個大家有興趣可以去玩一下。

          這個解決方法是要依賴上一篇服務架構里提到的差值排序,同一個人發出的消息,排序按消息附帶的本地時間來排。不同人的消息,按照服務器時間排序。

          具體我這邊就不再得復,可以回頭看看本篇中的第四節“4、消息有序性問題”。

          8.6 離線指令處理

          部分指令操作的時候,網絡可能出現了問題,等網絡恢復以后,要自動同步給服務器。

          舉一個例子,大家可以試試手機設置為飛行模式,然后在微信里刪除一個聯系人,看看能不能刪除。然后重新打開網絡,看看這個數據會不會同步到服務器。

          類似的邏輯也適用于已讀同步等場景,離線狀態看過的信息,要正確的跟服務器同步。

          8.7 小結一下

          IM的弱網處理,其實相對還是比較簡單的,基本上自動重試+消息狀態就可以解決絕大部分的問題了。

          一些細節處理也并不復雜,主要原因是IM的消息量比較小,網絡恢復后能快速的恢復操作。

          視頻會議在弱網下的邏輯,就要復雜的多了。尤其是高丟包的弱網環境下,要盡力去保證音視頻的流暢性。

          9、本文小結

          《一套億級用戶的IM架構技術干貨》這期文章的上下兩篇就這么侃完了,上篇涉及到的IM架構問題倒還好,下篇一不小心又帶出了IM里的各種熱門問題“坑”,搞IM開發直是一言難盡。。。

          建議IM開發的入門朋友們,如果想要系統地學習移動端IM開發的話,應該去讀一讀我整理的那篇IM開發“從入門到放棄”的文章(哈哈哈),就是這篇《新手入門一篇就夠:從零開發移動端IM》。具體我就不再展開了,不然這篇幅又要剎不住車了。。。

          10、參考資料

          [1] 大規模并發IM服務架構設計

          [2] IM的弱網場景優化

          [3] 零基礎IM開發入門(三):什么是IM系統的可靠性?

          [4] IM消息送達保證機制實現(一):保證在線實時消息的可靠投遞

          [5] IM開發干貨分享:如何優雅的實現大量離線消息的可靠投遞

          [6] 即時通訊安全篇(二):探討組合加密算法在IM中的應用

          [7] 微信新一代通信安全解決方案:基于TLS1.3的MMTLS詳解

          附錄:更多IM開發文章匯總

          零基礎IM開發入門(一):什么是IM系統?

          零基礎IM開發入門(二):什么是IM系統的實時性?

          IM開發干貨分享:如何優雅的實現大量離線消息的可靠投遞

          IM開發干貨分享:有贊移動端IM的組件化SDK架構設計實踐

          IM開發寶典:史上最全,微信各種功能參數和邏輯規則資料匯總

          IM開發干貨分享:我是如何解決大量離線消息導致客戶端卡頓的

          從客戶端的角度來談談移動端IM的消息可靠性和送達機制

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

          移動端IM中大規模群消息的推送如何保證效率、實時性?

          移動端IM開發需要面對的技術問題

          開發IM是自己設計協議用字節流好還是字符流好?

          請問有人知道語音留言聊天的主流實現方式嗎?

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

          IM群聊消息如此復雜,如何保證不丟不重?

          談談移動端 IM 開發中登錄請求的優化

          移動端IM登錄時拉取數據如何作到省流量?

          通俗易懂:基于集群的移動端IM接入層負載均衡方案分享

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

          本文已同步發布于“即時通訊技術圈”公眾號。

          ▲ 本文在公眾號上的鏈接是:點此進入。同步發布鏈接是:http://www.52im.net/thread-3445-1-1.html



          作者:Jack Jiang (點擊作者姓名進入Github)
          出處:http://www.52im.net/space-uid-1.html
          交流:歡迎加入即時通訊開發交流群 215891622
          討論:http://www.52im.net/
          Jack Jiang同時是【原創Java Swing外觀工程BeautyEye】【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
          本博文 歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          Jack Jiang的 Mail: jb2011@163.com, 聯系QQ: 413980957, 微信: hellojackjiang
          主站蜘蛛池模板: 库尔勒市| 枣庄市| 米脂县| 双流县| 南投县| 承德县| 苗栗市| 眉山市| 龙海市| 共和县| 云霄县| 齐河县| 汤阴县| 紫金县| 喀什市| 赤城县| 永康市| 盐边县| 济源市| 夹江县| 岳阳市| 盐津县| 楚雄市| 南岸区| 福鼎市| 桐乡市| 花莲市| 黄梅县| 卢湾区| 喀喇| 临泉县| 曲阳县| 大竹县| 都昌县| 阳原县| 辽中县| 平江县| 长沙市| 策勒县| 榆社县| 诸暨市|