最近上線了一個推送系統。推送系統做用是將短信,郵件,app push等消息觸達到用戶。目前功能上只實現了短信通道,而且隨着業務量的擴大,還須要並行擴展推送能力。 java
公司的對推送系統的應用場景有兩個,一個是主動發送,即在某一個時間點集中推送幾萬條短信。另外一個是用戶觸發,經過上游系統接收到用戶的行爲而發送一條短信給用戶。產品部門要求每條短信發送出去後,都要具體的執行報告:用戶是否收到,收到的時間,失敗的緣由等等。因此係統就須要跟蹤一次推出去的每條短信的發送狀態。 sql
目前系統接入了5個不一樣的短信網關。這個5個短信網關其中有4個是經過提供開發包(jar包形式),以socket的方式鏈接,一個是經過http協議訪問,因此對於短信狀態的處理也就有兩種不一樣的形式。 數據庫
第一種socket形式。這種開發包的處理方式通常是在第一次發送時與網關創建socket連接,雙方在連接中進行通訊。有的開發包裏會自動維護該連接的狀態,保證不會斷掉,一直能夠工做。而有的開發包則沒有該項功能,須要客戶端去維持這個連接。 緩存
若是系統中沒有短信上行的需求,能夠不作維持,在發送的時候判斷一下連接是否可用,若是斷掉,從新創建就是了。若是有上行的需求,恰巧鏈接斷掉,那就會收不到網關推送過來的上行信息了,這種狀況下必須維持。維持的方式通常都是定時去檢查連接是否可用,若是不可用,馬上創建恢復。咱們在接某個網關的時候,發現這個連接維持2個小時通常沒有問題,若是單純檢查連接,調用方法返回是通的,但實際上已經不可用了,判斷不是很準確,因此咱們乾脆再也不去檢查這個連接,而是每一個一小時發一條短信,用正式的短信發送去維護他,這樣比較省事。這裏面還有一個小插曲,每小時發送的短信,開始時接收方設置成了10086,後來被網關方發現,說每小時給10086這樣的號碼發一條不大好,後來又設置成了公司內部的一個測試號碼。 app
通常jar包中封裝好了短信發送接口,短信報告回調接口,短信上行接口。要想追蹤每條短信的發送狀態,必須對回調接口的數據進行處理。 socket
已其中一個短信網關的開發包爲例。它的整個一條短信發送過程分紅了3個處理部分: 測試
一,發送。發送時會獲得一個seqId,這個是在客戶端標示短信的id 優化
二,第一次回調,網關返回seqId和msgId 兩個值。此次回調標誌着短信已經進入網關的發送隊列裏,排隊等待發送,msgId是在網關端惟一標示該短信的id url
三,第二次回調,網關返回msgId和status,errMessage三個值,標誌着該條短信最終的狀態,用戶是否收到出錯緣由等。 spa
經過上面三個步驟能夠看到,必須每次都要進行關聯處理,才能保證最後的短信狀態。
步驟 | seqId |
msgId |
status |
一 | √ | |
|
二 | √ | √ | |
三 | |
√ | √ |
一,10w端短信由其餘系統插入到數據庫中,做爲一個發送task通知推送系統,每條短信都有一個自增加的smsId。
二,推送系統批量取出10w條短信,調用短信網關,每條短信都獲得一個seqId。這樣每條短信都須要根據smsId,更新seqId
三,第一次回調,每條短信要根據seqId,更新msgId
三,第二次回調,每條短信要根據msgId,更新最終的狀態。
這樣算起來10w條短信,最終發送完畢要更新30w次數據,並且這其中有兩個要注意的是:1, 三個步驟必須按照順序執行,不然where語句中須要的條件,數據庫里根本尚未插入。2, 10w條瞬間發送,大量的寫和回調在短期內就要操做,要保證不會丟失。
針對這個狀況咱們採用隊列的方式來處理,利用RabbitMQ來緩存全部的數據庫操做。RabbitMQ便可以緩存大量的對象不會丟失,並且先進先出,保證了這三步執行的順序。隊列中緩存的對象封裝成SmsDto這樣一個類,除了必要的字段後,另外增長了一個sqlAction字段,在對象從隊列中pop出來後,判斷是進行第幾步的數據庫操做。
public static final int UPDATE_SEQID_STATE_BY_SMSID = 100; public static final int UPDATE_MSGID_BY_SEQID = 101; public static final int UPDATE_STATE_BY_MSGID = 102;這樣在發送快速執行的時候,不用擔憂數據庫短期內的大量寫,能夠經過隊列pop的速度來控制寫的速度。
其中系統優化的一個地方是隊列pop寫,開始的時候是每pop一個smsDto,根據sqlAction就執行一次數據庫的操做,這樣發現寫的速度太慢,產品要求客戶能夠實時在網頁上看到發送的統計結果,而咱們的發送回調常常要寫2,3個小時才能從隊列裏消費完。針對這個狀況,就是要批量寫數據庫,然而又必須嚴格按照順序來執行,因此就作了個3個List,根據sqlAction分別將smsDto放入這個三個不一樣的list裏,在一分鐘的間隔裏,按照順序來批量保存在3個List。這樣保存的效率大大提升,基本上每條短信均可以實時從網頁上看到發送狀態。
/** * 批量處理數據庫操做 * 必須按照順序執行 * */ private void batchSave(){ if (!step1List.isEmpty()){ smsService.save(step1List); } if (!step2List.isEmpty()){ smsService.save(step2List); } if (!step3List.isEmpty()){ smsService.save(step3List); } step1List.clear(); step2List.clear(); step3List.clear(); }以上是socket方式發送短信的處理方式。http協議的方式通常是經過在網關設置一個本身系統的回調地址來接收發送報告和短信上行的。這就須要在本身系統實現兩個接口來接收返回的信息。
http形式通常在url上能夠批多個手機號來發送,相似於http://ip:port/send?content=abc&mobile=m1,m2,m3..咱們使用的網關會在這個請求後返回一個惟一的碼,相似於上述的msgId,惟一標示這一批短信的一個id。
在本身系統實現的短信狀態回調接口相似於http://ip:port/report?msgId,mobile1,status,time;msgId,mobile2,status,time...這樣就能夠根據msgId和mobile來惟一肯定數據庫中的一條短信記錄,來更新發送狀態了。
短信網關的調用已經屬於古老的成熟的技術了,大致狀況也基本相同,一個成熟的網關均可以獲得每條短信的發送結果,這些都從他提供的開發包,文檔裏來肯定具體的實現方式。不一樣的可能就是各自在對大量狀態更新方面的處理方式,這就須要結合各自的業務須要來實現了。