成功遷移到自定義 XMPP 解決方案

已發表: 2019-04-08

我將向您介紹我們從第三方聊天遷移到為我們的客戶 Forward Health 定制的基於 XMPP 的消息傳遞解決方案所面臨的挑戰,Forward Health 是一個位於英國的消息傳遞醫療保健解決方案。 本文將介紹遷移的原因、我們的期望與實施的現實以及構建附加功能的挑戰。

我們開始的地方

我們的客戶 Forward Health 想要為英國的醫護人員構建一個移動通信應用程序,包括聊天功能。 作為一家初創公司,他們想快速展示他們的工作產品。 同時,消息傳遞必須可靠、穩健並且能夠安全地發送敏感的患者數據。 為了實現這一點,我們決定使用一種可用的第三方解決方案來實現聊天功能

聊天功能並不是一件小事,尤其是當它旨在支持醫療保健行業時。 隨著應用程序的發展,我們在庫方面遇到了更多的邊緣案例和一些第三方不願意處理的錯誤。 此外,Forward Health 希望添加第三方庫不支持的新功能。 下一步是切換到自定義解決方案。

那是我們開始使用MongooseIM的時候。 MIM 是基於成熟的 XMPP 協議的開源解決方案。 我們與外部公司 Erlang Solutions Limited 合作建立我們的後端並提供實施定制解決方案的支持。

MongooseIM 儀表板

起初,關於消息傳遞的一切似乎都不同。 以前,我們通過 SDK 及其 REST API 滿足了我們的所有需求。 現在,使用 MongooseIM,我們不得不花一些時間來了解 XMPP 的本質並實現我們自己的 SDK 。 事實證明,“最基本的”XMPP 服務器只在客戶端之間實時傳遞節(XML 消息)。 節可以是不同的類型,即正常的聊天消息、存在、請求和響應。 可以將各種各樣的模塊添加到服務器中,例如,存儲消息並讓客戶端查詢它們。

在客戶端(Android、iOS)有一些低級 SDK。 不幸的是,它們只是作為一個層來實現與 MongooseIM 及其一些稱為 XEP 的可插入模塊的通信(XMPP 擴展協議負責為每條消息發送推送通知等)。 消息處理、存儲和查詢消息的整個架構必須由我們的團隊實現。

拯救我們的是我們之前使用的第三方庫。 它有一個經過深思熟慮的 API,所以我們讓我們的解決方案以類似的方式工作。 我們將 XMPP 特定代碼分離到我們的內部 SDK 中,其接口對應於之前解決方案中的一個。 這導致遷移後我們的應用程序代碼只發生了一些變化。

在 MongooseIM 的實施過程中,我們多次對我們認為是標準的元素感到驚訝,但這些元素對我們來說是不可用的,即使是 XEP 也是如此。

實現基於 XMPP 的聊天的關鍵功能

時間戳

您可能會認為,就像我們所做的那樣,時間戳就像“我收到一條消息,我在 UI 上顯示它並帶有時間戳”一樣簡單。 不,沒那麼容易。 默認情況下,消息節沒有時間戳字段。 對於我們的團隊來說幸運的是,XMPP 是一個易於擴展的協議。 在後端,我們實現了一項自定義功能,為通過 MongooseIM 服務器的每條消息添加時間戳。 然後收件人會將時間戳附加到消息中。

為什麼發件人不能自己添加時間戳? 好吧,我們不知道他們是否在手機上設置了正確的時間。

為什麼沒有任何 XEP 呢? 可能因為 XMPP 是一個實時協議,所以理論上發送的每條消息都會立即收到。

編輯:正如 Florian Schmaus 指出的那樣:“實際上有一個,儘管它很容易被忽略,因為它的名稱令人困惑:XEP-0203:延遲交付。” 僅當消息的傳遞延遲時,它才會向消息添加時間戳。 否則,消息是剛剛發送的。

離線消息

當兩個用戶都登錄到應用程序時,他們可以實時互相發送消息。 但是,如果其中一個離線怎麼辦? 快速回答是:消息必須在後端緩衝。 離線消息功能處理這項工作,並在用戶重新登錄後將所有緩衝的節發送給用戶。

但隨後出現了幾個問題:

  • 這些消息應該緩衝多長時間?
  • 他們有多少?
  • 是否應該在重新登錄後重新發送? 但它會用消息淹沒客戶端,不是嗎?
  • 如果用戶只登錄,但沒有與新消息一起進入聊天,該怎麼辦。 他們都會消失嗎?
  • 如果用戶在多個設備上登錄怎麼辦?

很明顯,離線消息功能只能將消息發送到第一個重新在線的設備,然後這些消息對於所有其他設備都會丟失。 我們決定放棄此功能,並以不同的持久方式將消息存儲在 XMPP 後端。

消息歸檔管理 (MAM)

MAM 是消息的服務器上存儲。 當客戶端登錄時,他們可以向服務器查詢消息。 可以按頁查詢,也可以按日期查詢。 它很靈活——您甚至可以在具有特定 ID 的消息之前或之後查詢頁面,為來自確切對話的消息添加過濾器。

但這就是問題所在。 普通聊天消息包裝在 MAM 消息中存儲,這些消息具有自己的唯一 ID。 當用戶在流中收到聊天消息時,它不包含 MAM ID。 他們必須查詢 MAM 才能得到它。

從 MAM 檢索是一個網絡請求,這意味著它可能需要相對較長的時間。 當用戶進入聊天時,他們希望立即看到消息。 所以我們還需要一個本地數據庫

當用戶在流中收到消息(在線消息)時,我們將其保存到本地數據庫並顯示給用戶。 這樣,我們向用戶顯示實時快速到達的消息。

此外,每次他們進入聊天屏幕時,我們都會將所有消息下載到存儲在本地數據庫中的最新 MAM 消息中,然後將它們放入數據庫中,忽略重複項。

這就是我們處理存儲舊消息的方式。 此外,我們確信在數據庫中有一組完整的消息,用於來自 MAM 的第一條和最後一條消息之間的特定對話。

為了跟踪從 MAM 下載的消息,我們向對話實體添加了兩個屬性:

  1. 數據庫中最新 MAM 消息的 MAM id
  2. 數據庫中最早的 MAM 消息的 MAM id

在本地數據庫中處理破碎的 MAM 消息集將是非常有問題的。

此外,每個對話都有這兩個屬性允許我們在數據庫中存儲正常的聊天消息,同時忽略包裝器 - MAM 消息。 當用戶進入聊天時,我們可以顯示來自數據庫的最新消息,並在後台從 MAM 獲取丟失的消息。

收件箱

每個基於聊天的應用程序都需要一個帶有聊天列表的屏幕——您可以在其中查看姓名、最後一條消息和未讀消息計數。 一定有辦法解決的!

實際上,沒有……有一個叫做名冊的東西——它可以保存一個標記為“朋友”的用戶列表。 不幸的是,沒有最後一條消息,也沒有附加到它們的未讀消息計數。 當然,您可以從後端獲取所需的信息。 起初,我們想那樣做,但它工作起來很慢而且做起來很複雜。 就在那時,我們開始與Erlang Solutions合作開發收件箱功能,該功能也正在走向開源。

基於 XMPP 的聊天 - 收件箱屏幕
收件箱屏幕 - 所有聊天數據均由收件箱功能提供

當用戶連接到 XMPP 後端時,應用程序會獲取他們的收件箱,其中包含該用戶的所有對話——包括一對一和團隊聊天。 他們每個人都附有最後一條消息和未讀消息的計數。 應用程序將整個收件箱保存到本地數據庫。 當用戶在應用程序中並且有新消息到達時,我們會在本地更新收件箱狀態。 這樣,應用程序就不需要為每條新消息獲取收件箱。

概括

一些第三方聊天解決方案提供了高級別的抽象。 如果您想創建一個簡單的聊天應用程序,這是可以的。 通過在 Forward 應用程序中實現我們自己的基於 XMPP 的解決方案,我們能夠獲得更好的低級訪問,這使得解決問題變得更加容易。 當然,這需要一些時間,但現在我們知道我們可以提供任何自定義功能來幫助英國的醫生以 NHS 批准的安全和簡單的方式進行溝通。

消息傳遞是關於高性能的實時通信。 通過切換到 MIM,我們能夠優化解決方案的每個部分,以提高速度、可靠性並最終提高信任度。 目前,我們擁有完整的代碼,因此很容易追踪它們。 此外,我們正處於穩定階段之後,與消息傳遞相關的一些報告已大幅減少。 用戶對能夠信任該平台感到滿意。

設計和編寫我們自己的 SDK 是一項具有挑戰性的任務,我們喜歡它。 這與需要從服務器獲取數據並將其顯示在屏幕上的簡單應用程序不同。 在實現過程中,我們了解了我們之前使用的第三方庫 API 的許多設計選擇。 為什麼? 因為我們也遇到過同樣的問題。