1. 程式人生 > >如何構建一套高可用的 APP 訊息推送平臺

如何構建一套高可用的 APP 訊息推送平臺

轉載自  如何構建一套高可用的 APP 訊息推送平臺

訊息推送作為移動 APP 運營中的一項關鍵技術,已經被越來越廣泛的運用。本文追溯了推送技術的發展歷史,剖析了其核心原理,並對推送服務的關鍵技術進行深入剖析,圍繞訊息推送時產生的服務不穩定性,訊息丟失、延遲,接入複雜性,統計缺失等問題,提供了一整套平臺級的高可用訊息推送解決方案。實踐中,藉助於該平臺,不僅能提能顯著提高訊息到達率,還能提高研發效率,並道出了移動開發基礎設施的平臺化架構思路。

推送基礎

移動網際網路蓬勃發展的今天,大部分手機 APP 都提供了訊息推送功能,如新聞客戶端的熱點新聞推薦,IM 工具的聊天訊息提醒,電商產品促銷資訊,企業應用的通知和審批流程等等。推送對於提高產品活躍度、提高功能模組使用率、提升使用者粘性、提升使用者留存率起到了重要作用,作為 APP 運營中一個關鍵的免費渠道,對訊息推送的合理運用能有效促進目標的實現。

推送最早誕生於 Email 中,用於提醒新的訊息,而移動網際網路時代則更多的運用在了移動客戶端程式。要獲取伺服器的資料,通常有兩種方式:第一種是客戶端 PULL(拉)方式,即每隔一段時間去伺服器獲取是否有資料;第二種是服務端 PUSH(推)方式,伺服器在有資料的時候主動發給客戶端。

很顯然,PULL 方案優點是簡單但是實時性較差,我們也可以通過提高查詢頻率來提高實時性,但這又會造電量、流量的消耗過高,反之 PUSH 方案基於 TCP 長連線方式實現,訊息實時性好,但是由於要保持 APP 客戶端和服務端的長連線心跳,也會帶來額外的電量和流量消耗。因此在整體架構設計中需要折中平衡,目前主流的推送實現方式都是基於 PUSH 的方案。

 

移動推送的三種實現方式

目前移動推送技術實現方式主要有以下三種:

輪詢方式(PULL)

客戶端和伺服器定期的建立連線,通過訊息佇列等方式來查詢是否有新的訊息,需要控制連線和查詢的頻率,頻率不能過慢或過快,過慢會導致部分訊息更新不及時,過快會消耗更多的資源(流量、電量等),對使用者體驗有較大傷害。

簡訊推送方式(SMS PUSH)

通過簡訊傳送推送訊息,並在客戶端植入簡訊攔截模組(主要針對 Android 平臺),可以實現對簡訊進行攔截並提取其中的內容轉發給 App 應用處理,這個方案藉助於運營商的短訊息,能夠保證最好的實時性和到達率,但此方案對於成本要求較高,開發者需要為每一條 SMS 支付費用。

長連線方式(PUSH)

移動 Push 推送基於 TCP 長連線實現, 客戶端主動和伺服器建立 TCP 長連線之後, 客戶端定期向伺服器傳送心跳包用於保持連線, 有訊息的時候, 伺服器直接通過這個已經建立好的 TCP 連線通知客戶端。儘管長連線也會造成一定的開銷,對於輪詢和 SMS 方案的硬傷來說,目前已經是最優的方式,而且通過良好的設計,可以將損耗降至最低。不過,隨著客戶端數量和訊息併發量的上升,對於訊息伺服器的效能和穩定性要求提出了非常大的考驗。因此,就難度而言,此方式代價最高。

 

推送解決方案

基於 TCP 長連線的方式是主流的推送方式,基於該推送方式逐步發展出系統級、應用級一系列的推送解決方案。

系統級方案

 iOS 平臺(APNs)

iOS 在系統層面與蘋果 APNs(Apple Push Notification service)伺服器建立連線,應用通過觀察者模式向 ioS 系統註冊關注的訊息,系統收到 APNs Server 訊息後轉發到相應的應用程式,整個過程很清晰,並且所有 APP 都共用同一個系統級的連線,減少了系統開銷,雖然 APNs 能無障礙的訪問,但實際使用過程中,發現延時和丟訊息的情況偶有發生。

 Android 平臺(C2DM)

Android 的 C2DM(Android Cloud to Device Messaging)採取與 iOS 類似的機制,都是由系統層面來支援訊息推送,但是由於 Google 的服務在國內不能穩定的訪問,此方案對於中國使用者來說基本是無法使用的。

除了 Google 官方提供的方案,中國眾多的手機廠商在其定製的系統中也內建了推送功能,如小米、華為等。

 

應用級方案

 1. 第三方推送服務

鑑於 Android 平臺 C2DM 推送的不可用性,國內湧現出大量的第三方推送服務提供商,採用第三方推送服務的系統流程如下圖:

 

圖 1:訊息推送流程

目前應用最為廣泛的第三方推送服務提供商包括個推、極光、友盟、小米、華為、BAT 等,絕大部分 APP 都會優先考慮採用第三方推送服務。

 2. 自建推送服務

第三方服務在開發成本和訊息到達率上表現都不錯,但所有資訊會經過第三方伺服器,對於資訊敏感類 APP 而言,有必要考慮自建一套訊息推送服務,能最大化保證安全,但對於自建推送服務,如果從零開始來做需要解決幾個難點:

第一,移動推送伺服器對 App 客戶端海量長連線的維護管理。第二,App 客戶端如何保證 Push Service 常駐,對於 Android 我們可以通過發現 push service 不存在可以定時拉起的方式。第三,通訊協議的制定,我們可以採用開源的 XMPP 方式實現,也可以自定義協議,不管哪種方式我們都要保證訊息傳送的到達率的準確性。第四,在移動網際網路網路環境下,經常出現弱網環境,特別是 2G、3G 等網路不穩定的情況下,如果保證訊息在弱網環境下不重、不丟也是一個挑戰。

存在問題

無論是第三方推送服務,還是自建推送服務,在實際的使用過程中,發現都存在以下問題:

  • 應用服務端與推送服務強耦合。當推送服務不可用時,造成整個業務系統無法推送,甚無法正常工作。

  • 缺乏 ACK 機制。推送的過程是非同步的,從應用服務端傳送到推送服務時,可以得知傳送是否成功,但是從第三方推送服務下發到 APP 時,無法得知客戶端是否接收到。iOS 平臺中,從推送服務傳送到蘋果 APNs 服務時,同樣無法確定 APNs 是否收到。同時,第三方推送服務通常使用共享的推送通道,受其他推送方的影響,可能造成訊息的延遲和丟失。

  • 服務會被殺死。尤其在 Android 平臺上,後臺推送 service 會被各種主動或者被動原因 kill 掉,導致訊息丟失。

  • 缺乏訊息的持久化。對於推送服務而言,訊息推送是來一條推一條,無法追溯歷史訊息和訊息狀態。

  • 缺乏重傳機制。整個推送過程涉及多個環節,當其中某個環節出現問題,造成客戶端接收不到推送的訊息時,就導致訊息丟失,再無法接收到。

  • 客戶端接入邏輯複雜。每接入一個新的 APP,都要進行重複的接入工作,接入邏輯完全一致,程式碼無法複用,需要在不同專案中拷貝。

  • 客戶端與推送服務的 SDK 強耦合。客戶端使用推送服務的介面,而各推送服務提供的介面不統一,如果需要替換推送服務,那麼接入部分程式碼需完全重寫。

  • 缺乏資料監控和統計。每個應用每天推送了多少訊息,成功到達 app 多少,失敗多少,目前均沒有統計。

解決之道

為了解決以上問題,我們考慮基於第三方訊息推送服務構建一套移動訊息推送中介軟體平臺,該訊息平臺採用了低耦合的分層架構設計(如圖 2 所示),分為三層:接入層、傳輸層和應用層。其中接入層是業務方呼叫的入口,我們採用非同步訊息佇列的方式提供了較高的業務系統傳送訊息的速度,並且具備了訊息緩衝功能,即使高峰期的海量訊息推送對整個平臺衝擊較少,保護了推送系統;

傳輸層會從接入層接收訊息並進行解析,對推送訊息進行合法性檢查校驗,如果訊息不合法直接丟棄,同時將合法的訊息進行協議轉換併發送到對應的第三方推送平臺;應用層主要是提供統一的 SDK 供業務使用,封裝適配第三方推送平臺的 SDK 介面到統一的介面 SDK 中,這樣業務 APP 使用方只關注統一封裝的 SDK 即可實現業務訊息的操作,而不需要考慮各種濾重、校驗等通用操作。主要功能包括:

  • 遮蔽推送介面,實現業務與推送服務解耦,提供一套通用的客戶端 SDK,簡化客戶端接入。

  • 實現多點接入,可同時接入多套推送服務,根據歷史推送成功率動態選擇最優推送路徑,當一條路徑失效可選擇備用路徑進行推送,保證訊息推送萬無一失。

  • 引入訊息持久化機制,方便追溯和統計。

  • 引入訊息的 ACK 機制和重傳機制,提高訊息的到達率。

  • 實現資料監控和統計機制,提供相關資料的統計分析,和報警預警功能。

  • 提供 web 管理後臺,便於進行 APP 設定、推送設定、檢視資料報表,提高系統維護的工作效率。

整個系統設計由三部分組成:移動推送平臺、客戶端 SDK、應用管理介面(第三方推送服務和自建推送服務統稱為推送服務)。

 

圖 2:系統架構

移動推送平臺提供統一的服務,對於應用層遮蔽推送服務介面,且實現推送服務可動態輪替。推送平臺將接收到的訊息持久化到資料庫中,方便進行訊息推送失敗後的重發,以及後續資料的統計分析。

客戶端 SDK 對 App 提供統一的使用介面,遮蔽推送服務 SDK 使用細節,且實現多種推送 SDK 可替換,隱藏 SDK 複雜的接入過程,方便使用。

應用管理系統面向 App 開發人員,實現應用申請,推送服務配置,訊息查詢與管理,資料統計與分析。

主要流程

訊息推送涉及的主要模組是訊息推送平臺和客戶端 SDK,主要流程如下圖所示:

 

圖 3:訊息推送中介軟體核心流程

正常情況下,訊息推送過程如下:

  • 系統接收到業務方的推送請求後,首先進行許可權的驗證,這包括應用 appKey 的驗證、介面引數的驗證、黑名單驗證等。

  • 驗證不通過,返回錯誤資訊;驗證通過後,為此條訊息分配一個唯一 id(uuid),將訊息內容持久化到資料庫中,此時訊息的狀態為待發送。

  • 訊息進入推送佇列中,將之後推送介面請求的響應返回給業務方。

  • 推送佇列的消費者從佇列中取出待發送的訊息,標記該條訊息的狀態為傳送中,然後呼叫第三方推送服務介面進行傳送。

  • 如果呼叫成功,那麼標記該訊息的狀態為傳送成功客戶端未收到。

  • 客戶端 SDK 在收到推送後,回撥服務端介面,傳送收到推送的回執;服務端收到客戶端回執後,標記訊息狀態為傳送成功客戶端已收到。

對於推送過程中可能出現的異常情況,總結如下:

  • 在呼叫第三方推送服務介面時,可能出現呼叫失敗的情況;此時需要標記訊息的狀態為傳送失敗,留待重發。

  • 在呼叫第三方推送服務介面成功後、第三方推送服務在下發至客戶端的過程中,可能由於某種原因,造成客戶端無法收到訊息;此時訊息的狀態為傳送成功客戶端未收到,對於這種狀態,需要重發。

  • 客戶端在收到推送的訊息後、向服務端傳送 ACK 回執時,可能由於網路環境的問題,造成服務端沒有收到客戶端傳送的回執,此時訊息的狀態為傳送成功客戶端未收到,對於這種狀態,需要重發。

  • 訊息在重發 N 次(N 次可配置)、仍然沒有進入傳送成功客戶端已收到的狀態,那麼將不再進行自動重發;管理介面將提供手動重發訊息的操作入口,如有需要,可以手動再進行重發。監控平臺對於一直重複不成功的訊息會報警通知操作人員,這樣操作人員可以及時通過手動方式處理。

根據訊息傳送流程,可以得到訊息在生命週期中狀態的變遷如下圖:

 

圖 4:訊息狀態機

重發機制

訊息重發主要存在三種場景:系統啟動時,查詢所有的傳送失敗或傳送成功未收到客戶端回執的訊息,載入到推送佇列重發;系統執行時,後臺執行緒定時查詢需要重發的訊息,進入推送佇列;手動觸發時,直接將訊息加入推送佇列。

由於訊息推送中介軟體服務通常要求高可用,為分散式部署,訊息重發必須保證在單一節點執行,且保證只發送一次。需採用分散式鎖的方式,保證重發只發一次,主流實現方式有三種:

  1. ZooKeeper:通過競爭建立臨時節點的方式獲取鎖。

  2. Redis:Redlock 是 Redis 作者的提出了一種分散式鎖的演算法,基於 Redis 實現,該演算法實現了一種更安全、可靠的分散式鎖管理。

  3. 資料庫:如使用 MySQL 的 GET_LOCK 函式

對於每種鎖機制的特點本文不詳細介紹,根據實際應用需要任選一種即可。

由於 iOS 平臺和 Android 平臺的差異,訊息重發需要考慮平臺差異性。

使用第三方推送時,如果 iOS 應用在前臺執行,那麼將通過第三方推送維護的長連線,以透傳的方式直接下發到 APP,稱為應用內訊息;而當 APP 在後臺時,則第三方推送將訊息推送到 APNs,由 APNs 推送到 APP,稱為 APNs 通知。當通過 APNs 推送時,手機在收到訊息後將在頂部的通知欄出現相關推送內容,這一行為是系統級別的,APP 無法控制。可能會出現這一問題:當 APP 在後臺或者手機鎖屏的情況下,如果服務端重發了訊息,手機的通知欄將出現多條通知。

因此,考慮當 APP 在後臺時,針對 iOS 平臺的訊息不再進行重發;只有當 APP 進入前臺,才重新進行重發。APP 的活動狀態通過第三方推送服務的 api 可以獲取到。

Android 平臺不存在該問題。

由於訊息重發可能會造成客戶端收到重複訊息,需要在客戶端進行訊息去重。服務端為每一條訊息分配了一個唯一 id,重發時唯一 id 不變。客戶端需要儲存收到的每一條訊息,在接收到新訊息時首先根據唯一 id 判斷是否已經收到了這條訊息,如收到則不響應。客戶端儲存訊息可以採用 sqlite 資料庫。

安全和控制

客戶端 SDK 與服務端的通訊過程使用 appKey 和 appSecret 進行許可權控制。appKey 是服務端為每個 app 分配的唯一標識,appSecret 是服務端為每個 app 分配的祕鑰。

客戶端 SDK 在請求服務端 HTTP 介面時,會將 appKey+appSecret 做一次簽名,將簽名值作為簽名 sign 引數,與其他請求引數(業務引數 +appKey)一同傳到服務端;服務端拿到請求引數後,也先用 appKey+appSecret 做一次簽名,比較和客戶端傳來的 sign 引數是否一致,從而完成許可權驗證過程。為了能夠實現靈活控制推送與否,可實現黑名單管理的功能。處於黑名單內的 app 客戶端不再進行訊息的推送。黑名單控制的粒度到賬號級別,也可以根據實際業務需要進行分組管理。

在某些業務場景中,需要對訊息進行過濾,分析,做出相應的處理甚至預警,藉助於訊息推送平臺,都能方便的實現。

SDK 設計

客戶端 SDK 是基於推送服務的 SDK 封裝實現,對外提供統一的使用介面。SDK 的使用者不再關注具體使用了哪些第三方推送、推送服務的接入細節。實現與推送服務的充分解耦,降低開發和使用成本。

由於 iOS 和 Android 平臺的差異性,在客戶端 SDK 的封裝上存在差異,下面分別介紹兩個平臺的 SDK 封裝方式。

iOS 平臺

SDK 提供啟動和停止的方法;同時定義一個 protocol,包含 SDK 提供的介面。SDK 在收到訊息或出現錯誤時將會回撥 protocol 中的介面。

 

由於推送的接入涉及 AppDelegate 的生命週期方法,為避免 SDK 使用者關注這些繁瑣的細節,SDK 使用 Aspects 的方式,將推送時相應的處理函式 hook 到 AppDelegate 的生命週期方法上。

 

Android 平臺

在 Android 中使用 Receiver 元件來接收收到的訊息。一個基本的配置如下所示:

 

流程如下:當推送服務的 SDK 在接收到推送過來的訊息後,將傳送廣播,這個廣播的用 intent-filter 標識,當應用中的 Receiver 程式碼註冊了這個 intent-filter,就可以接收到廣播,並進行後續處理。

系統管理

 

圖 5:後臺管理示意圖

訊息後臺管理系統提供應用申請、應用服務配置、推送服務配置、訊息查詢與管理等功能。

1、應用申請

填寫應用名、應用描述等資訊後,生成該應用唯一的 appKey 和 appSecret。

2、應用服務配置

為應用選擇要使用的移動端通用服務,可供選擇的有推送、反饋、版本釋出。

3、推送服務配置

為應用配置推送服務,可供選擇個推、極光等;以及推送時使用的優先順序順序。

4、訊息查詢與管理

檢視應用所發出的訊息,包括訊息所屬應用、所屬賬號、訊息的狀態、最終傳送成功的第三方渠道、訊息的來源、傳送者 ip 等資訊

5、資料統計

通過分析 message 表中的各訊息的狀態,可統計各應用訊息的傳送成功率和到達率,以及哪個第三方推送的更優,方便選擇。同時,提供每日、每週、每月推送訊息量的統計,並提供統計圖表。

高可用、高效能、高穩定性

訊息推送平臺通過無狀態設計、統一儲存、冗餘部署方式保證了高可用,對應的狀態資料統一儲存到 MySQL、Redis 中保證各個無狀態例項共享資料。

對於訊息的接收處理我們通過純非同步、動態多執行緒的方式提供了推送平臺的高效能。同時對於非同步接收的訊息我們通過 log append 的方式保證訊息先落地然後再進行處理,進一步確保系統在異常過程中我們可以隨時恢復訊息,保證不丟失。

通過質量保障、全方位多維度監控體系(基礎監控、錯誤日誌監控、傳送資料波動監控、程序監控等監控指標)保障系統在出現問題時實現秒級報警、及時處理保證了訊息推送平臺的高穩定性。

寫在最後

本文介紹了一種基於第三方或自建推送服務、但又不強依賴特定推送服務的通用移動訊息推送中介軟體平臺,可以實現安全、穩定、可靠的訊息推送功能,並提供完善的資料統計,在實際應用中,可以結合郵件、簡訊、網站訊息、使用者留言等打造成更加通用的企業訊息平臺。