您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“消息管理平臺(tái)的Java實(shí)現(xiàn)原理是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
簡(jiǎn)單認(rèn)識(shí)《消息管理平臺(tái)》
「消息管理平臺(tái)」可能在不同的公司會(huì)有不同的叫法,有的時(shí)候我會(huì)叫它「推送系統(tǒng)」,有的時(shí)候我會(huì)叫它「消息管理平臺(tái)」,也有的同事叫它「觸達(dá)平臺(tái)」,甚至浮夸點(diǎn)我也可以叫它「消息中臺(tái)」
但是不管怎么樣,它的功能就是給用戶發(fā)消息。在公司里它是怎么樣的定位?只要以官方名義發(fā)送的消息,都走消息管理平臺(tái)。
一般你注冊(cè)一個(gè)APP/網(wǎng)站,你可以收到該APP/網(wǎng)站給你發(fā)什么消息呢?一般就以下吧?
站內(nèi)信(IM)消息:其實(shí)就是APP內(nèi)聊天的消息
通知欄(PUSH)消息:系統(tǒng)彈窗消息
郵件(Email)消息
短信(Sms)消息
微信服務(wù)號(hào)消息
微信小程序(服務(wù)通知)消息
為什么要有消息管理平臺(tái)?
可以說(shuō),只要是做APP的公司幾乎都會(huì)有消息管理平臺(tái)。
我們很多時(shí)候都會(huì)想給用戶發(fā)消息:
有可能是用戶想要這樣的功能(預(yù)約活動(dòng)提醒通知)
也有可能是我們想通過(guò)發(fā)消息來(lái)「喚醒」/「告知」等操作,告訴用戶我們還在(大爺來(lái)玩啊)
那么問(wèn)題來(lái)了,發(fā)消息困難嗎?發(fā)消息復(fù)雜嗎?
顯然,發(fā)消息非常簡(jiǎn)單,一點(diǎn)兒也不復(fù)雜。
發(fā)短信無(wú)非就是調(diào)用第三方短信的API、發(fā)郵件無(wú)非就是調(diào)用郵件的API、發(fā)微信類的消息(手Q/小程序/微信服務(wù)號(hào))無(wú)非就是調(diào)用微信的API、發(fā)通知欄消息(Push)無(wú)非就是調(diào)APNS/手機(jī)廠商的API、發(fā)IM消息也可以使用云服務(wù),調(diào)云服務(wù)的API...
可能很多人的項(xiàng)目都是這么干的,無(wú)非發(fā)條消息,自己實(shí)現(xiàn)也不是不可以。
但這樣會(huì)帶來(lái)的問(wèn)題就是在一個(gè)公司內(nèi)部,會(huì)有很多個(gè)項(xiàng)目都會(huì)有「發(fā)送消息」的代碼實(shí)現(xiàn)。假設(shè)發(fā)消息出了問(wèn)題,還得去自己解決。
首先是系統(tǒng)不好維護(hù),其次是沒必要。我一個(gè)搞廣告的,雖然我要發(fā)消息,憑什么要我自己去實(shí)現(xiàn)?
我們?cè)趯懘a時(shí),可能會(huì)把公用的代碼抽成方法,供當(dāng)前的項(xiàng)目重復(fù)調(diào)用。如果該公用的代碼被多個(gè)項(xiàng)目使用,可能我們又會(huì)抽成組件包,供多個(gè)項(xiàng)目使用。只要該公用的代碼被足夠多的人去用,那它就很有可能從組件上升為一個(gè)平臺(tái)(系統(tǒng))級(jí)的東西。
如何實(shí)現(xiàn)消息管理平臺(tái)?
回到消息管理平臺(tái)的本質(zhì),它就是一個(gè)可以發(fā)消息的系統(tǒng)。那怎么設(shè)計(jì)和實(shí)現(xiàn)呢?我們從接口說(shuō)起吧。
接口設(shè)計(jì)
消息管理平臺(tái)是一個(gè)提供消息發(fā)送服務(wù)的平臺(tái),如果讓我去實(shí)現(xiàn),我的想法可能是把每種類型的消息都寫一個(gè)接口,然后把這些接口對(duì)外暴露。
所以,可能會(huì)有以下的接口:
/** * content:發(fā)送的文案 * 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); //....
這樣實(shí)現(xiàn)好像也不是不可以,反正每個(gè)接口都挺清晰的,要發(fā)什么類型的消息,你調(diào)用哪個(gè)接口就好了。
假設(shè)我們定義了如上的接口,現(xiàn)在我們要發(fā)消息了,我們會(huì)有以下的場(chǎng)景:
文案:「你好,我是三歪」,接收人:「woshisanwai」 (一次只發(fā)給一個(gè)人)
文案:「你好,我是三歪」,接收人:「woshisanwai,java3y,javayyy」(相同的文案發(fā)給多個(gè)人)
假如你是新手,你可能會(huì)想:這簡(jiǎn)單,我每種類型分開兩個(gè)接口,分別是單發(fā)和批量接口。
sendSingleSms(); sendBatchSms(); //...
上面這樣設(shè)計(jì)有必要嗎?其實(shí)沒啥必要。我將接收人定義為一個(gè)Array不就得了?Array的size==1,那我就把該文案發(fā)給這個(gè)人,Array的size>1,那我就把這個(gè)文案發(fā)給Array里邊的所有人。
所以我們的接口還是只有一個(gè):
/** * content:發(fā)送的文案 * receiver:接收者(可多個(gè),可單個(gè)) */ sendSms(String content,Set<String> receiver);
其實(shí)在我們這也不是定義Array,我的接口receiver仍然是String,如果有多個(gè)用,號(hào)分隔就可以了。
/** * content:發(fā)送的文案 * receiver:接收者(可多個(gè),可單個(gè)),多個(gè)用逗號(hào)分隔開 */ sendSms(String content,String receiver);
現(xiàn)在還有個(gè)場(chǎng)景,不同的文案發(fā)給不同的人怎么辦?有的人就說(shuō),這不已經(jīng)實(shí)現(xiàn)了嗎?直接調(diào)用上面的接口就完事了啊。你又不是不能重復(fù)調(diào)用,比如說(shuō):
文案:「你好,我是Java3y」,接收人:「woshisanwai」
文案:「你好,我是三歪」,接收人:「3y」
文案:「你好,woshisanwai」,接收人:「三歪」
.....
確實(shí)如此,本來(lái)就可以這樣做的。但不夠好
舉個(gè)真實(shí)的場(chǎng)景:現(xiàn)在有一個(gè)主播開播了,得發(fā)送一條消息告訴訂閱該主播的人趕緊去看。為了提高該條通知的效果 ,在文案上我們是這樣設(shè)計(jì)的:{用戶昵稱},你訂閱的主播三歪已經(jīng)開播了,趕緊去看吧!
這種消息我們肯定是要求實(shí)時(shí)性的(假設(shè)推送消息的速度太慢了,等到用戶收到消息了,主播都下播了,那用戶不得錘死你?)
畫外音:顯然這種情況屬于不同的文案發(fā)給不同的人
這種消息在業(yè)務(wù)層是怎么做的呢?可能是掃DB表,遍歷出訂閱該主播的粉絲,然后給他們推送消息。
那現(xiàn)在我們只能每掃出一個(gè)訂閱該主播的粉絲,就得調(diào)用send()接口發(fā)送消息。如果該主播有500W的粉絲,那就得調(diào)用500W次send接口,這不是很可怕?這調(diào)用次數(shù),這網(wǎng)絡(luò)開銷...
于是乎,我們得提供一個(gè)“批量”接口,可以讓調(diào)用方一次傳入不同文案所攜帶不同的人。那怎么做呢?也很簡(jiǎn)單,實(shí)際上就是上面接口再封裝一層,讓調(diào)用方能“批量”傳進(jìn)來(lái)就好了。所以代碼可以是這樣的:
/** * 一次傳入多個(gè)(文案以及發(fā)送者)的“組”進(jìn)來(lái) * List<SendParam> * SendParam 里邊 定義了 content 和receiver */ sendBatchSms(List<SendParam> sendParam);
現(xiàn)在接口的“雛形”已經(jīng)出現(xiàn)了,到這里我們實(shí)現(xiàn)了消息管理平臺(tái)最基本的功能:發(fā)消息
我們先不管內(nèi)部的實(shí)現(xiàn)是如何,假設(shè)我們已經(jīng)適配好調(diào)通好對(duì)應(yīng)的API了,現(xiàn)在我們的接口在發(fā)消息層面上已經(jīng)有充分必要的條件了:只要你傳入接收者和發(fā)送內(nèi)容,我就可以給你發(fā)消息。
但我們對(duì)外稱可是一個(gè)平臺(tái)啊,怎么能搞得像是只封裝了幾個(gè)方法似的,平臺(tái)就該有平臺(tái)的樣子。
我舉個(gè)日常最最最基本的功能:有人調(diào)用了我的接口發(fā)了條短信,這條短信的文案是一條內(nèi)容為驗(yàn)證碼類型,他問(wèn)我這條短信到底下發(fā)到用戶手上了沒有。
如果接入過(guò)短信的同學(xué)就會(huì)知道:發(fā)送短信到用戶收到是一個(gè)異步的過(guò)程
調(diào)用短信提供商的API,假設(shè)你的入?yún)]有問(wèn)題,它會(huì)告訴你“調(diào)用”成功。你想真正地知道此條內(nèi)容到底有沒有下發(fā)到用戶手上,你有兩種辦法:一、提供一個(gè)接口給短信服務(wù)商調(diào)用,等真正處理完了,短信服務(wù)商會(huì)調(diào)用你的接口,告訴你最終的結(jié)果是什么。二、你去輪詢短信服務(wù)商的接口,獲取最終的結(jié)果。
回到問(wèn)題上,他想要他調(diào)用我的接口有沒有把短信發(fā)送成功,那我只要問(wèn)他拿到手機(jī)號(hào)和文案,然后有以下步驟:
判斷該手機(jī)號(hào)和文案在下發(fā)時(shí)是否正常(有沒有真正調(diào)用下發(fā)短信的接口)
假設(shè)調(diào)用短信接口下發(fā)成功,那看下返回的回執(zhí)(下發(fā)結(jié)果)是否正常
那目前我們?cè)诂F(xiàn)有的接口,還是很完美地支持上面的問(wèn)題的,對(duì)吧?只要我們記錄了下發(fā)的結(jié)果和回執(zhí)的信息,我們就可以告訴他所提供的手機(jī)號(hào)和文案究竟有沒有下發(fā)到用戶手上。
那今天他又過(guò)來(lái)問(wèn)了:今天有很多人來(lái)反饋收不到驗(yàn)證碼短信(不是全部人收不到,是大部分人),我想了解一下今天驗(yàn)證碼短信下發(fā)的成功率是多少。
此時(shí)的我,只能去匹配(like %%)他的文案調(diào)用我的接口下發(fā)了多少人,調(diào)用短信服務(wù)商的API下發(fā)成功多少人,收到的成功回執(zhí)(結(jié)果)有多少人。
通過(guò)匹配文案的方式最終也是可以告訴他結(jié)果的,但是這種是很傻X的做法。歸根到底還是因?yàn)橄到y(tǒng)提供的服務(wù)還是太薄弱了。
那怎么解決上面所講的問(wèn)題呢?其實(shí)也很簡(jiǎn)單,既然匹配文案很傻X,那我給他這一批驗(yàn)證碼的短信取個(gè)唯一的Id那不就可以了嗎?
像我們?nèi)ソ尤攵绦欧?wù)商一樣,我們需要去新建一個(gè)短信模板,這個(gè)模板代表了你要發(fā)送的內(nèi)容,新建模板后會(huì)給你個(gè)模板Id,你下發(fā)的時(shí)候指定這個(gè)模板Id就好了。
那我們的平臺(tái)也可以這樣玩啊,你想發(fā)消息對(duì)吧?可以,先來(lái)我的平臺(tái)新建一個(gè)”模板“,到時(shí)候把模板Id發(fā)給我就行。
于是,我們就完美地解決上面所提到的問(wèn)題了。
我們現(xiàn)在再來(lái)討論一下有沒有必要不同的消息類型(短信、郵件、IM等)需要分開不同的的接口,其實(shí)是沒必要的了。因?yàn)橹灰橄罅恕蹦0濉斑@個(gè)概念,消息類型自然我們就可以在模板上固化掉,只要傳了模板Id,我就知道你發(fā)的是什么類型消息。
這樣一來(lái),我們最終會(huì)有兩個(gè)接口:批量與單個(gè)發(fā)送接口。
/** * 發(fā)送消息接口 * @author java3y */ public interface SendService { /** * 相同文案,發(fā)給0~N 人 * @param sendParam */ void send(SendParam sendParam); /** * 不同文案,發(fā)給不同人,一次可接收多組 * @param sendParam */ void batchSend(BatchSendParam sendParam); } public class SendParam { /** * 模板Id */ private String templateId; /** * 消息參數(shù) */ private MsgParam msgParam; } public class MsgParam { /** * 接收者:假設(shè)有多個(gè),則用「,」分隔開 */ private String receiver; /** * 自定義參數(shù)(文案) */ private Map<String, String> variables; }
單個(gè)接口指的是:一次給1~N人發(fā)送消息,這批人收到的是相同的文案
批量接口指的是:一次給1個(gè)人發(fā)送一個(gè)文案,但一次調(diào)用可以傳N個(gè)人及對(duì)應(yīng)的文案
這里的單個(gè)和批量不是以發(fā)送人的維度去定義的,而是人所對(duì)應(yīng)的消息文案。
再再再舉個(gè)例子,現(xiàn)在我給關(guān)注我的同學(xué)都發(fā)一條消息:「大哥大嫂新年好」,這種情況我只需要使用send方法就好了,相同的文案我給一批人發(fā),這批人收到的文案是一模一樣的。
一次單推接口調(diào)用的請(qǐng)求參數(shù):
{ "templateId": 12345, "msgParam": { "receivers": "三歪,敖丙,雞蛋,米豆", "variables": { "content": "大哥大哥新年好", "title": "來(lái)個(gè)贊吧,親" } } }
如果我要給關(guān)注我的同學(xué)都發(fā)一條消息:「{微信用戶名},大哥大哥新年好」,這種情況我一般用batchSend方法,在發(fā)送之前組合人所對(duì)應(yīng)的文案封裝成一個(gè)List,一次調(diào)用接口對(duì)調(diào)用方而言就是一次發(fā)了List.size()組人。
一次批量接口調(diào)用的請(qǐng)求參數(shù):
{ "templateId": 12345, "msgParam": [ { "receivers": "敖丙", "variables": { "content": "敖丙,大哥大哥新年好", "title": "來(lái)個(gè)贊吧,親" } }, { "receivers": "雞蛋", "variables": { "content": "雞蛋,大哥大哥新年好", "title": "來(lái)個(gè)贊吧,親" } } ] }
沒想到單單接口這塊我這篇就寫了這么長(zhǎng),主要是照顧沒有經(jīng)驗(yàn)的同學(xué)哈~
回顧設(shè)計(jì)接口的思路:
起初是想每種消息類型分開不同的接口
考慮到同一個(gè)文案會(huì)下發(fā)給多個(gè)人,所以接收者參數(shù)得是支持”批量“的傳入
考慮到會(huì)有批量調(diào)用接口的場(chǎng)景,所以需要一個(gè)批量接口
考慮到需要統(tǒng)計(jì)下發(fā)消息的場(chǎng)景,所以需要抽象出”模板“,在平臺(tái)下發(fā)的消息都得有”模板“
有了”模板“,可以將很多信息固化到模板中,所以最終我們抽象出兩個(gè)接口:?jiǎn)瓮坪团俊?/p>
再來(lái)聊聊模板
在前面我們已經(jīng)定義好接口了,跟簡(jiǎn)單你們所實(shí)現(xiàn)的發(fā)消息功能最主要的區(qū)別就是多了”模板“的概念。
在上面提到了一點(diǎn):有了”模板“,可以將很多信息固化到模板中。那我們固化了什么東西到模板中呢?
能夠發(fā)送的消息種類。消息管理平臺(tái)是可以發(fā)多種類型的消息的,所以我們模板是需要有字段區(qū)分不同的消息類型。別想得這么難,其實(shí)我們就用1表示短信,2表示郵件...
模板創(chuàng)建者信息(手機(jī)號(hào)、姓名),這個(gè)跟發(fā)消息的實(shí)質(zhì)內(nèi)容沒有任何關(guān)系,只是如果模板出現(xiàn)了什么不可描述的問(wèn)題,背鍋俠總得找出來(lái)吧,如果模板創(chuàng)建者離職了怎么辦?沒事,我會(huì)根據(jù)創(chuàng)建者把所在部門給找到,那就找部門背鍋(嘿嘿)
消息的文案。綜合上面所看到的消息,我們可以看到一條消息無(wú)非由以下部分所組成:內(nèi)容、標(biāo)題、圖片、鏈接、視頻...不同的消息能發(fā)的文案也不一樣,像短信頂多就只有內(nèi)容和鏈接,而像通知欄消息(Push)就可以有標(biāo)題、內(nèi)容、圖片、鏈接所組成。所以,我們會(huì)把消息的文案用json的格式存儲(chǔ)在一個(gè)字段中。
消息的業(yè)務(wù)規(guī)則。這里所講的業(yè)務(wù)規(guī)則并不是真正的細(xì)節(jié)業(yè)務(wù),而是對(duì)不同消息類型上的平臺(tái)性約束。比如說(shuō),在產(chǎn)品層面上,希望晚上用戶收不到通知欄推送(畢竟會(huì)對(duì)用戶進(jìn)行打擾);希望用戶一個(gè)小時(shí)內(nèi)不會(huì)接收到兩條,一天最多收到N條通知欄推送(也是出于用戶的體驗(yàn))。這些平臺(tái)性的約束就適合放在消息管理平臺(tái)上做,你可以理解為是一個(gè)兜底的功能。
發(fā)送賬號(hào)。什么?發(fā)條消息還有賬號(hào)的概念?你搞錯(cuò)了吧,三歪?。其實(shí)是真的有的,在發(fā)郵件的時(shí)候可以選取不同的郵件賬號(hào),在發(fā)微信公眾號(hào)消息時(shí)可以選取不同的微信公眾號(hào)(小程序同理),在發(fā)IM消息時(shí)可以使用不同的賬號(hào)發(fā)送。而在接入短信的時(shí)候其實(shí)是分了兩種類型的:通知和營(yíng)銷。我們會(huì)把這些都抽象為賬號(hào)。
接收者Id類型。站內(nèi)的IM消息用的是站內(nèi)的userId,發(fā)通知欄消息(PUSH)用的是did,發(fā)短信用的是手機(jī)號(hào),發(fā)微信類的消息用的是openId。指定接收者的Id類型,表明這個(gè)模板你要傳入哪種類型的id。假設(shè)你指明是userId,但你要發(fā)短信,消息管理平臺(tái)就需要將userId轉(zhuǎn)成手機(jī)號(hào)。這里也是用一個(gè)字段標(biāo)識(shí),1表示userId,2表示did ...
可以發(fā)現(xiàn)的是,我們把一條消息所需要的信息(甚至不需要的信息)都塞進(jìn)模板里面了,等調(diào)用方傳入模板Id時(shí),我就能拿到我想要的所有信息了。
這是一個(gè)模板的全部了嗎?當(dāng)然不是咯。上面提到的是模板共性的內(nèi)容,我們按模板的使用場(chǎng)景還劃分兩種類型:
運(yùn)營(yíng)模板:運(yùn)營(yíng)要給指定一批人在某時(shí)某刻發(fā)送消息。(這一批人是T+1離線的)。例子:如果用戶注冊(cè)登錄了APP,可以隔一天(甚至更長(zhǎng)時(shí)間)給用戶發(fā)消息。這種屬于非實(shí)時(shí)(離線)推送,這種就不需要技術(shù)來(lái)承接,去圈選人群后設(shè)置對(duì)應(yīng)的時(shí)間即可推送。
技術(shù)模板:系統(tǒng)根據(jù)業(yè)務(wù)條件自動(dòng)觸發(fā)一批消息,接收者名單也依賴業(yè)務(wù)場(chǎng)景(這批人一般是實(shí)時(shí)的)。例子:如果用戶注冊(cè)登錄了APP,就立馬需要給該用戶發(fā)消息。這種屬于實(shí)時(shí)推送,需要對(duì)應(yīng)的技術(shù)來(lái)承接。
隨著系統(tǒng)和業(yè)務(wù)的演進(jìn),運(yùn)營(yíng)模板和技術(shù)模板的界限會(huì)越來(lái)越模糊。從本質(zhì)上就是提供了兩種發(fā)消息的方式:
圈定一批人群,通過(guò)使用定時(shí)任務(wù)到點(diǎn)調(diào)用接口觸發(fā)(接收者、文案、發(fā)送時(shí)間都已明確)。
技術(shù)調(diào)用接口發(fā)送消息(接收者,文案,發(fā)送時(shí)間均由業(yè)務(wù)邏輯所產(chǎn)生)。例子:歡迎關(guān)注三歪,你的驗(yàn)證碼是:888。有內(nèi)鬼,終止交易。(當(dāng)你關(guān)注三歪時(shí),系統(tǒng)觸發(fā)一條消息。發(fā)送時(shí)間、驗(yàn)證碼值、人員均不確定)
用戶在平臺(tái)創(chuàng)建模板時(shí),不同類型的模板需要填寫的字段是不一樣的:運(yùn)營(yíng)模板需要填寫人群和任務(wù)觸發(fā)時(shí)間,而技術(shù)模板壓根就不需要填人群和任務(wù)觸發(fā)時(shí)間,所以我們模板會(huì)有一個(gè)字段標(biāo)識(shí)該模板是運(yùn)營(yíng)類型還是技術(shù)類型。1表示運(yùn)營(yíng)類型,2表示技術(shù)類型...
你覺得已經(jīng)完了嗎?nonono,還沒有。我們還會(huì)區(qū)分消息的類型,目前最主要由三類組成:通知、營(yíng)銷和驗(yàn)證碼。
問(wèn)題來(lái)了,為什么我們要區(qū)分消息的類型呢?做統(tǒng)計(jì)用嗎?當(dāng)然不是了,就這幾個(gè)粒度的類型有什么好統(tǒng)計(jì)的。
還是以例子來(lái)說(shuō)明吧:在2020-02-30日,運(yùn)營(yíng)同學(xué)圈選了一個(gè)5000W的人群選擇在晚上8點(diǎn)發(fā)送一條短信,大致的情況就是告訴用戶三歪文章更新了,不看血虧。系統(tǒng)在晚上8點(diǎn)準(zhǔn)時(shí)執(zhí)行任務(wù),讀取該模板的模板信息下發(fā)。5000W人,系統(tǒng)能秒發(fā)嗎?顯然是不行的
畫外音:除了考慮自身的系統(tǒng)能力,還得考慮下游能承受的能力。你瞎搞,人家就不帶你玩了。
所以,這5000W人肯定是需要一定的時(shí)間才能完全下發(fā)的,現(xiàn)在我們假設(shè)是15分鐘完全下發(fā)完畢吧。在8點(diǎn)2分觸發(fā)了一條驗(yàn)證碼的短信,結(jié)果因?yàn)檫@個(gè)5000W的人群所導(dǎo)致驗(yàn)證碼的消息延遲發(fā)送,這合理嗎?顯然不合理。
怎么導(dǎo)致的?原因是這5000W的消息和驗(yàn)證碼的消息走的是同一個(gè)通道,導(dǎo)致驗(yàn)證碼的消息被阻塞掉了。我們將不同的消息類型走不同的通道,就可以解決掉上面的問(wèn)題。
所以,我們的系統(tǒng)在設(shè)計(jì)層面上就把運(yùn)營(yíng)模板默認(rèn)設(shè)置為營(yíng)銷類型的消息,而技術(shù)模板的消息類型由調(diào)用者自行選擇。在現(xiàn)實(shí)場(chǎng)景中,能堵的就只有營(yíng)銷類的消息。
畫外音:上面所講的這些實(shí)踐都是跟使用場(chǎng)景和具體業(yè)務(wù)所關(guān)聯(lián)的,肯定不是一朝一夕就可以全想出來(lái)的。
模板也已經(jīng)聊完了,還有些細(xì)節(jié)的東西我這就不贅述了。我再來(lái)簡(jiǎn)要總結(jié)一下:
我們把發(fā)送一條消息所必要的信息(文案、發(fā)送賬號(hào)、傳入的接收者Id類型、消息類型:通知、營(yíng)銷和驗(yàn)證碼)、平臺(tái)性的信息(業(yè)務(wù)規(guī)則:是否去重、屏蔽、展示邏輯等)和基本信息(業(yè)務(wù)方信息、消息名稱)全都塞到模板中
由于使用場(chǎng)景,模板會(huì)分為運(yùn)營(yíng)模板和技術(shù)模板。運(yùn)營(yíng)模板主要的特點(diǎn)是需要填寫人群信息和發(fā)送時(shí)間,運(yùn)營(yíng)模板由消息管理平臺(tái)自身進(jìn)行調(diào)度發(fā)送消息。
接口實(shí)現(xiàn)
BB了這么久了,可能很多人只是想來(lái)看看:三歪這逼在標(biāo)題還敢還寫個(gè)揭秘,發(fā)消息誰(shuí)不會(huì),不就調(diào)個(gè)API嘛,還能給你玩出花來(lái)?
別急嘛,現(xiàn)在就寫。前面已經(jīng)鋪墊了接口的設(shè)計(jì)和模板究竟是什么了,現(xiàn)在我們還是回到接口的實(shí)現(xiàn)上吧。
首先我們簡(jiǎn)單來(lái)看看消息管理平臺(tái)的系統(tǒng)架構(gòu)鏈路圖:
畫外音:上面我們所說(shuō)的接口定義在統(tǒng)一調(diào)用層(接入層)中
調(diào)用者調(diào)用我們的send/batchSend方法,會(huì)直接調(diào)用下游的API下發(fā)消息嗎?不會(huì)
直接調(diào)用下游的API下發(fā)消息風(fēng)險(xiǎn)太大了,接口1W+QPS都是很正常的事,所以我們接收到消息后只是做簡(jiǎn)單的參數(shù)校驗(yàn)處理和信息補(bǔ)全就把消息發(fā)到消息隊(duì)列上。這樣做的好處就是接口接入層十分輕量級(jí),只要Kafka抗得住,請(qǐng)求就沒問(wèn)題。
發(fā)到消息隊(duì)列時(shí),會(huì)根據(jù)不同的消息類型發(fā)到不同的topic上,發(fā)送層監(jiān)聽topic進(jìn)行消費(fèi)就好了。架構(gòu)大致如下:
發(fā)送層消費(fèi)topic后,會(huì)把消息放在各自的內(nèi)存隊(duì)列上,多個(gè)線程消費(fèi)內(nèi)存隊(duì)列的消息來(lái)實(shí)現(xiàn)消息的下發(fā)。
可以看到的是:從接入層發(fā)到消息隊(duì)列上我們就已經(jīng)做了分topic來(lái)實(shí)現(xiàn)業(yè)務(wù)上的隔離,在消費(fèi)時(shí)我們也是放到各自的內(nèi)存隊(duì)列中來(lái)進(jìn)行消費(fèi)。這就實(shí)現(xiàn)了:不同渠道和同渠道的不同類型的消息都互不干擾。
看到上面這張圖,如果思考過(guò)的同學(xué)肯定會(huì)問(wèn):這要內(nèi)存隊(duì)列干啥啊?反正你在上層已經(jīng)分了topic了,不用內(nèi)存隊(duì)列也可以實(shí)現(xiàn)你所講的“業(yè)務(wù)隔離”啊。
也的確,這里使用內(nèi)存隊(duì)列的主要原因是為了提高并發(fā)度。提高了并發(fā)度,這意味著下發(fā)速度可以更快(在下發(fā)消息的過(guò)程中,最耗時(shí)的還是網(wǎng)絡(luò)交互,像短信這種可以多開點(diǎn)線程進(jìn)行消費(fèi))。
在前面所提到的業(yè)務(wù)規(guī)則就是在下發(fā)層這兒做的,包括夜間屏蔽、1小時(shí)去重和Id轉(zhuǎn)換等
夜間屏蔽就是判斷是否在晚上,如果勾選了夜間屏蔽并且在晚上,過(guò)濾掉就好了
1小時(shí)去重就是拿userId+消息渠道作為Key,看是否存在Redis上,假設(shè)存在,則過(guò)濾掉
id轉(zhuǎn)換這功能我們做成了個(gè)系統(tǒng),這塊我放在下面簡(jiǎn)單說(shuō)一下吧,這就不在贅述了。
畫外音:這種場(chǎng)景最好使用Pipeline來(lái)讀寫Redis
隨后就是適配各個(gè)渠道的接口,調(diào)用API下發(fā)消息了,這塊就跟你們單個(gè)的實(shí)現(xiàn)沒什么大的區(qū)別了,調(diào)用個(gè)接口還能給你玩出花來(lái)?(代碼風(fēng)格會(huì)稍好一些,模板方法模式、責(zé)任鏈、生產(chǎn)者與消費(fèi)者模式等在項(xiàng)目中都有對(duì)應(yīng)的應(yīng)用)
總結(jié)一下接口的實(shí)現(xiàn):
調(diào)用方調(diào)用接口時(shí),接口不會(huì)同步直接調(diào)用下游的API發(fā)送消息,而是放入消息隊(duì)列上(支持高并發(fā))
放入隊(duì)列時(shí),會(huì)根據(jù)不同渠道以及不同類型的消息進(jìn)行分類,放到不同的topic(業(yè)務(wù)隔離)
消費(fèi)隊(duì)列時(shí),會(huì)在本地使用阻塞隊(duì)列來(lái)提高并發(fā)度(加快消費(fèi)的速度)
Id轉(zhuǎn)換
(擴(kuò)展)在前面也提到了,發(fā)不同類型的消息會(huì)需要有不同的id類型:微信類需要openId、短信需要手機(jī)號(hào)、push通知欄推送需要did。
在大多數(shù)情況下,一般調(diào)用者就傳入userId給到我,我這邊需要根據(jù)不同的消息類型對(duì)userId進(jìn)行轉(zhuǎn)換。
那在我們這邊是怎么實(shí)現(xiàn)該系統(tǒng)的呢?主要的步驟和邏輯有以下:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
監(jiān)聽用戶變更和微信公眾號(hào)訂閱/取關(guān)的topic,在Flink清洗出一個(gè)統(tǒng)一的數(shù)據(jù)模型,將清洗后的數(shù)據(jù)寫到另一個(gè)的topic。
Id映射系統(tǒng)監(jiān)聽Flink清洗出的topic,實(shí)時(shí)寫到數(shù)據(jù)源(這里我們用的是搜索引擎)
看著也不會(huì)很難,對(duì)吧?
有沒有想過(guò)一個(gè)問(wèn)題,為什么要用一個(gè)Id映射系統(tǒng)去監(jiān)聽Flink洗出來(lái)的topic,而不是在Flink直接寫到數(shù)據(jù)源呢?
其實(shí)通過(guò)Flink直接寫到數(shù)據(jù)源也是完全沒問(wèn)題的,而封裝了一個(gè)Id映射系統(tǒng),就可以把這活做得更細(xì)致。
從描述可以發(fā)現(xiàn)的是:在上面只實(shí)現(xiàn)了實(shí)時(shí)增量。很多時(shí)候我們會(huì)擔(dān)心增量存在問(wèn)題,導(dǎo)致部分?jǐn)?shù)據(jù)的不準(zhǔn)確或者丟失,都會(huì)寫一份全量,Id映射也是同樣的。
那Id映射的全量是怎么做的呢?用戶數(shù)據(jù)通過(guò)各種關(guān)聯(lián)關(guān)系會(huì)在Hive形成一張表,而Id映射的全量就是基于這張Hive表來(lái)實(shí)現(xiàn)全量(每天凌晨會(huì)讀取Hive表的信息,再寫一遍數(shù)據(jù)源)。
基于上面這些邏輯,專門給Id映射做了個(gè)后臺(tái)管理(可以手動(dòng)觸發(fā)全量、是否開啟增量/全量、修改全量觸發(fā)的時(shí)間)
數(shù)據(jù)統(tǒng)計(jì)
我覺得這塊是消息管理平臺(tái)最最最精華的一部分。
夢(mèng)回我們當(dāng)初的接口設(shè)計(jì)環(huán)節(jié),我們就是因?yàn)橛小皵?shù)據(jù)統(tǒng)計(jì)”的需求,才引入了模板的概念?,F(xiàn)在我們已經(jīng)有了一個(gè)模板Id了,在我們這邊是怎么實(shí)現(xiàn)數(shù)據(jù)的統(tǒng)計(jì)的呢?我們對(duì)消息的統(tǒng)計(jì)都是基于模板的維度來(lái)實(shí)現(xiàn)的。
在創(chuàng)建模板時(shí)就會(huì)有一個(gè)模板Id生成,基于這個(gè)模板Id,我們生成了一個(gè)叫做umpId的值:第一位分為技術(shù)/運(yùn)營(yíng)推送,最后八位是日期,中間六位是模板Id
因?yàn)樗械南⒍紩?huì)經(jīng)過(guò)接入層,只要消息帶有鏈接,我們就會(huì)給鏈接后加上umpid參數(shù),鏈接會(huì)一直下發(fā)透?jìng)?,直至用戶點(diǎn)擊
每個(gè)系統(tǒng)在執(zhí)行消息的時(shí)候都會(huì)可能導(dǎo)致這條消息發(fā)不出去(可能是消息去重了,可能是用戶的手機(jī)號(hào)不正確,可能是用戶太久沒有登錄了等等都有可能)。我們?cè)谶@些『關(guān)鍵位置』都打上日志,方便我們?nèi)ヅ挪椤?/p>
這些「關(guān)鍵位置」我們都給它用簡(jiǎn)單的數(shù)字來(lái)命個(gè)名。比如說(shuō):我們用「11」來(lái)代表這個(gè)用戶沒有綁定手機(jī)號(hào),用「12」來(lái)代表這個(gè)用戶10分鐘前收到了一條一模一樣的消息,用「13」來(lái)代表這個(gè)用戶屏蔽了消息.....
「11」「12」「13」「14」「15」「16」這些就叫做「點(diǎn)位」,把這些點(diǎn)位在關(guān)鍵的位置中打上日志,這個(gè)就叫做「埋點(diǎn)」
有了埋點(diǎn),我們要做的就是將這些點(diǎn)位收集起來(lái),然后統(tǒng)一處理成我們的數(shù)據(jù)格式,輸出到數(shù)據(jù)源中。
收集日志
清洗日志
輸出到數(shù)據(jù)源
有l(wèi)ogAgent幫我們收集日志到Kafka,實(shí)時(shí)清洗日志我們用的是Flink,清洗完我們輸出到Redis(實(shí)時(shí))/Hive(離線)。
Hive表的數(shù)據(jù)樣例(主要用于離線報(bào)表統(tǒng)計(jì)):
Redis會(huì)以多維度來(lái)進(jìn)行存儲(chǔ),以便支撐我們的業(yè)務(wù)需要。比如,要查一條消息為何發(fā)送失敗,通過(guò)userId搜一下,直接完事(實(shí)時(shí)的都記錄在Redis中,所以這里讀取的是Redis的數(shù)據(jù))
比如,通過(guò)模板Id,查某條消息的整體下發(fā)情況:
為什么我說(shuō)這是消息管理平臺(tái)最最最精華的呢?umpId貫穿了所有消息管理平臺(tái)經(jīng)過(guò)的系統(tǒng),只要是在消息管理平臺(tái)發(fā)的消息,都會(huì)被記錄下來(lái)發(fā)送,可以通過(guò)點(diǎn)位來(lái)快速追蹤消息的下發(fā)情況。
總結(jié)一下數(shù)據(jù)統(tǒng)計(jì):
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
設(shè)計(jì)出業(yè)務(wù)上的umpid,給所有的消息推送鏈接都加上umpdId 參數(shù)
打通上下游,共同設(shè)計(jì)和維護(hù)關(guān)鍵點(diǎn)位,統(tǒng)一日志格式來(lái)實(shí)現(xiàn)跨平臺(tái)的收集和清洗
兼顧實(shí)時(shí)和離線需求寫到不同的數(shù)據(jù)源,實(shí)時(shí)以多維度統(tǒng)計(jì)來(lái)快速定位問(wèn)題
聊聊運(yùn)營(yíng)層
面前面提到了,運(yùn)營(yíng)的模板是需要圈選一批人群,然后下發(fā)消息的,那這群人從哪里來(lái)?
在很久之前,消息管理平臺(tái)也把人群給做掉了,大致的思路就是可以支持文件上傳和hivesql上傳兩種方式去圈選人群,圈出來(lái)上傳到hdfs進(jìn)行讀取,支持對(duì)人群的更新/切分/導(dǎo)出等功能。
有了人群的概念,你會(huì)發(fā)現(xiàn)你收到的消息其實(shí)都是跟你息息相關(guān)的(不是瞎給你推送的,你在里面,才能圈到你)??赡苁且?yàn)槟憧戳藥滋斓倪B衣裙,所以給你推送連衣裙的消息,吸引去你購(gòu)買。
后來(lái),由于公司內(nèi)部DMP系統(tǒng)崛起,人群就都交由DMP給管理了。但實(shí)現(xiàn)的思路也都是類似的,只不過(guò)還是同樣的:人家做的是平臺(tái),功能肯定比會(huì)自己寫幾個(gè)接口要完善不少。
做推送就免不了發(fā)錯(cuò)了消息,特別是在運(yùn)營(yíng)側(cè)(分分鐘就推送千萬(wàn)人),我們平臺(tái)又做了什么措施去盡可能避免這種問(wèn)題的發(fā)生呢?
在運(yùn)營(yíng)圈定人群后,我們會(huì)有單獨(dú)的測(cè)試功能去「測(cè)試單個(gè)用戶」是否能正常下發(fā)消息,文案鏈接是否存在問(wèn)題。
這一個(gè)步驟是必須要做的,給用戶發(fā)出的消息,首先要經(jīng)過(guò)自己的校驗(yàn)。如果確認(rèn)鏈接和文案都無(wú)問(wèn)題后,則提交任務(wù),走工單審批后才能發(fā)送。
如果在啟動(dòng)之后發(fā)現(xiàn)文案/鏈接存在問(wèn)題,還可以攔截剩余未發(fā)的消息。
針對(duì)于(技術(shù)方推送),我們?cè)陬A(yù)發(fā)環(huán)境下配置了「白名單」才能收到消息。
線上消息有「去重」的邏輯:
在某段時(shí)間內(nèi),過(guò)濾掉重復(fù)消息
運(yùn)營(yíng)類消息推送(圈定人群的方式去下發(fā)消息)同一個(gè)用戶需要相隔一段時(shí)間才能下發(fā)一次。
雖然說(shuō),我們制定了很多的規(guī)則去盡量避免事故的發(fā)生,但不得不說(shuō)推送還是一個(gè)容易出現(xiàn)事故的功能。
“消息管理平臺(tái)的Java實(shí)現(xiàn)原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。