別寫秒殺系統了,我告訴你訊息管理平臺實現原理吧

2020-09-23 11:00:42

前言

文字已收錄至我的GitHubhttps://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);
//....

這樣實現好像也不是不可以,反正每個介面都挺清晰的,要發什麼型別的訊息,你呼叫哪個介面就好了。

假設我們定義瞭如上的介面,現在我們要發訊息了,我們會有以下的場景:

  1. 文案:「你好,我是三歪」,接收人:「woshisanwai」 (一次只發給一個人
  2. 文案:「你好,我是三歪」,接收人:「woshisanwai,java3y,javayyy」(相同的文案發給多個人

假如你是新手,你可能會想:這簡單,我每種型別分開兩個介面,分別是單發和批次介面。

sendSingleSms();
sendBatchSms();
//...

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

所以我們的介面還是隻有一個:

/**
* content:傳送的文案
* receiver:接收者(可多個,可單個)
*/
sendSms(String content,Set<String> receiver);

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

/**
* content:傳送的文案
* receiver:接收者(可多個,可單個),多個用逗號分隔開
*/
sendSms(String content,String receiver);

現在還有個場景,不同的文案發給不同的人怎麼辦?有的人就說,這不已經實現了嗎?直接呼叫上面的介面就完事了啊。你又不是不能重複呼叫,比如說:

  1. 文案:「你好,我是Java3y」,接收人:「woshisanwai」
  2. 文案:「你好,我是三歪」,接收人:「3y」
  3. 文案:「你好,woshisanwai」,接收人:「三歪」

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

舉個真實的場景:現在有一個主播開播了,得傳送一條訊息告訴訂閱該主播的人趕緊去看。為了提高該條通知的效果 ,在文案上我們是這樣設計的:{使用者暱稱},你訂閱的主播三歪已經開播了,趕緊去看吧!

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

畫外音:顯然這種情況屬於不同的文案發給不同的人

這種訊息在業務層是怎麼做的呢?可能是掃DB表,遍歷出訂閱該主播的粉絲,然後給他們推播訊息。

那現在我們只能每掃出一個訂閱該主播的粉絲,就得呼叫send()介面傳送訊息。如果該主播有500W的粉絲,那就得呼叫500Wsend介面,這不是很可怕?這呼叫次數,這網路開銷…

於是乎,我們得提供一個「批次」介面,可以讓呼叫方一次傳入不同文案所攜帶不同的人。那怎麼做呢?也很簡單,實際上就是上面介面再封裝一層,讓呼叫方能「批次」傳進來就好了。所以程式碼可以是這樣的:

/**
* 一次傳入多個(文案以及傳送者)的「組」進來
* List<SendParam>
* SendParam 裡邊 定義了 content 和receiver
*/
sendBatchSms(List<SendParam> sendParam);

現在介面的「雛形」已經出現了,到這裡我們實現了訊息管理平臺最基本的功能:發訊息

我們先不管內部的實現是如何,假設我們已經適配好調通好對應的API了,現在我們的介面在發訊息層面上已經有充分必要的條件了:只要你傳入接收者和傳送內容,我就可以給你發訊息。

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

我舉個日常最最最基本的功能:有人呼叫了我的介面發了條簡訊,這條簡訊的文案是一條內容為驗證碼型別,他問我這條簡訊到底下發到使用者手上了沒有。

如果接入過簡訊的同學就會知道:傳送簡訊到使用者收到是一個非同步的過程

  • 呼叫簡訊提供商的API,假設你的入參沒有問題,它會告訴你「呼叫」成功。你想真正地知道此條內容到底有沒有下發到使用者手上,你有兩種辦法:一、提供一個介面給簡訊服務商呼叫,等真正處理完了,簡訊服務商會呼叫你的介面,告訴你最終的結果是什麼。二、你去輪詢簡訊服務商的介面,獲取最終的結果。

回到問題上,他想要他呼叫我的介面有沒有把簡訊傳送成功,那我只要問他拿到手機號和文案,然後有以下步驟:

  1. 判斷該手機號和文案在下發時是否正常(有沒有真正呼叫下傳簡訊的介面)
  2. 假設呼叫簡訊介面下發成功,那看下返回的回執(下發結果)是否正常

那目前我們在現有的介面,還是很完美地支援上面的問題的,對吧?只要我們記錄了下發的結果和回執的資訊,我們就可以告訴他所提供的手機號和文案究竟有沒有下發到使用者手上。

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

此時的我,只能去匹配(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. 考慮到同一個文案會下發給多個人,所以接收者引數得是支援」批次「的傳入
  3. 考慮到會有批次呼叫介面的場景,所以需要一個批次介面
  4. 考慮到需要統計下發訊息的場景,所以需要抽象出」模板「,在平臺下發的訊息都得有」模板「
  5. 有了」模板「,可以將很多資訊固化到模板中,所以最終我們抽象出兩個介面:單推和批次。

再來聊聊模板

在前面我們已經定義好介面了,跟簡單你們所實現的發訊息功能最主要的區別就是多了」模板「的概念。

在上面提到了一點:有了」模板「,可以將很多資訊固化到模板中。那我們固化了什麼東西到模板中呢?

  • 能夠傳送的訊息種類。訊息管理平臺是可以發多種型別的訊息的,所以我們模板是需要有欄位區分不同的訊息型別。別想得這麼難,其實我們就用1表示簡訊,2表示郵件…
  • 模板建立者資訊(手機號、姓名),這個跟發訊息的實質內容沒有任何關係,只是如果模板出現了什麼不可描述的問題,背鍋俠總得找出來吧,如果模板建立者離職了怎麼辦?沒事,我會根據建立者把所在部門給找到,那就找部門背鍋(嘿嘿)
  • 訊息的文案。綜合上面所看到的訊息,我們可以看到一條訊息無非由以下部分所組成:內容、標題、圖片、連結、視訊…不同的訊息能發的文案也不一樣,像簡訊頂多就只有內容和連結,而像通知欄訊息(Push)就可以有標題、內容、圖片、連結所組成。所以,我們會把訊息的文案用json的格式儲存在一個欄位中。
  • 訊息的業務規則。這裡所講的業務規則並不是真正的細節業務,而是對不同訊息型別上的平臺性約束。比如說,在產品層面上,希望晚上使用者收不到通知欄推播(畢竟會對使用者進行打擾);希望使用者一個小時內不會接收到兩條,一天最多收到N條通知欄推播(也是出於使用者的體驗)。這些平臺性的約束就適合放在訊息管理平臺上做,你可以理解為是一個兜底的功能。
  • 傳送賬號。什麼?發條訊息還有賬號的概念?你搞錯了吧,三歪?。其實是真的有的,在發郵件的時候可以選取不同的郵件賬號,在發微信公眾號訊息時可以選取不同的微信公眾號(小程式同理),在發IM訊息時可以使用不同的賬號傳送。而在接入簡訊的時候其實是分了兩種型別的:通知和行銷。我們會把這些都抽象為賬號
  • 接收者Id型別。站內的IM訊息用的是站內的userId,發通知欄訊息(PUSH)用的是did,傳簡訊用的是手機號,發微信類的訊息用的是openId。指定接收者的Id型別,表明這個模板你要傳入哪種型別的id。假設你指明是userId,但你要傳簡訊,訊息管理平臺就需要將userId轉成手機號。這裡也是用一個欄位標識,1表示userId2表示did

可以發現的是,我們把一條訊息所需要的資訊(甚至不需要的資訊)都塞進模板裡面了,等呼叫方傳入模板Id時,我就能拿到我想要的所有資訊了。

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

  • 運營模板:運營要給指定一批人某時某刻傳送訊息。(這一批人是T+1離線的)。例子:如果使用者註冊登入了APP,可以隔一天(甚至更長時間)給使用者發訊息。這種屬於非實時(離線)推播,這種就不需要技術來承接,去圈選人群后設定對應的時間即可推播。
  • 技術模板:系統根據業務條件自動觸發一批訊息,接收者名單也依賴業務場景(這批人一般是實時的)。例子:如果使用者註冊登入了APP,就立馬需要給該使用者發訊息。這種屬於實時推播,需要對應的技術來承接。

隨著系統和業務的演進,運營模板和技術模板的界限會越來越模糊。從本質上就是提供了兩種發訊息的方式:

  1. 圈定一批人群,通過使用定時任務到點呼叫介面觸發(接收者、文案、傳送時間都已明確)。
  2. 技術呼叫介面傳送訊息(接收者,文案,傳送時間均由業務邏輯所產生)。例子:歡迎關注三歪,你的驗證碼是: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下發訊息了,這塊就跟你們單個的實現沒什麼大的區別了,呼叫個介面還能給你玩出花來?(程式碼風格會稍好一些,模板方法模式、責任鏈、生產者與消費者模式等在專案中都有對應的應用)

總結一下介面的實現:

  1. 呼叫方呼叫介面時,介面不會同步直接呼叫下游的API傳送訊息,而是放入訊息佇列上(支援高並行)
  2. 放入佇列時,會根據不同渠道以及不同型別的訊息進行分類,放到不同的topic(業務隔離)
  3. 消費佇列時,會在本地使用阻塞佇列來提高並行度(加快消費的速度)

Id轉換(擴充套件)

在前面也提到了,發不同型別的訊息會需要有不同的id型別:微信類需要openId、簡訊需要手機號、push通知欄推播需要did

在大多數情況下,一般呼叫者就傳入userId給到我,我這邊需要根據不同的訊息型別對userId進行轉換。

那在我們這邊是怎麼實現該系統的呢?主要的步驟和邏輯有以下:

  1. 監聽使用者變更和微信公眾號訂閱/取關的topic,在Flink清洗出一個統一的資料模型,將清洗後的資料寫到另一個的topic
  2. 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」這些就叫做「點位」,把這些點位在關鍵的位置中打上紀錄檔,這個就叫做「埋點

有了埋點,我們要做的就是將這些點位收集起來,然後統一處理成我們的資料格式,輸出到資料來源中。

  1. 收集紀錄檔
  2. 清洗紀錄檔
  3. 輸出到資料來源

有logAgent幫我們收集紀錄檔到Kafka,實時清洗紀錄檔我們用的是Flink,清洗完我們輸出到Redis(實時)/Hive(離線)

Hive表的資料樣例(主要用於離線報表統計):

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

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

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

總結一下資料統計:

  1. 設計出業務上的umpid,給所有的訊息推播連結都加上umpdId 引數
  2. 打通上下游,共同設計和維護關鍵點位,統一紀錄檔格式來實現跨平臺的收集和清洗
  3. 兼顧實時和離線需求寫到不同的資料來源,實時以多維度統計來快速定位問題

聊聊運營層面

前面提到了,運營的模板是需要圈選一批人群,然後下發訊息的,那這群人從哪裡來?

在很久之前,訊息管理平臺也把人群給做掉了,大致的思路就是可以支援檔案上傳hivesql上傳兩種方式去圈選人群,圈出來上傳到hdfs進行讀取,支援對人群的更新/切分/匯出等功能。

有了人群的概念,你會發現你收到的訊息其實都是跟你息息相關的(不是瞎給你推播的,你在裡面,才能圈到你)。可能是因為你看了幾天的連衣裙,所以給你推播連衣裙的訊息,吸引去你購買。

後來,由於公司內部DMP系統崛起,人群就都交由DMP給管理了。但實現的思路也都是類似的,只不過還是同樣的:人家做的是平臺,功能肯定比會自己寫幾個介面要完善不少。

做推播就免不了發錯了訊息,特別是在運營側(分分鐘就推播千萬人),我們平臺又做了什麼措施去儘可能避免這種問題的發生呢?

在運營圈定人群后,我們會有單獨的測試功能去「測試單個使用者」是否能正常下發訊息,文案連結是否存在問題。

這一個步驟是必須要做的,給使用者發出的訊息,首先要經過自己的校驗。如果確認連結和文案都無問題後,則提交任務,走工單審批後才能傳送。

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

針對於(技術方推播),我們在預發環境下設定了「白名單」才能收到訊息。

線上訊息有「去重」的邏輯:

  • 在某段時間內,過濾掉重複訊息
  • 運營類訊息推播(圈定人群的方式去下發訊息)同一個使用者需要相隔一段時間才能下發一次。

雖然說,我們制定了很多的規則去儘量避免事故的發生,但不得不說推播還是一個容易出現事故的功能。我的牛逼已經吹完了,如果某天發現我的推播出了事故,不要@我,當沒見過這篇文章就好。

總結

不知道大家看完之後覺得訊息管理平臺難不難,從理解上的角度而言,這系統應該是很好理解的,沒有摻雜很多業務的東西,都是做平臺性相關的內容。

這個系統能支援數W的QPS,每天億級的流量推播,一篇文章也不可能把訊息管理平臺的所有功能點都講完,內容也不止上面這些,但核心我應該是講清楚的了。

傳送訊息可以做得很簡單,也可以做得很平臺化,如果你覺得你學到了些許東西,希望可以給我點個點贊和轉發一波。如果你對我寫的內容有疑問,歡迎評論區交流。

後續可能會更多寫廣告系統相關的內容,會以一些小的問題切入,不得不說,廣告系統比訊息管理平臺還是要複雜和有趣得多。提前關注預定最新文章,不會讓你希望的!

我是三歪,下期揭祕-廣告系統再見