別寫秒殺系統了,我告訴你消息管理平台實現原理吧
- 2020 年 9 月 22 日
- 筆記
前言
「
微信搜【Java3y】關注這個有夢想的男人,點贊關注是對我最大的支援!
文本已收錄至我的GitHub://github.com/ZhongFuCheng3y/3y,有300多篇原創文章,最近在連載面試和項目系列!
我,三歪,最近要開始寫項目系列文章。我給這個系列取了一個名字,叫做《揭秘》
沒錯,我又給自己挖了個坑。
為什麼想寫項目相關的文章呢?原因有以下:
-
當我還沒正式開始工作時,我經常會想:」網上的影片項目我是看過了,但真正的商業項目究竟長什麼樣?會不會很難?「我是挺想知道真正的商業項目跟自己練習的項目區別在哪。我估摸還沒工作的同學應該也有跟我類似的思考吧? -
變相推動自己持續輸出,在這個過程中學習和成長。關注我可能有小白,也可能有跟我做同一領域的大佬。我把我所了解的寫下來:可能我這邊的實現方案被大佬們唾棄,交流和學習後,改善了我系統的實現方案。也有可能給正準備踏進該領域的同學提供一些參考價值。豈不美哉?
這個系列就以「消息管理平台」來打個樣吧,這是我維護近一年的系統了。這篇文章可以帶你全面認識「消息管理平台」是怎麼設計和實現的,有興趣的同學歡迎在評論區下留言和交流。
這篇文章可能稍微會有些許長,我是打算一篇就把該系統給講清楚。「消息管理平台」原理並不難,沒有很多專業名詞,實現起來也不會複雜,你要是覺得學到了,歡迎給我點個贊👍
簡單認識《消息管理平台》
「消息管理平台」可能在不同的公司會有不同的叫法,有的時候我會叫它「推送系統」,有的時候我會叫它「消息管理平台」,也有的同事叫它「觸達平台」,甚至浮誇點我也可以叫它「消息中台」
但是不管怎麼樣,它的功能就是給用戶發消息。在公司里它是怎麼樣的定位?只要以官方名義發送的消息,都走消息管理平台。
一般你註冊一個APP/網站
,你可以收到該APP/網站
給你發什麼消息呢?一般就以下吧?
-
站內信(IM)消息:其實就是APP內聊天的消息 -
通知欄(PUSH)消息:系統彈窗消息 -
郵件(Email)消息 -
簡訊(Sms)消息 -
微信服務號消息 -
微信小程式(服務通知)消息






好了,我相信你已經知道這個系統是用來幹嘛的了。那為什麼要有這個系統呢?
為什麼要有消息管理平台?
可以說,只要是做APP的公司幾乎都會有消息管理平台。
我們很多時候都會想給用戶發消息:
-
有可能是用戶想要這樣的功能(預約活動提醒通知) -
也有可能是我們想通過發消息來「喚醒」/「告知」等操作,告訴用戶我們還在(大爺來玩啊)
那麼問題來了,發消息困難嗎?發消息複雜嗎?
顯然,發消息非常簡單,一點兒也不複雜。
發簡訊無非就是調用第三方簡訊的API、發郵件無非就是調用郵件的API、發微信類的消息(手Q/小程式/微信服務號)無非就是調用微信的API、發通知欄消息(Push)無非就是調APNS/手機廠商的API、發IM消息也可以使用雲服務,調雲服務的API…
可能很多人的項目都是這麼乾的,無非發條消息,自己實現也不是不可以。
但這樣會帶來的問題就是在一個公司內部,會有很多個項目都會有「發送消息」的程式碼實現。假設發消息出了問題,還得去自己解決。
首先是系統不好維護,其次是沒必要。我一個搞廣告的,雖然我要發消息,憑什麼要我自己去實現?
我們在寫程式碼時,可能會把公用的程式碼抽成方法,供當前的項目重複調用。如果該公用的程式碼被多個項目使用,可能我們又會抽成組件包,供多個項目使用。只要該公用的程式碼被足夠多的人去用,那它就很有可能從組件上升為一個平台(系統)級的東西。
如何實現消息管理平台?
回到消息管理平台的本質,它就是一個可以發消息的系統。那怎麼設計和實現呢?我們從介面說起吧。
介面設計
消息管理平台是一個提供消息發送服務的平台,如果讓我去實現,我的想法可能是把每種類型的消息都寫一個介面,然後把這些介面對外暴露。
所以,可能會有以下的介面:
/**
* content:發送的文案
* receiver:接收者
*/
sendSms(String content,String receiver);
sendIm(String content,String receiver);
sendPush(String content,String receiver);
sendEmail(String content,String receiver);
sendTencent(String content,String receiver);
//....
這樣實現好像也不是不可以,反正每個介面都挺清晰的,要發什麼類型的消息,你調用哪個介面就好了。
假設我們定義了如上的介面,現在我們要發消息了,我們會有以下的場景:
-
文案:「你好,我是三歪」,接收人:「woshisanwai」 (一次只發給一個人) -
文案:「你好,我是三歪」,接收人:「woshisanwai,java3y,javayyy」(相同的文案發給多個人)
假如你是新手,你可能會想:這簡單,我每種類型分開兩個介面,分別是單發和批量介面。
sendSingleSms();
sendBatchSms();
//...
上面這樣設計有必要嗎?其實沒啥必要。我將接收人定義為一個Array
不就得了?Array
的size==1
,那我就把該文案發給這個人,Array
的size>1
,那我就把這個文案發給Array
裡邊的所有人。
所以我們的介面還是只有一個:
/**
* content:發送的文案
* receiver:接收者(可多個,可單個)
*/
sendSms(String content,Set<String> receiver);
其實在我們這也不是定義Array
,我的介面receiver
仍然是String
,如果有多個用,
號分隔就可以了。
/**
* content:發送的文案
* receiver:接收者(可多個,可單個),多個用逗號分隔開
*/
sendSms(String content,String receiver);
現在還有個場景,不同的文案發給不同的人怎麼辦?有的人就說,這不已經實現了嗎?直接調用上面的介面就完事了啊。你又不是不能重複調用,比如說:
-
文案:「你好,我是Java3y」,接收人:「woshisanwai」 -
文案:「你好,我是三歪」,接收人:「3y」 -
文案:「你好,woshisanwai」,接收人:「三歪」 -
…..
確實如此,本來就可以這樣做的。但不夠好
舉個真實的場景:現在有一個主播開播了,得發送一條消息告訴訂閱該主播的人趕緊去看。為了提高該條通知的效果 ,在文案上我們是這樣設計的:{用戶昵稱},你訂閱的主播三歪已經開播了,趕緊去看吧!
這種消息我們肯定是要求實時性的(假設推送消息的速度太慢了,等到用戶收到消息了,主播都下播了,那用戶不得錘死你?)
「
畫外音:顯然這種情況屬於不同的文案發給不同的人
這種消息在業務層是怎麼做的呢?可能是掃DB表,遍歷出訂閱該主播的粉絲,然後給他們推送消息。
那現在我們只能每掃出一個訂閱該主播的粉絲,就得調用send()
介面發送消息。如果該主播有500W
的粉絲,那就得調用500W
次send
介面,這不是很可怕?這調用次數,這網路開銷…
於是乎,我們得提供一個「批量」介面,可以讓調用方一次傳入不同文案所攜帶不同的人。那怎麼做呢?也很簡單,實際上就是上面介面再封裝一層,讓調用方能「批量」傳進來就好了。所以程式碼可以是這樣的:
/**
* 一次傳入多個(文案以及發送者)的「組」進來
* List<SendParam>
* SendParam 裡邊 定義了 content 和receiver
*/
sendBatchSms(List<SendParam> sendParam);
現在介面的「雛形」已經出現了,到這裡我們實現了消息管理平台最基本的功能:發消息
我們先不管內部的實現是如何,假設我們已經適配好調通好對應的API了,現在我們的介面在發消息層面上已經有充分必要的條件了:只要你傳入接收者和發送內容
,我就可以給你發消息。
但我們對外稱可是一個平台啊,怎麼能搞得像是只封裝了幾個方法似的,平台就該有平台的樣子。
我舉個日常最最最基本的功能:有人調用了我的介面發了條簡訊,這條簡訊的文案是一條內容為驗證碼類型,他問我這條簡訊到底下發到用戶手上了沒有。

如果接入過簡訊的同學就會知道:發送簡訊到用戶收到是一個非同步的過程
-
調用簡訊提供商的API,假設你的入參沒有問題,它會告訴你「調用」成功。你想真正地知道此條內容到底有沒有下發到用戶手上,你有兩種辦法:一、提供一個介面給簡訊服務商調用,等真正處理完了,簡訊服務商會調用你的介面,告訴你最終的結果是什麼。二、你去輪詢簡訊服務商的介面,獲取最終的結果。

回到問題上,他想要他調用我的介面有沒有把簡訊發送成功,那我只要問他拿到手機號和文案,然後有以下步驟:
-
判斷該手機號和文案在下發時是否正常(有沒有真正調用下發簡訊的介面) -
假設調用簡訊介面下發成功,那看下返回的回執(下髮結果)是否正常
那目前我們在現有的介面,還是很完美地支援上面的問題的,對吧?只要我們記錄了下發的結果和回執的資訊,我們就可以告訴他所提供的手機號和文案究竟有沒有下發到用戶手上。
那今天他又過來問了:今天有很多人來回饋收不到驗證碼簡訊(不是全部人收不到,是大部分人),我想了解一下今天驗證碼簡訊下發的成功率是多少。
此時的我,只能去匹配(like %%
)他的文案調用我的介面下發了多少人,調用簡訊服務商的API下發成功多少人,收到的成功回執(結果)有多少人。
通過匹配文案的方式最終也是可以告訴他結果的,但是這種是很傻X的做法。歸根到底還是因為系統提供的服務還是太薄弱了。
那怎麼解決上面所講的問題呢?其實也很簡單,匹配文案很傻X,那我給他這一批驗證碼的簡訊取個唯一的Id那不就可以了嗎?
像我們去接入簡訊服務商一樣,我們需要去新建一個簡訊模板,這個模板代表了你要發送的內容,新建模板後會給你個模板Id,你下發的時候指定這個模板Id就好了。

那我們的平台也可以這樣玩啊,你想發消息對吧?可以,先來我的平台新建一個」模板「,到時候把模板Id發給我就行。
於是,我們就完美地解決上面所提到的問題了。
我們現在再來討論一下有沒有必要不同的消息類型(簡訊、郵件、IM等)需要分開不同的的介面,其實是沒必要的了。因為只要抽象了」模板「這個概念,消息類型自然我們就可以在模板上固化掉,只要傳了模板Id,我就知道你發的是什麼類型消息。
這樣一來,我們最終會有兩個介面:批量與單個發送介面。
/**
* 發送消息介面
* @author java3y
*/
public interface SendService {
/**
* 相同文案,發給0~N 人
* @param sendParam
*/
void send(SendParam sendParam);
/**
* 不同文案,發給不同人,一次可接收多組
* @param sendParam
*/
void batchSend(BatchSendParam sendParam);
}
public class SendParam {
/**
* 模板Id
*/
private String templateId;
/**
* 消息參數
*/
private MsgParam msgParam;
}
public class MsgParam {
/**
* 接收者:假設有多個,則用「,」分隔開
*/
private String receiver;
/**
* 自定義參數(文案)
*/
private Map<String, String> variables;
}
單個介面指的是:一次給1~N
人發送消息,這批人收到的是相同的文案
批量介面指的是:一次給1個人發送一個文案,但一次調用可以傳N個人及對應的文案
這裡的單個和批量不是以發送人的維度去定義的,而是人所對應的消息文案。
再再再舉個例子,現在我給關注我的同學都發一條消息:「大哥大嫂新年好」,這種情況我只需要使用send
方法就好了,相同的文案我給一批人發,這批人收到的文案是一模一樣的。
一次單推介面調用的請求參數:
{
"templateId": 12345,
"msgParam":
{
"receivers": "三歪,敖丙,雞蛋,米豆",
"variables": {
"content": "大哥大哥新年好",
"title": "來個贊吧,親"
}
}
}
如果我要給關注我的同學都發一條消息:「{微信用戶名},大哥大哥新年好」,這種情況我一般用batchSend
方法,在發送之前組合人所對應的文案封裝成一個List
,一次調用介面對調用方而言就是一次發了List.size()
組人。
一次批量介面調用的請求參數:
{
"templateId": 12345,
"msgParam": [
{
"receivers": "敖丙",
"variables": {
"content": "敖丙,大哥大哥新年好",
"title": "來個贊吧,親"
}
},
{
"receivers": "雞蛋",
"variables": {
"content": "雞蛋,大哥大哥新年好",
"title": "來個贊吧,親"
}
}
]
}
沒想到單單介面這塊我這篇就寫了這麼長,主要是照顧沒有經驗的同學哈~
回顧設計介面的思路:
-
起初是想每種消息類型分開不同的介面 -
考慮到同一個文案會下發給多個人,所以接收者參數得是支援」批量「的傳入 -
考慮到會有批量調用介面的場景,所以需要一個批量介面 -
考慮到需要統計下發消息的場景,所以需要抽象出」模板「,在平台下發的消息都得有」模板「 -
有了」模板「,可以將很多資訊固化到模板中,所以最終我們抽象出兩個介面:單推和批量。
再來聊聊模板
在前面我們已經定義好介面了,跟簡單你們所實現的發消息功能最主要的區別就是多了」模板「的概念。
在上面提到了一點:有了」模板「,可以將很多資訊固化到模板中。那我們固化了什麼東西到模板中呢?
-
能夠發送的消息種類。消息管理平台是可以發多種類型的消息的,所以我們模板是需要有欄位區分不同的消息類型。別想得這麼難,其實我們就用 1
表示簡訊,2
表示郵件… -
模板創建者資訊(手機號、姓名),這個跟發消息的實質內容沒有任何關係,只是如果模板出現了什麼不可描述的問題,背鍋俠總得找出來吧,如果模板創建者離職了怎麼辦?沒事,我會根據創建者把所在部門給找到,那就找部門背鍋(嘿嘿) -
消息的文案。綜合上面所看到的消息,我們可以看到一條消息無非由以下部分所組成:內容、標題、圖片、鏈接、影片…不同的消息能發的文案也不一樣,像簡訊頂多就只有內容和鏈接,而像通知欄消息(Push)就可以有標題、內容、圖片、鏈接所組成。所以,我們會把消息的文案用 json
的格式存儲在一個欄位中。 -
消息的業務規則。這裡所講的業務規則並不是真正的細節業務,而是對不同消息類型上的平台性約束。比如說,在產品層面上,希望晚上用戶收不到通知欄推送(畢竟會對用戶進行打擾);希望用戶一個小時內不會接收到兩條,一天最多收到N條通知欄推送(也是出於用戶的體驗)。這些平台性的約束就適合放在消息管理平台上做,你可以理解為是一個兜底的功能。 -
發送帳號。什麼?發條消息還有帳號的概念?你搞錯了吧,三歪?。其實是真的有的,在發郵件的時候可以選取不同的郵件帳號,在發微信公眾號消息時可以選取不同的微信公眾號(小程式同理),在發IM消息時可以使用不同的帳號發送。而在接入簡訊的時候其實是分了兩種類型的:通知和營銷。我們會把這些都抽象為帳號。 -
接收者Id類型。站內的IM消息用的是站內的 userId
,發通知欄消息(PUSH)用的是did
,發簡訊用的是手機號,發微信類的消息用的是openId
。指定接收者的Id類型,表明這個模板你要傳入哪種類型的id
。假設你指明是userId
,但你要發簡訊,消息管理平台就需要將userId
轉成手機號。這裡也是用一個欄位標識,1
表示userId
,2
表示did
…
可以發現的是,我們把一條消息所需要的資訊(甚至不需要的資訊)都塞進模板裡面了,等調用方傳入模板Id時,我就能拿到我想要的所有資訊了。
這是一個模板的全部了嗎?當然不是咯。上面提到的是模板共性的內容,我們按模板的使用場景還劃分兩種類型:
-
運營模板:運營要給指定一批人在某時某刻發送消息。(這一批人是 T+1
離線的)。例子:如果用戶註冊登錄了APP,可以隔一天(甚至更長時間)給用戶發消息。這種屬於非實時(離線)推送,這種就不需要技術來承接,去圈選人群後設置對應的時間即可推送。 -
技術模板:系統根據業務條件自動觸發一批消息,接收者名單也依賴業務場景(這批人一般是實時的)。例子:如果用戶註冊登錄了APP,就立馬需要給該用戶發消息。這種屬於實時推送,需要對應的技術來承接。
隨著系統和業務的演進,運營模板和技術模板的界限會越來越模糊。從本質上就是提供了兩種發消息的方式:
-
圈定一批人群,通過使用定時任務到點調用介面觸發(接收者、文案、發送時間都已明確)。 -
技術調用介面發送消息(接收者,文案,發送時間均由業務邏輯所產生)。例子:歡迎關注三歪,你的驗證碼是:888。有內鬼,終止交易。(當你關注三歪時,系統觸發一條消息。發送時間、驗證碼值、人員均不確定)
用戶在平台創建模板時,不同類型的模板需要填寫的欄位是不一樣的:運營模板需要填寫人群和任務觸發時間,而技術模板壓根就不需要填人群和任務觸發時間,所以我們模板會有一個欄位標識該模板是運營類型還是技術類型。1
表示運營類型,2
表示技術類型…
你覺得已經完了嗎?nonono,還沒有。我們還會區分消息的類型,目前最主要由三類組成:通知、營銷和驗證碼。
問題來了,為什麼我們要區分消息的類型呢?做統計用嗎?當然不是了,就這幾個粒度的類型有什麼好統計的。
還是以例子來說明吧:在2020-02-30
日,運營同學圈選了一個5000W
的人群選擇在晚上8點發送一條簡訊,大致的情況就是告訴用戶三歪文章更新了,不看血虧。系統在晚上8點
準時執行任務,讀取該模板的模板資訊下發。5000W
人,系統能秒發嗎?顯然是不行的
「
畫外音:除了考慮自身的系統能力,還得考慮下游能承受的能力。你瞎搞,人家就不帶你玩了。
所以,這5000W
人肯定是需要一定的時間才能完全下發的,現在我們假設是15分鐘
完全下發完畢吧。在8點2分
觸發了一條驗證碼的簡訊,結果因為這個5000W
的人群所導致驗證碼的消息延遲發送,這合理嗎?顯然不合理。
怎麼導致的?原因是這5000W
的消息和驗證碼的消息走的是同一個通道,導致驗證碼的消息被阻塞掉了。我們將不同的消息類型走不同的通道,就可以解決掉上面的問題。
所以,我們的系統在設計層面上就把運營模板默認設置為營銷類型的消息,而技術模板的消息類型由調用者自行選擇。在現實場景中,能堵的就只有營銷類的消息。

「
畫外音:上面所講的這些實踐都是跟使用場景和具體業務所關聯的,肯定不是一朝一夕就可以全想出來的。
模板也已經聊完了,還有些細節的東西我這就不贅述了。我再來簡要總結一下:
-
我們把發送一條消息所必要的資訊(文案、發送帳號、傳入的接收者Id類型、消息類型:通知、營銷和驗證碼)、平台性的資訊(業務規則:是否去重、屏蔽、展示邏輯等)和基本資訊(業務方資訊、消息名稱)全都塞到模板中 -
由於使用場景,模板會分為運營模板和技術模板。運營模板主要的特點是需要填寫人群資訊和發送時間,運營模板由消息管理平台自身進行調度發送消息。
介面實現
BB了這麼久了,可能很多人只是想來看看:三歪這逼在標題還敢還寫個揭秘,發消息誰不會,不就調個API嘛,還能給你玩出花來?
別急嘛,現在就寫。前面已經鋪墊了介面的設計和模板究竟是什麼了,現在我們還是回到介面的實現上吧。
首先我們簡單來看看消息管理平台的系統架構鏈路圖:

「
畫外音:上面我們所說的介面定義在統一調用層(接入層)中
調用者調用我們的send/batchSend
方法,會直接調用下游的API下發消息嗎?不會
直接調用下游的API下發消息風險太大了,介面1W+QPS
都是很正常的事,所以我們接收到消息後只是做簡單的參數校驗處理和資訊補全就把消息發到消息隊列上。這樣做的好處就是介面接入層十分輕量級,只要Kafka抗得住,請求就沒問題。

發到消息隊列時,會根據不同的消息類型發到不同的topic
上,發送層監聽topic
進行消費就好了。架構大致如下:

發送層消費topic
後,會把消息放在各自的記憶體隊列上,多個執行緒消費記憶體隊列的消息來實現消息的下發。
可以看到的是:從接入層發到消息隊列上我們就已經做了分topic
來實現業務上的隔離,在消費時我們也是放到各自的記憶體隊列中來進行消費。這就實現了:不同渠道和同渠道的不同類型的消息都互不干擾。
看到上面這張圖,如果思考過的同學肯定會問:這要記憶體隊列幹啥啊?反正你在上層已經分了topic
了,不用記憶體隊列也可以實現你所講的「業務隔離」啊。
也的確,這裡使用記憶體隊列的主要原因是為了提高並發度。提高了並發度,這意味著下發速度可以更快(在下發消息的過程中,最耗時的還是網路交互,像簡訊這種可以多開點執行緒進行消費)。
在前面所提到的業務規則就是在下發層這兒做的,包括夜間屏蔽、1小時去重和Id轉換等
-
夜間屏蔽就是判斷是否在晚上,如果勾選了夜間屏蔽並且在晚上,過濾掉就好了 -
1小時去重就是拿 userId+消息渠道
作為Key,看是否存在Redis上,假設存在,則過濾掉 -
id轉換
這功能我們做成了個系統,這塊我放在下面簡單說一下吧,這就不在贅述了。
「
畫外音:這種場景最好使用Pipeline來讀寫Redis
隨後就是適配各個渠道的介面,調用API
下發消息了,這塊就跟你們單個的實現沒什麼大的區別了,調用個介面還能給你玩出花來?(程式碼風格會稍好一些,模板方法模式、責任鏈、生產者與消費者模式等在項目中都有對應的應用)
總結一下介面的實現:
-
調用方調用介面時,介面不會同步直接調用下游的 API
發送消息,而是放入消息隊列上(支援高並發) -
放入隊列時,會根據不同渠道以及不同類型的消息進行分類,放到不同的topic(業務隔離) -
消費隊列時,會在本地使用阻塞隊列來提高並發度(加快消費的速度)
Id轉換(擴展)
在前面也提到了,發不同類型的消息會需要有不同的id
類型:微信類需要openId
、簡訊需要手機號、push通知欄推送需要did
。
在大多數情況下,一般調用者就傳入userId
給到我,我這邊需要根據不同的消息類型對userId
進行轉換。
那在我們這邊是怎麼實現該系統的呢?主要的步驟和邏輯有以下:
-
監聽用戶變更和微信公眾號訂閱/取關的 topic
,在Flink
清洗出一個統一的數據模型,將清洗後的數據寫到另一個的topic
。 -
Id映射系統監聽 Flink
清洗出的topic
,實時寫到數據源(這裡我們用的是搜索引擎)
看著也不會很難,對吧?
有沒有想過一個問題,為什麼要用一個Id映射系統去監聽Flink
洗出來的topic
,而不是在Flink
直接寫到數據源呢?
其實通過Flink直接寫到數據源也是完全沒問題的,而封裝了一個Id映射系統,就可以把這活做得更細緻。
從描述可以發現的是:在上面只實現了實時增量。很多時候我們會擔心增量存在問題,導致部分數據的不準確或者丟失,都會寫一份全量,Id映射也是同樣的。
那Id映射的全量是怎麼做的呢?用戶數據通過各種關聯關係會在Hive
形成一張表,而Id映射的全量就是基於這張Hive
表來實現全量(每天凌晨會讀取Hive表的資訊,再寫一遍數據源)。
基於上面這些邏輯,專門給Id映射做了個後台管理(可以手動觸發全量、是否開啟增量/全量、修改全量觸發的時間)

數據統計
我覺得這塊是消息管理平台最最最精華的一部分。
夢回我們當初的介面設計環節,我們就是因為有「數據統計」的需求,才引入了模板的概念。現在我們已經有了一個模板Id
了,在我們這邊是怎麼實現數據的統計的呢?我們對消息的統計都是基於模板的維度來實現的。
在創建模板時就會有一個模板Id生成,基於這個模板Id,我們生成了一個叫做umpId
的值:第一位分為技術/運營推送,最後八位是日期,中間六位是模板Id

因為所有的消息都會經過接入層,只要消息帶有鏈接,我們就會給鏈接後加上umpid
參數,鏈接會一直下發透傳,直至用戶點擊

每個系統在執行消息的時候都會可能導致這條消息發不出去(可能是消息去重了,可能是用戶的手機號不正確,可能是用戶太久沒有登錄了等等都有可能)。我們在這些『關鍵位置』都打上日誌,方便我們去排查。
這些「關鍵位置」我們都給它用簡單的數字來命個名。比如說:我們用「11」來代表這個用戶沒有綁定手機號,用「12」來代表這個用戶10分鐘前收到了一條一模一樣的消息,用「13」來代表這個用戶屏蔽了消息…..
「11」「12」「13」「14」「15」「16」這些就叫做「點位」,把這些點位在關鍵的位置中打上日誌,這個就叫做「埋點」
有了埋點,我們要做的就是將這些點位收集起來,然後統一處理成我們的數據格式,輸出到數據源中。
-
收集日誌 -
清洗日誌 -
輸出到數據源
有logAgent幫我們收集日誌到Kafka,實時清洗日誌我們用的是Flink,清洗完我們輸出到Redis(實時)/Hive(離線)。
Hive表的數據樣例(主要用於離線報表統計):

Redis會以多維度來進行存儲,以便支撐我們的業務需要。比如,要查一條消息為何發送失敗,通過userId
搜一下,直接完事(實時的都記錄在Redis中,所以這裡讀取的是Redis的數據)

比如,通過模板Id,查某條消息的整體下發情況:

為什麼我說這是消息管理平台最最最精華的呢?umpId
貫穿了所有消息管理平台經過的系統,只要是在消息管理平台發的消息,都會被記錄下來發送,可以通過點位來快速追蹤消息的下發情況。

總結一下數據統計:
-
設計出業務上的 umpid
,給所有的消息推送鏈接都加上umpdId
參數 -
打通上下游,共同設計和維護關鍵點位,統一日誌格式來實現跨平台的收集和清洗 -
兼顧實時和離線需求寫到不同的數據源,實時以多維度統計來快速定位問題
聊聊運營層面
前面提到了,運營的模板是需要圈選一批人群,然後下發消息的,那這群人從哪裡來?
在很久之前,消息管理平台也把人群給做掉了,大致的思路就是可以支援文件上傳
和hivesql
上傳兩種方式去圈選人群,圈出來上傳到hdfs
進行讀取,支援對人群的更新/切分/導出等功能。
有了人群的概念,你會發現你收到的消息其實都是跟你息息相關的(不是瞎給你推送的,你在裡面,才能圈到你)。可能是因為你看了幾天的連衣裙,所以給你推送連衣裙的消息,吸引去你購買。
後來,由於公司內部DMP
系統崛起,人群就都交由DMP
給管理了。但實現的思路也都是類似的,只不過還是同樣的:人家做的是平台,功能肯定比會自己寫幾個介面要完善不少。
做推送就免不了發錯了消息,特別是在運營側(分分鐘就推送千萬人),我們平台又做了什麼措施去儘可能避免這種問題的發生呢?
在運營圈定人群後,我們會有單獨的測試功能去「測試單個用戶」是否能正常下發消息,文案鏈接是否存在問題。
這一個步驟是必須要做的,給用戶發出的消息,首先要經過自己的校驗。如果確認鏈接和文案都無問題後,則提交任務,走工單審批後才能發送。

如果在啟動之後發現文案/鏈接存在問題,還可以攔截剩餘未發的消息。

針對於(技術方推送),我們在預發環境下配置了「白名單」才能收到消息。
線上消息有「去重」的邏輯:
-
在某段時間內,過濾掉重複消息 -
運營類消息推送(圈定人群的方式去下發消息)同一個用戶需要相隔一段時間才能下發一次。

雖然說,我們制定了很多的規則去盡量避免事故的發生,但不得不說推送還是一個容易出現事故的功能。我的牛逼已經吹完了,如果某天發現我的推送出了事故,不要@我,當沒見過這篇文章就好。
總結
不知道大家看完之後覺得消息管理平台難不難,從理解上的角度而言,這系統應該是很好理解的,沒有摻雜很多業務的東西,都是做平台性相關的內容。
這個系統能支援數W的QPS,每天億級的流量推送,一篇文章也不可能把消息管理平台的所有功能點都講完,內容也不止上面這些,但核心我應該是講清楚的了。

發送消息可以做得很簡單,也可以做得很平台化,如果你覺得你學到了些許東西,希望可以給我點個點贊和轉發一波。如果你對我寫的內容有疑問,歡迎評論區交流。
後續可能會更多寫廣告系統相關的內容,會以一些小的問題切入,不得不說,廣告系統比消息管理平台還是要複雜和有趣得多。提前關注預定最新文章,不會讓你希望的!
我是三歪,下期揭秘-廣告系統再見
三歪把【大廠面試知識點】、【簡歷模板】、【原創文章】全部整理成電子書,共有1263頁!點擊下方鏈接直接取就好了
PDF文檔的內容均為手打,有任何的不懂都可以直接來問我
