前言
文本已收錄至我的GitHub:https://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,每天億級的流量推送,一篇文章也不可能把訊息管理平臺的所有功能點都講完,內容也不止上面這些,但核心我應該是講清楚的了,

發送訊息可以做得很簡單,也可以做得很平臺化,如果你覺得你學到了些許東西,希望可以給我點個點贊和轉發一波,如果你對我寫的內容有疑問,歡迎評論區交流,
后續可能會更多寫廣告系統相關的內容,會以一些小的問題切入,不得不說,廣告系統比訊息管理平臺還是要復雜和有趣得多,提前關注預定最新文章,不會讓你希望的!
我是三歪,下期揭秘-廣告系統再見
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/111529.html
標籤:區塊鏈
上一篇:膜拜!阿里高級架構師用467張圖就講透了服務器端網路架構,真開發神器!
下一篇:作業三年的前端開發20k簡歷標準
