1. 程式人生 > >後臺訊息推送框架設計

後臺訊息推送框架設計

前言

  最開始自己公司的後臺推送系統只能是使用者線上時推送,推送訊息也不會儲存,若使用者離線,那麼這條推送訊息就再也無法獲取。更讓人頭疼的是:推送的內容和推送系統是耦合在一起的,這樣往往在改一處程式碼的同時,會出現意想不到的bug。著就更加堅定了自己要把推送程式碼重構的決心了。下面就是自己的整個設計過程和期間遇到的問題,寫出來和大家分享一下,望大家多多指教。

設計過程

  最開始設計的時候是把資料的儲存,修改和訊息的推送放到一個類中pushmanager中,各種訊息都糅合到一個message中,從後臺拉取訊息單獨提出來pullManager,如圖1:
  圖1
  
  上面的設計有如下弊端:
  1、Message來表示各種型別的訊息,而它又與資料庫表直接關係,這樣Message要把各種訊息型別的不同點都包含進來,這樣,新增一個訊息型別,就要在資料庫中新增欄位來表示它與其他型別不同點。不符合開閉原則,維護性極差。
  2、訊息儲存和推送的耦合性太強了。

  針對上面問題,對其做了進一步的優化(如下圖):
  1、用MessageTypeBase這個抽象類來將訊息的共同行為和屬性抽象出來,各種不同型別的訊息繼承這個抽象類,然後再針對不同型別的訊息新增不同的行為和屬性
  2、將訊息儲存和推送兩個分開,messageManager負責訊息的增刪改查(拉取訊息的行為也放入其中),它對外提供服務,PushManager負責訊息的推送(包括單推,群推,組推)
  3、MessageType中用靜態常量來標識不同型別的訊息對應的數字,MessagetypeContent不同訊息型別對應的內容模板和標題
  4、在資料庫表中新增extendInfo欄位(string型別),來儲存不同訊息的額外資訊,具體的訊息對其進行具體的處理(參考最後的UML圖t_message表,當初自己忘記修改下圖的表結構)
  圖2


  
  在依照上面設計來實現程式碼時,自己又發覺上面的那個設計方案仍有很多的弊端:
  1、messageManager不僅負責訊息的推送和拉取,還負責和資料庫進行互動,功能太過複雜
  2、MessageTypeBase不僅表示訊息的型別,還負責推送和拉取訊息的組裝,最讓人無法忍受的是,如果推送的訊息格式發生變化,就要改 MessageTypeBase的程式碼,RealnameMessage等具體的訊息型別也會隨之發生變化。

  於是,自己又重新對上面的系統進行拆分(如下圖):
  1、新增PushMessageType2這個列舉型別來標識訊息的推送型別(即單推,群推,組推等)
  2、將messageManager名字改為MessageManagerService
  3、新增MessageDao來專門負責與message資料庫來打交道,對應將messageManager中的DBHelper屬性改成MessageDao
  4、在PushManager中增加了三個屬性,clientCache用來快取推送時用到的client物件,剩下兩個用來標識Android和ios裝置,增加了5個方法,一個是獲取單例物件(之前的圖忘記畫了),一個是獲取client物件,一個是用於日誌列印(方便除錯時檢視資訊),剩下兩個是在組推時用到的兩個兩個方法。
  5、增加MessageManager來負責messageManagerServer與MessageBase之間的互動,用messageType這個屬性來標識 MessageBase子類的型別 (比如實名訊息,加好友訊息等) 。MessageManager中有個map,裡面維護著所有的MessageBase子類(比如實名訊息,加好友等各種訊息型別),messageManagerServer可以根據需要用getMessageBase()取出對應的訊息型別,值得注意的是,在用 getMessageBase ()這個方法是,必須先用setMessageType(int messageType,Message message)來設定 MessageBase 子類和訊息內容。相對應的將messageManager中的addMessageType(int type,MessageTypeBase message)方法和removeMessageType(int type)這兩個方法移到了MessageManager中。增加getMessageMap來獲取訊息處理後的message訊息內容(主要使用者拉取),和getPushMessage()來獲取推送訊息(主要用於推送)。相對應的增加pushManager屬性來設定推送訊息的內容,這樣在MessageManagerServer就可以根據需求靈活地傳送訊息內容
  這裡寫圖片描述


  
  在編寫程式碼時,自己又發覺上面的設計中的兩個弊端:
  1、channelid,tagname,pushType,deployStatus等推送環境引數放在MessageBase中,這樣在每次推送時,通過MessageManager取出MessageBase從而獲取推送環境引數(上面PushManager中的push方法的引數應該是 MessageManager 型別,自己當初忘記改了,大家可以參考下圖的PushManager中的推送方法引數);再者,將這些推送環境引數放在訊息型別中,將訊息內容和環境引數耦合在一起,不夠靈活,也不符合單一責任原則
  2、在MessageManager中,用一個map來維護各種訊息型別(在 MessageManager 的建構函式中,會new出各種訊息型別的物件,然後放入這個map中),用messageType來標識MessageManager使用的是哪個具體的訊息,這樣,有以下兩點壞處:(1)因為只使用map中的一種具體型別的訊息,這樣在這個map中,也就只有一個具體的訊息有資料,其他各種型別的訊息均為空物件,這些空物件與MessageManager中的map綁在一起,也不會被GC回收(因為map中有一個訊息型別在使用);(2)若增加一個訊息型別,還要改MessageManager的建構函式,違背了開閉原則
  
  依照上述原因,對其進行進一步的優化(如下圖):
  1、將推送環境引數從MessageBase中移到MessageManager中,MeeageBase只用來標識訊息
  2、在 MessageManager中只維護一個MessageBase,在業務層中將具體的訊息型別傳入。這樣,不管怎麼增加或者刪除具體的訊息型別,也只是在業務中增加或刪除而已,MessageManager中的程式碼不用修改
  這裡寫圖片描述
  至此,整個後臺訊息推送框架基本設計完成。從最初的設計到最終程式碼的實現,可謂一波三折,不僅要抽象業務,拆分,解耦,還要考慮異常,錯誤資訊的輸出等等各種問題,自己也從中學到了很多,也從中得到了很多的快樂。如大家有什麼想法,請多多指教。