偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

揭秘!消息管理平臺的實現(xiàn)原理

開發(fā) 架構(gòu)
「消息管理平臺」可能在不同的公司會有不同的叫法,有的時候我會叫它「推送系統(tǒng)」,有的時候我會叫它「消息管理平臺」,也有的同事叫它「觸達平臺」,甚至浮夸點我也可以叫它「消息中臺」。

[[343175]]

本文轉(zhuǎn)載自微信公眾號「Java3y」,作者Java3y。轉(zhuǎn)載本文請聯(lián)系Java3y公眾號。

我,三歪,最近要開始寫項目系列文章。我給這個系列取了一個名字,叫做《揭秘》

沒錯,我又給自己挖了個坑。

為什么想寫項目相關(guān)的文章呢?原因有以下:

  • 當(dāng)我還沒正式開始工作時,我經(jīng)常會想:”網(wǎng)上的視頻項目我是看過了,但真正的商業(yè)項目究竟長什么樣?會不會很難?“我是挺想知道真正的商業(yè)項目跟自己練習(xí)的項目區(qū)別在哪。我估摸還沒工作的同學(xué)應(yīng)該也有跟我類似的思考吧?
  • 變相推動自己持續(xù)輸出,在這個過程中學(xué)習(xí)和成長。關(guān)注我可能有小白,也可能有跟我做同一領(lǐng)域的大佬。我把我所了解的寫下來:可能我這邊的實現(xiàn)方案被大佬們唾棄,交流和學(xué)習(xí)后,改善了我系統(tǒng)的實現(xiàn)方案。也有可能給正準(zhǔn)備踏進該領(lǐng)域的同學(xué)提供一些參考價值。豈不美哉?

這個系列就以「消息管理平臺」來打個樣吧,這是我維護近一年的系統(tǒng)了。這篇文章可以帶你全面認識「消息管理平臺」是怎么設(shè)計和實現(xiàn)的,有興趣的同學(xué)歡迎在評論區(qū)下留言和交流。

這篇文章可能稍微會有些許長,我是打算一篇就把該系統(tǒng)給講清楚?!赶⒐芾砥脚_」原理并不難,沒有很多專業(yè)名詞,實現(xiàn)起來也不會復(fù)雜,你要是覺得學(xué)到了,歡迎給我點個贊👍

簡單認識《消息管理平臺》

「消息管理平臺」可能在不同的公司會有不同的叫法,有的時候我會叫它「推送系統(tǒng)」,有的時候我會叫它「消息管理平臺」,也有的同事叫它「觸達平臺」,甚至浮夸點我也可以叫它「消息中臺」

但是不管怎么樣,它的功能就是給用戶發(fā)消息。在公司里它是怎么樣的定位?只要以官方名義發(fā)送的消息,都走消息管理平臺。

一般你注冊一個APP/網(wǎng)站,你可以收到該APP/網(wǎng)站給你發(fā)什么消息呢?一般就以下吧?

  • 站內(nèi)信(IM)消息:其實就是APP內(nèi)聊天的消息
  • 通知欄(PUSH)消息:系統(tǒng)彈窗消息
  • 郵件(Email)消息
  • 短信(Sms)消息
  • 微信服務(wù)號消息
  • 微信小程序(服務(wù)通知)消息

 

 


 

 

好了,我相信你已經(jīng)知道這個系統(tǒng)是用來干嘛的了。那為什么要有這個系統(tǒng)呢?

為什么要有消息管理平臺?

可以說,只要是做APP的公司幾乎都會有消息管理平臺。

我們很多時候都會想給用戶發(fā)消息:

  • 有可能是用戶想要這樣的功能(預(yù)約活動提醒通知)
  • 也有可能是我們想通過發(fā)消息來「喚醒」/「告知」等操作,告訴用戶我們還在(大爺來玩啊)

那么問題來了,發(fā)消息困難嗎?發(fā)消息復(fù)雜嗎?

顯然,發(fā)消息非常簡單,一點兒也不復(fù)雜。

發(fā)短信無非就是調(diào)用第三方短信的API、發(fā)郵件無非就是調(diào)用郵件的API、發(fā)微信類的消息(手Q/小程序/微信服務(wù)號)無非就是調(diào)用微信的API、發(fā)通知欄消息(Push)無非就是調(diào)APNS/手機廠商的API、發(fā)IM消息也可以使用云服務(wù),調(diào)云服務(wù)的API...

可能很多人的項目都是這么干的,無非發(fā)條消息,自己實現(xiàn)也不是不可以。

但這樣會帶來的問題就是在一個公司內(nèi)部,會有很多個項目都會有「發(fā)送消息」的代碼實現(xiàn)。假設(shè)發(fā)消息出了問題,還得去自己解決。

首先是系統(tǒng)不好維護,其次是沒必要。我一個搞廣告的,雖然我要發(fā)消息,憑什么要我自己去實現(xiàn)?

我們在寫代碼時,可能會把公用的代碼抽成方法,供當(dāng)前的項目重復(fù)調(diào)用。如果該公用的代碼被多個項目使用,可能我們又會抽成組件包,供多個項目使用。只要該公用的代碼被足夠多的人去用,那它就很有可能從組件上升為一個平臺(系統(tǒng))級的東西。

如何實現(xiàn)消息管理平臺?

回到消息管理平臺的本質(zhì),它就是一個可以發(fā)消息的系統(tǒng)。那怎么設(shè)計和實現(xiàn)呢?我們從接口說起吧。

接口設(shè)計

消息管理平臺是一個提供消息發(fā)送服務(wù)的平臺,如果讓我去實現(xiàn),我的想法可能是把每種類型的消息都寫一個接口,然后把這些接口對外暴露。

所以,可能會有以下的接口:

  1. /** 
  2. * content:發(fā)送的文案 
  3. * receiver:接收者 
  4. */ 
  5.  
  6. sendSms(String content,String receiver); 
  7. sendIm(String content,String receiver); 
  8. sendPush(String content,String receiver); 
  9. sendEmail(String content,String receiver); 
  10. sendTencent(String content,String receiver); 
  11. //.... 

這樣實現(xiàn)好像也不是不可以,反正每個接口都挺清晰的,要發(fā)什么類型的消息,你調(diào)用哪個接口就好了。

假設(shè)我們定義了如上的接口,現(xiàn)在我們要發(fā)消息了,我們會有以下的場景:

a.文案:「你好,我是三歪」,接收人:「woshisanwai」 (一次只發(fā)給一個人)

b.文案:「你好,我是三歪」,接收人:「woshisanwai,java3y,javayyy」(相同的文案發(fā)給多個人)

假如你是新手,你可能會想:這簡單,我每種類型分開兩個接口,分別是單發(fā)和批量接口。

  1. sendSingleSms(); 
  2. sendBatchSms(); 
  3. //... 

上面這樣設(shè)計有必要嗎?其實沒啥必要。我將接收人定義為一個Array不就得了?Array的size==1,那我就把該文案發(fā)給這個人,Array的size>1,那我就把這個文案發(fā)給Array里邊的所有人。

所以我們的接口還是只有一個:

  1. /** 
  2. * content:發(fā)送的文案 
  3. * receiver:接收者(可多個,可單個) 
  4. */ 
  5. sendSms(String content,Set<String> receiver); 

其實在我們這也不是定義Array,我的接口receiver仍然是String,如果有多個用,號分隔就可以了。

  1. /** 
  2. * content:發(fā)送的文案 
  3. * receiver:接收者(可多個,可單個),多個用逗號分隔開 
  4. */ 
  5. sendSms(String content,String receiver); 

現(xiàn)在還有個場景,不同的文案發(fā)給不同的人怎么辦?有的人就說,這不已經(jīng)實現(xiàn)了嗎?直接調(diào)用上面的接口就完事了啊。你又不是不能重復(fù)調(diào)用,比如說:

a.文案:「你好,我是Java3y」,接收人:「woshisanwai」

b.文案:「你好,我是三歪」,接收人:「3y」

c.文案:「你好,woshisanwai」,接收人:「三歪」

d......

確實如此,本來就可以這樣做的。但不夠好

舉個真實的場景:現(xiàn)在有一個主播開播了,得發(fā)送一條消息告訴訂閱該主播的人趕緊去看。為了提高該條通知的效果 ,在文案上我們是這樣設(shè)計的:{用戶昵稱},你訂閱的主播三歪已經(jīng)開播了,趕緊去看吧!

這種消息我們肯定是要求實時性的(假設(shè)推送消息的速度太慢了,等到用戶收到消息了,主播都下播了,那用戶不得錘死你?)

畫外音:顯然這種情況屬于不同的文案發(fā)給不同的人

這種消息在業(yè)務(wù)層是怎么做的呢?可能是掃DB表,遍歷出訂閱該主播的粉絲,然后給他們推送消息。

那現(xiàn)在我們只能每掃出一個訂閱該主播的粉絲,就得調(diào)用send()接口發(fā)送消息。如果該主播有500W的粉絲,那就得調(diào)用500W次send接口,這不是很可怕?這調(diào)用次數(shù),這網(wǎng)絡(luò)開銷...

于是乎,我們得提供一個“批量”接口,可以讓調(diào)用方一次傳入不同文案所攜帶不同的人。那怎么做呢?也很簡單,實際上就是上面接口再封裝一層,讓調(diào)用方能“批量”傳進來就好了。所以代碼可以是這樣的:

  1. /** 
  2. * 一次傳入多個(文案以及發(fā)送者)的“組”進來 
  3. * List<SendParam> 
  4. * SendParam 里邊 定義了 content 和receiver 
  5. */ 
  6. sendBatchSms(List<SendParam> sendParam); 

現(xiàn)在接口的“雛形”已經(jīng)出現(xiàn)了,到這里我們實現(xiàn)了消息管理平臺最基本的功能:發(fā)消息

我們先不管內(nèi)部的實現(xiàn)是如何,假設(shè)我們已經(jīng)適配好調(diào)通好對應(yīng)的API了,現(xiàn)在我們的接口在發(fā)消息層面上已經(jīng)有充分必要的條件了:只要你傳入接收者和發(fā)送內(nèi)容,我就可以給你發(fā)消息。

但我們對外稱可是一個平臺啊,怎么能搞得像是只封裝了幾個方法似的,平臺就該有平臺的樣子。

我舉個日常最最最基本的功能:有人調(diào)用了我的接口發(fā)了條短信,這條短信的文案是一條內(nèi)容為驗證碼類型,他問我這條短信到底下發(fā)到用戶手上了沒有。

 

如果接入過短信的同學(xué)就會知道:發(fā)送短信到用戶收到是一個異步的過程

  • 調(diào)用短信提供商的API,假設(shè)你的入?yún)]有問題,它會告訴你“調(diào)用”成功。你想真正地知道此條內(nèi)容到底有沒有下發(fā)到用戶手上,你有兩種辦法:一、提供一個接口給短信服務(wù)商調(diào)用,等真正處理完了,短信服務(wù)商會調(diào)用你的接口,告訴你最終的結(jié)果是什么。二、你去輪詢短信服務(wù)商的接口,獲取最終的結(jié)果。

 

回到問題上,他想要他調(diào)用我的接口有沒有把短信發(fā)送成功,那我只要問他拿到手機號和文案,然后有以下步驟:

a.判斷該手機號和文案在下發(fā)時是否正常(有沒有真正調(diào)用下發(fā)短信的接口)

b.假設(shè)調(diào)用短信接口下發(fā)成功,那看下返回的回執(zhí)(下發(fā)結(jié)果)是否正常

那目前我們在現(xiàn)有的接口,還是很完美地支持上面的問題的,對吧?只要我們記錄了下發(fā)的結(jié)果和回執(zhí)的信息,我們就可以告訴他所提供的手機號和文案究竟有沒有下發(fā)到用戶手上。

那今天他又過來問了:今天有很多人來反饋收不到驗證碼短信(不是全部人收不到,是大部分人),我想了解一下今天驗證碼短信下發(fā)的成功率是多少。

此時的我,只能去匹配(like %%)他的文案調(diào)用我的接口下發(fā)了多少人,調(diào)用短信服務(wù)商的API下發(fā)成功多少人,收到的成功回執(zhí)(結(jié)果)有多少人。

通過匹配文案的方式最終也是可以告訴他結(jié)果的,但是這種是很傻X的做法。歸根到底還是因為系統(tǒng)提供的服務(wù)還是太薄弱了。

那怎么解決上面所講的問題呢?其實也很簡單,既然匹配文案很傻X,那我給他這一批驗證碼的短信取個唯一的Id那不就可以了嗎?

像我們?nèi)ソ尤攵绦欧?wù)商一樣,我們需要去新建一個短信模板,這個模板代表了你要發(fā)送的內(nèi)容,新建模板后會給你個模板Id,你下發(fā)的時候指定這個模板Id就好了。

 

那我們的平臺也可以這樣玩啊,你想發(fā)消息對吧?可以,先來我的平臺新建一個”模板“,到時候把模板Id發(fā)給我就行。

于是,我們就完美地解決上面所提到的問題了。

我們現(xiàn)在再來討論一下有沒有必要不同的消息類型(短信、郵件、IM等)需要分開不同的的接口,其實是沒必要的了。因為只要抽象了”模板“這個概念,消息類型自然我們就可以在模板上固化掉,只要傳了模板Id,我就知道你發(fā)的是什么類型消息。

這樣一來,我們最終會有兩個接口:批量與單個發(fā)送接口。

  1. /** 
  2.  * 發(fā)送消息接口 
  3.  * @author java3y 
  4.  */ 
  5. public interface SendService { 
  6.  
  7.     /** 
  8.      * 相同文案,發(fā)給0~N 人 
  9.      * @param sendParam 
  10.      */ 
  11.     void send(SendParam sendParam); 
  12.  
  13.     /** 
  14.      * 不同文案,發(fā)給不同人,一次可接收多組 
  15.      * @param sendParam 
  16.      */ 
  17.     void batchSend(BatchSendParam sendParam); 
  18.  
  19. public class SendParam { 
  20.  
  21.     /** 
  22.      * 模板Id 
  23.      */ 
  24.     private String templateId; 
  25.  
  26.     /** 
  27.      * 消息參數(shù) 
  28.      */ 
  29.     private MsgParam msgParam; 
  30.  
  31. public class MsgParam { 
  32.  
  33.     /** 
  34.      * 接收者:假設(shè)有多個,則用「,」分隔開 
  35.      */ 
  36.     private String receiver; 
  37.  
  38.     /** 
  39.      * 自定義參數(shù)(文案) 
  40.      */ 
  41.     private Map<String, String> variables; 
  42.  

單個接口指的是:一次給1~N人發(fā)送消息,這批人收到的是相同的文案

批量接口指的是:一次給1個人發(fā)送一個文案,但一次調(diào)用可以傳N個人及對應(yīng)的文案

這里的單個和批量不是以發(fā)送人的維度去定義的,而是人所對應(yīng)的消息文案。

再再再舉個例子,現(xiàn)在我給關(guān)注我的同學(xué)都發(fā)一條消息:「大哥大嫂新年好」,這種情況我只需要使用send方法就好了,相同的文案我給一批人發(fā),這批人收到的文案是一模一樣的。

一次單推接口調(diào)用的請求參數(shù):

  1.     "templateId": 12345, 
  2.     "msgParam":  
  3.         {  
  4.             "receivers""三歪,敖丙,雞蛋,米豆"
  5.             "variables": { 
  6.                 "content""大哥大哥新年好"
  7.                 "title""來個贊吧,親" 
  8.             } 
  9.         } 

如果我要給關(guān)注我的同學(xué)都發(fā)一條消息:「{微信用戶名},大哥大哥新年好」,這種情況我一般用batchSend方法,在發(fā)送之前組合人所對應(yīng)的文案封裝成一個List,一次調(diào)用接口對調(diào)用方而言就是一次發(fā)了List.size()組人。

一次批量接口調(diào)用的請求參數(shù):

  1.     "templateId": 12345, 
  2.     "msgParam": [ 
  3.         {  
  4.             "receivers""敖丙"
  5.             "variables": { 
  6.                 "content""敖丙,大哥大哥新年好"
  7.                 "title""來個贊吧,親" 
  8.             } 
  9.         }, 
  10.         { 
  11.             "receivers""雞蛋"
  12.             "variables": { 
  13.                 "content""雞蛋,大哥大哥新年好"
  14.                 "title""來個贊吧,親" 
  15.             } 
  16.         } 
  17.     ] 

沒想到單單接口這塊我這篇就寫了這么長,主要是照顧沒有經(jīng)驗的同學(xué)哈~

回顧設(shè)計接口的思路:

a.起初是想每種消息類型分開不同的接口

b.考慮到同一個文案會下發(fā)給多個人,所以接收者參數(shù)得是支持”批量“的傳入

c.考慮到會有批量調(diào)用接口的場景,所以需要一個批量接口

d.考慮到需要統(tǒng)計下發(fā)消息的場景,所以需要抽象出”模板“,在平臺下發(fā)的消息都得有”模板“

e.有了”模板“,可以將很多信息固化到模板中,所以最終我們抽象出兩個接口:單推和批量。

再來聊聊模板

在前面我們已經(jīng)定義好接口了,跟簡單你們所實現(xiàn)的發(fā)消息功能最主要的區(qū)別就是多了”模板“的概念。

在上面提到了一點:有了”模板“,可以將很多信息固化到模板中。那我們固化了什么東西到模板中呢?

  • 能夠發(fā)送的消息種類。消息管理平臺是可以發(fā)多種類型的消息的,所以我們模板是需要有字段區(qū)分不同的消息類型。別想得這么難,其實我們就用1表示短信,2表示郵件...
  • 模板創(chuàng)建者信息(手機號、姓名),這個跟發(fā)消息的實質(zhì)內(nèi)容沒有任何關(guān)系,只是如果模板出現(xiàn)了什么不可描述的問題,背鍋俠總得找出來吧,如果模板創(chuàng)建者離職了怎么辦?沒事,我會根據(jù)創(chuàng)建者把所在部門給找到,那就找部門背鍋(嘿嘿)
  • 消息的文案。綜合上面所看到的消息,我們可以看到一條消息無非由以下部分所組成:內(nèi)容、標(biāo)題、圖片、鏈接、視頻...不同的消息能發(fā)的文案也不一樣,像短信頂多就只有內(nèi)容和鏈接,而像通知欄消息(Push)就可以有標(biāo)題、內(nèi)容、圖片、鏈接所組成。所以,我們會把消息的文案用json的格式存儲在一個字段中。
  • 消息的業(yè)務(wù)規(guī)則。這里所講的業(yè)務(wù)規(guī)則并不是真正的細節(jié)業(yè)務(wù),而是對不同消息類型上的平臺性約束。比如說,在產(chǎn)品層面上,希望晚上用戶收不到通知欄推送(畢竟會對用戶進行打擾);希望用戶一個小時內(nèi)不會接收到兩條,一天最多收到N條通知欄推送(也是出于用戶的體驗)。這些平臺性的約束就適合放在消息管理平臺上做,你可以理解為是一個兜底的功能。
  • 發(fā)送賬號。什么?發(fā)條消息還有賬號的概念?你搞錯了吧,三歪?。其實是真的有的,在發(fā)郵件的時候可以選取不同的郵件賬號,在發(fā)微信公眾號消息時可以選取不同的微信公眾號(小程序同理),在發(fā)IM消息時可以使用不同的賬號發(fā)送。而在接入短信的時候其實是分了兩種類型的:通知和營銷。我們會把這些都抽象為賬號。
  • 接收者Id類型。站內(nèi)的IM消息用的是站內(nèi)的userId,發(fā)通知欄消息(PUSH)用的是did,發(fā)短信用的是手機號,發(fā)微信類的消息用的是openId。指定接收者的Id類型,表明這個模板你要傳入哪種類型的id。假設(shè)你指明是userId,但你要發(fā)短信,消息管理平臺就需要將userId轉(zhuǎn)成手機號。這里也是用一個字段標(biāo)識,1表示userId,2表示did ...

可以發(fā)現(xiàn)的是,我們把一條消息所需要的信息(甚至不需要的信息)都塞進模板里面了,等調(diào)用方傳入模板Id時,我就能拿到我想要的所有信息了。

這是一個模板的全部了嗎?當(dāng)然不是咯。上面提到的是模板共性的內(nèi)容,我們按模板的使用場景還劃分兩種類型:

  • 運營模板:運營要給指定一批人在某時某刻發(fā)送消息。(這一批人是T+1離線的)。例子:如果用戶注冊登錄了APP,可以隔一天(甚至更長時間)給用戶發(fā)消息。這種屬于非實時(離線)推送,這種就不需要技術(shù)來承接,去圈選人群后設(shè)置對應(yīng)的時間即可推送。
  • 技術(shù)模板:系統(tǒng)根據(jù)業(yè)務(wù)條件自動觸發(fā)一批消息,接收者名單也依賴業(yè)務(wù)場景(這批人一般是實時的)。例子:如果用戶注冊登錄了APP,就立馬需要給該用戶發(fā)消息。這種屬于實時推送,需要對應(yīng)的技術(shù)來承接。

隨著系統(tǒng)和業(yè)務(wù)的演進,運營模板和技術(shù)模板的界限會越來越模糊。從本質(zhì)上就是提供了兩種發(fā)消息的方式:

a.圈定一批人群,通過使用定時任務(wù)到點調(diào)用接口觸發(fā)(接收者、文案、發(fā)送時間都已明確)。

b.技術(shù)調(diào)用接口發(fā)送消息(接收者,文案,發(fā)送時間均由業(yè)務(wù)邏輯所產(chǎn)生)。例子:歡迎關(guān)注三歪,你的驗證碼是:888。有內(nèi)鬼,終止交易。(當(dāng)你關(guān)注三歪時,系統(tǒng)觸發(fā)一條消息。發(fā)送時間、驗證碼值、人員均不確定)

用戶在平臺創(chuàng)建模板時,不同類型的模板需要填寫的字段是不一樣的:運營模板需要填寫人群和任務(wù)觸發(fā)時間,而技術(shù)模板壓根就不需要填人群和任務(wù)觸發(fā)時間,所以我們模板會有一個字段標(biāo)識該模板是運營類型還是技術(shù)類型。1表示運營類型,2表示技術(shù)類型...

你覺得已經(jīng)完了嗎?nonono,還沒有。我們還會區(qū)分消息的類型,目前最主要由三類組成:通知、營銷和驗證碼。

問題來了,為什么我們要區(qū)分消息的類型呢?做統(tǒng)計用嗎?當(dāng)然不是了,就這幾個粒度的類型有什么好統(tǒng)計的。

還是以例子來說明吧:在2020-02-30日,運營同學(xué)圈選了一個5000W的人群選擇在晚上8點發(fā)送一條短信,大致的情況就是告訴用戶三歪文章更新了,不看血虧。系統(tǒng)在晚上8點準(zhǔn)時執(zhí)行任務(wù),讀取該模板的模板信息下發(fā)。5000W人,系統(tǒng)能秒發(fā)嗎?顯然是不行的

畫外音:除了考慮自身的系統(tǒng)能力,還得考慮下游能承受的能力。你瞎搞,人家就不帶你玩了。

所以,這5000W人肯定是需要一定的時間才能完全下發(fā)的,現(xiàn)在我們假設(shè)是15分鐘完全下發(fā)完畢吧。在8點2分觸發(fā)了一條驗證碼的短信,結(jié)果因為這個5000W的人群所導(dǎo)致驗證碼的消息延遲發(fā)送,這合理嗎?顯然不合理。

怎么導(dǎo)致的?原因是這5000W的消息和驗證碼的消息走的是同一個通道,導(dǎo)致驗證碼的消息被阻塞掉了。我們將不同的消息類型走不同的通道,就可以解決掉上面的問題。

所以,我們的系統(tǒng)在設(shè)計層面上就把運營模板默認設(shè)置為營銷類型的消息,而技術(shù)模板的消息類型由調(diào)用者自行選擇。在現(xiàn)實場景中,能堵的就只有營銷類的消息。

 

畫外音:上面所講的這些實踐都是跟使用場景和具體業(yè)務(wù)所關(guān)聯(lián)的,肯定不是一朝一夕就可以全想出來的。

  • 模板也已經(jīng)聊完了,還有些細節(jié)的東西我這就不贅述了。我再來簡要總結(jié)一下:
  • 我們把發(fā)送一條消息所必要的信息(文案、發(fā)送賬號、傳入的接收者Id類型、消息類型:通知、營銷和驗證碼)、平臺性的信息(業(yè)務(wù)規(guī)則:是否去重、屏蔽、展示邏輯等)和基本信息(業(yè)務(wù)方信息、消息名稱)全都塞到模板中

由于使用場景,模板會分為運營模板和技術(shù)模板。運營模板主要的特點是需要填寫人群信息和發(fā)送時間,運營模板由消息管理平臺自身進行調(diào)度發(fā)送消息。

接口實現(xiàn)

BB了這么久了,可能很多人只是想來看看:三歪這逼在標(biāo)題還敢還寫個揭秘,發(fā)消息誰不會,不就調(diào)個API嘛,還能給你玩出花來?

別急嘛,現(xiàn)在就寫。前面已經(jīng)鋪墊了接口的設(shè)計和模板究竟是什么了,現(xiàn)在我們還是回到接口的實現(xiàn)上吧。

首先我們簡單來看看消息管理平臺的系統(tǒng)架構(gòu)鏈路圖:

 

畫外音:上面我們所說的接口定義在統(tǒng)一調(diào)用層(接入層)中

調(diào)用者調(diào)用我們的send/batchSend方法,會直接調(diào)用下游的API下發(fā)消息嗎?不會

直接調(diào)用下游的API下發(fā)消息風(fēng)險太大了,接口1W+QPS都是很正常的事,所以我們接收到消息后只是做簡單的參數(shù)校驗處理和信息補全就把消息發(fā)到消息隊列上。這樣做的好處就是接口接入層十分輕量級,只要Kafka抗得住,請求就沒問題。

 

發(fā)到消息隊列時,會根據(jù)不同的消息類型發(fā)到不同的topic上,發(fā)送層監(jiān)聽topic進行消費就好了。架構(gòu)大致如下:

 

發(fā)送層消費topic后,會把消息放在各自的內(nèi)存隊列上,多個線程消費內(nèi)存隊列的消息來實現(xiàn)消息的下發(fā)。

可以看到的是:從接入層發(fā)到消息隊列上我們就已經(jīng)做了分topic來實現(xiàn)業(yè)務(wù)上的隔離,在消費時我們也是放到各自的內(nèi)存隊列中來進行消費。這就實現(xiàn)了:不同渠道和同渠道的不同類型的消息都互不干擾。

看到上面這張圖,如果思考過的同學(xué)肯定會問:這要內(nèi)存隊列干啥啊?反正你在上層已經(jīng)分了topic了,不用內(nèi)存隊列也可以實現(xiàn)你所講的“業(yè)務(wù)隔離”啊。

也的確,這里使用內(nèi)存隊列的主要原因是為了提高并發(fā)度。提高了并發(fā)度,這意味著下發(fā)速度可以更快(在下發(fā)消息的過程中,最耗時的還是網(wǎng)絡(luò)交互,像短信這種可以多開點線程進行消費)。

在前面所提到的業(yè)務(wù)規(guī)則就是在下發(fā)層這兒做的,包括夜間屏蔽、1小時去重和Id轉(zhuǎn)換等

  • 夜間屏蔽就是判斷是否在晚上,如果勾選了夜間屏蔽并且在晚上,過濾掉就好了
  • 1小時去重就是拿userId+消息渠道作為Key,看是否存在Redis上,假設(shè)存在,則過濾掉
  • id轉(zhuǎn)換這功能我們做成了個系統(tǒng),這塊我放在下面簡單說一下吧,這就不在贅述了。

畫外音:這種場景最好使用Pipeline來讀寫Redis

隨后就是適配各個渠道的接口,調(diào)用API下發(fā)消息了,這塊就跟你們單個的實現(xiàn)沒什么大的區(qū)別了,調(diào)用個接口還能給你玩出花來?(代碼風(fēng)格會稍好一些,模板方法模式、責(zé)任鏈、生產(chǎn)者與消費者模式等在項目中都有對應(yīng)的應(yīng)用)

總結(jié)一下接口的實現(xiàn):

a.調(diào)用方調(diào)用接口時,接口不會同步直接調(diào)用下游的API發(fā)送消息,而是放入消息隊列上(支持高并發(fā))

b.放入隊列時,會根據(jù)不同渠道以及不同類型的消息進行分類,放到不同的topic(業(yè)務(wù)隔離)

c.消費隊列時,會在本地使用阻塞隊列來提高并發(fā)度(加快消費的速度)

Id轉(zhuǎn)換

(擴展)在前面也提到了,發(fā)不同類型的消息會需要有不同的id類型:微信類需要openId、短信需要手機號、push通知欄推送需要did。

在大多數(shù)情況下,一般調(diào)用者就傳入userId給到我,我這邊需要根據(jù)不同的消息類型對userId進行轉(zhuǎn)換。

那在我們這邊是怎么實現(xiàn)該系統(tǒng)的呢?主要的步驟和邏輯有以下:

a.監(jiān)聽用戶變更和微信公眾號訂閱/取關(guān)的topic,在Flink清洗出一個統(tǒng)一的數(shù)據(jù)模型,將清洗后的數(shù)據(jù)寫到另一個的topic。

b.Id映射系統(tǒng)監(jiān)聽Flink清洗出的topic,實時寫到數(shù)據(jù)源(這里我們用的是搜索引擎)

看著也不會很難,對吧?

有沒有想過一個問題,為什么要用一個Id映射系統(tǒng)去監(jiān)聽Flink洗出來的topic,而不是在Flink直接寫到數(shù)據(jù)源呢?

其實通過Flink直接寫到數(shù)據(jù)源也是完全沒問題的,而封裝了一個Id映射系統(tǒng),就可以把這活做得更細致。

從描述可以發(fā)現(xiàn)的是:在上面只實現(xiàn)了實時增量。很多時候我們會擔(dān)心增量存在問題,導(dǎo)致部分數(shù)據(jù)的不準(zhǔn)確或者丟失,都會寫一份全量,Id映射也是同樣的。

那Id映射的全量是怎么做的呢?用戶數(shù)據(jù)通過各種關(guān)聯(lián)關(guān)系會在Hive形成一張表,而Id映射的全量就是基于這張Hive表來實現(xiàn)全量(每天凌晨會讀取Hive表的信息,再寫一遍數(shù)據(jù)源)。

基于上面這些邏輯,專門給Id映射做了個后臺管理(可以手動觸發(fā)全量、是否開啟增量/全量、修改全量觸發(fā)的時間)

 

數(shù)據(jù)統(tǒng)計

我覺得這塊是消息管理平臺最最最精華的一部分。

夢回我們當(dāng)初的接口設(shè)計環(huán)節(jié),我們就是因為有“數(shù)據(jù)統(tǒng)計”的需求,才引入了模板的概念?,F(xiàn)在我們已經(jīng)有了一個模板Id了,在我們這邊是怎么實現(xiàn)數(shù)據(jù)的統(tǒng)計的呢?我們對消息的統(tǒng)計都是基于模板的維度來實現(xiàn)的。

在創(chuàng)建模板時就會有一個模板Id生成,基于這個模板Id,我們生成了一個叫做umpId的值:第一位分為技術(shù)/運營推送,最后八位是日期,中間六位是模板Id

 

因為所有的消息都會經(jīng)過接入層,只要消息帶有鏈接,我們就會給鏈接后加上umpid參數(shù),鏈接會一直下發(fā)透傳,直至用戶點擊

 

每個系統(tǒng)在執(zhí)行消息的時候都會可能導(dǎo)致這條消息發(fā)不出去(可能是消息去重了,可能是用戶的手機號不正確,可能是用戶太久沒有登錄了等等都有可能)。我們在這些『關(guān)鍵位置』都打上日志,方便我們?nèi)ヅ挪椤?/p>

這些「關(guān)鍵位置」我們都給它用簡單的數(shù)字來命個名。比如說:我們用「11」來代表這個用戶沒有綁定手機號,用「12」來代表這個用戶10分鐘前收到了一條一模一樣的消息,用「13」來代表這個用戶屏蔽了消息.....

「11」「12」「13」「14」「15」「16」這些就叫做「點位」,把這些點位在關(guān)鍵的位置中打上日志,這個就叫做「埋點」

有了埋點,我們要做的就是將這些點位收集起來,然后統(tǒng)一處理成我們的數(shù)據(jù)格式,輸出到數(shù)據(jù)源中。

a.收集日志

b.清洗日志

c.輸出到數(shù)據(jù)源

有l(wèi)ogAgent幫我們收集日志到Kafka,實時清洗日志我們用的是Flink,清洗完我們輸出到Redis(實時)/Hive(離線)。

Hive表的數(shù)據(jù)樣例(主要用于離線報表統(tǒng)計):

 

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

 

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

 

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

 

總結(jié)一下數(shù)據(jù)統(tǒng)計:

a.設(shè)計出業(yè)務(wù)上的umpid,給所有的消息推送鏈接都加上umpdId 參數(shù)

b.打通上下游,共同設(shè)計和維護關(guān)鍵點位,統(tǒng)一日志格式來實現(xiàn)跨平臺的收集和清洗

c.兼顧實時和離線需求寫到不同的數(shù)據(jù)源,實時以多維度統(tǒng)計來快速定位問題

聊聊運營層

面前面提到了,運營的模板是需要圈選一批人群,然后下發(fā)消息的,那這群人從哪里來?

在很久之前,消息管理平臺也把人群給做掉了,大致的思路就是可以支持文件上傳和hivesql上傳兩種方式去圈選人群,圈出來上傳到hdfs進行讀取,支持對人群的更新/切分/導(dǎo)出等功能。

有了人群的概念,你會發(fā)現(xiàn)你收到的消息其實都是跟你息息相關(guān)的(不是瞎給你推送的,你在里面,才能圈到你)??赡苁且驗槟憧戳藥滋斓倪B衣裙,所以給你推送連衣裙的消息,吸引去你購買。

后來,由于公司內(nèi)部DMP系統(tǒng)崛起,人群就都交由DMP給管理了。但實現(xiàn)的思路也都是類似的,只不過還是同樣的:人家做的是平臺,功能肯定比會自己寫幾個接口要完善不少。

做推送就免不了發(fā)錯了消息,特別是在運營側(cè)(分分鐘就推送千萬人),我們平臺又做了什么措施去盡可能避免這種問題的發(fā)生呢?

在運營圈定人群后,我們會有單獨的測試功能去「測試單個用戶」是否能正常下發(fā)消息,文案鏈接是否存在問題。

這一個步驟是必須要做的,給用戶發(fā)出的消息,首先要經(jīng)過自己的校驗。如果確認鏈接和文案都無問題后,則提交任務(wù),走工單審批后才能發(fā)送。

 

如果在啟動之后發(fā)現(xiàn)文案/鏈接存在問題,還可以攔截剩余未發(fā)的消息。

 

 

針對于(技術(shù)方推送),我們在預(yù)發(fā)環(huán)境下配置了「白名單」才能收到消息。

  • 線上消息有「去重」的邏輯:
  • 在某段時間內(nèi),過濾掉重復(fù)消息

運營類消息推送(圈定人群的方式去下發(fā)消息)同一個用戶需要相隔一段時間才能下發(fā)一次。

 

雖然說,我們制定了很多的規(guī)則去盡量避免事故的發(fā)生,但不得不說推送還是一個容易出現(xiàn)事故的功能。我的牛逼已經(jīng)吹完了,如果某天發(fā)現(xiàn)我的推送出了事故,不要@我,當(dāng)沒見過這篇文章就好。

總結(jié)

不知道大家看完之后覺得消息管理平臺難不難,從理解上的角度而言,這系統(tǒng)應(yīng)該是很好理解的,沒有摻雜很多業(yè)務(wù)的東西,都是做平臺性相關(guān)的內(nèi)容。

這個系統(tǒng)能支持數(shù)W的QPS,每天億級的流量推送,一篇文章也不可能把消息管理平臺的所有功能點都講完,內(nèi)容也遠不止上面這些,但核心我應(yīng)該是講清楚的了。

 

 

發(fā)送消息可以做得很簡單,也可以做得很平臺化,如果你覺得你學(xué)到了些許東西,希望可以給我點個在看和轉(zhuǎn)發(fā)一波。如果你對我寫的內(nèi)容有疑問,歡迎評論區(qū)交流。

 

責(zé)任編輯:武曉燕 來源: Java3y
相關(guān)推薦

2021-07-19 09:25:19

數(shù)據(jù)庫MySQL技術(shù)

2022-04-08 08:32:40

mobx狀態(tài)管理庫redux

2025-06-12 05:00:00

@Autowired自動裝配實現(xiàn)機制

2024-10-10 17:46:06

2021-05-13 23:30:17

JavaScript 原理揭秘

2020-02-25 16:48:35

AndroidGoogle 移動系統(tǒng)

2013-05-17 15:34:45

2023-01-27 19:33:10

消息中心管理平臺

2010-07-12 22:19:27

UDP服務(wù)

2022-11-18 18:36:24

2019-11-15 15:12:19

Windows激活KMS

2022-06-24 15:18:48

字節(jié)跳動數(shù)據(jù)庫ClickHouse

2010-09-17 15:25:03

JAVAJVM

2021-12-12 21:01:12

CSS 技巧PurgeCss

2012-09-24 10:20:24

草根應(yīng)用平臺數(shù)據(jù)

2020-12-28 09:50:50

Python內(nèi)存管理語言

2010-02-04 17:29:35

Android OS平

2025-01-14 10:09:43

硬中斷Linux系統(tǒng)

2021-03-08 08:48:02

應(yīng)用場景項目

2009-12-04 18:09:56

點贊
收藏

51CTO技術(shù)棧公眾號