轉轉一體化推送平臺的實踐
1 背景
轉轉不同業務會根據各自業務特點,經常性地向不同用戶推送各種消息。例如:
- 每天向部分圖書新用戶發放紅包,并推送站內卡片消息,以刺激新客增長;
- 每周向部分奢侈品用戶發送站內系統消息,以召回老用戶;
- 在世界圖書日推送站內消息,同時發布圖書微信公眾號消息;
- 在指定日期,向游戲線索商家推送push,并發送短信提醒等等。
圖片
隨著各業務的不斷成熟,“消息推送”類型的需求逐漸累積,我們從中總結出了如下特點:
- 不同業務的推送主體不同,例如:圖書、游戲、奢品等。
- 每次推送面向的用戶群不同,例如:瀏覽過某些頁面的用戶、提交過某種訂單的用戶、新注冊的用戶等。
- 用戶數據來源不同,有些是通過執行HiveSQL生成的,有些是通過用戶畫像平臺圈定的,有些是產品同學人工指定的Excel表格。
- 推送渠道多種多樣,包含紅包、push&IM消息、短信、公眾號消息等。此外,還包含組合發送的情況,例如:先發紅包再發push,站內消息和短信同時推送等。
- 每個推送對應各自的推送內容,包含:標題文案、落地頁、圖片等。同時,可能會伴隨文案的動態替換。例如:點擊消息,跳轉至訂單詳情頁,需要根據不同用戶替換不同落地頁鏈接。
- 不同推送有各自的推送頻次,有些是定時周期性推送,有些是臨時一次性推送。此外,周期維度各不相同,有些是每天發送,有些是指定一周中的某幾天推送。
- 推送需求的業務目的不同,有些是為了拉新,有些是為了召回,還有些是為了提醒用戶。
痛點在于,每次面對新的推送,我們都需要針對上述特點,編碼一個新的推送任務,而每個任務的實現過程,都伴隨著重復搬磚:
- 都需要增加一套推送配置,進而生成一套消息上下文;
- 為了保證可靠性,都需要做防重頻率校驗;
- 為了提升效率,都需要通過異步方式提升速度;
- 為了保證高數據量級下的系統能力,還需要同時進行速度控制。
- 都需要統計對成功率、觸達率、打開率做統計。
上述的重復能力的實現會造成冗余代碼和人力成本。此外,每個推送配置分散在各個角落,也不方便代碼維護。因此,需要一個較為通用的推送平臺,將這一類需求進行統一管理。
2 設計思路
整體設計思路為:將“可變的”配置化,將“不變的”系統化。
2.1 將"可變"的配置化
維護一張推送配置表,為每一個推送需求創建一條配置記錄。上述背景中提到的“特點”即為“可變的”,將其抽象為配置表中的一個字段。例如:
- “業務方”字段:將推送主體抽象成一個枚舉類,每個業務方對應枚舉類中的一個值。
- “推送類型”字段:將不同推送渠道抽象成不同推送類型,該字段存儲的是渠道列表,允許同一推送需求選擇多個推送渠道。
- 各推送內容字段:標題、文案、落地頁、圖片、紅包計劃id等。此外,針對上述類似“訂單詳情頁”的推送,需要在配置項中加入占位符,例如:不同用戶根據訂單id區分落地頁,則需要在鏈接的相應位置中加入${訂單id}占位符。
- 用戶來源相關字段:若為HiveSQL生成的,則需要填寫“文件id”和“API key”,以觸發sql任務執行;若來自用戶畫像平臺,則需要填寫“人群id”,以從拉取圈定數據;若為人工指定,則需要上傳excel文件,由系統解析。
2.2 將"不變"的系統化
將上述“重復編程”的工作內容,抽取成一套代碼,沉淀為通用的系統能力。例如:
- 維護一張推送用戶表,記錄用戶id、對應的配置id、推送結果、失敗原因等。
- 將配置表中的配置項和用戶表中的用戶信息,根據不同推送渠道,映射成推送上下文實體。
- 執行推送前,進行用戶維度的防重和頻率校驗,若系統判斷重復推送,則直接將失敗結果寫回。
- 執行推送時,開啟線程池異步,并限制遠程服務接口調用QPS。
- 推送后,記錄推送結果,并分批將結果回寫至用戶表。
整體設計
按照上述思路,將開發后的推送工具可視化之后,當新增一個推送需求時,產品同學只需在后臺添加一個配置,將推送需求填充至各配置項即可。若用戶來源為HiveSQL生成,研發同學只需編寫SQL語句圈定用戶群,添加至用戶表。接下來的工作全部交給系統,即可滿足新的推送需求。一方面,將研發同學從重復搬磚中解放出來,省時省力;另一方面,所有推送配置集中交給后臺管理,方便維護。
3 實現細節
3.1 速度控制
- 異步化:由一個主線程順序執行推送,顯然效率低下。為保證推送速度,我們使用線程池開啟異步推送:主線程只負責從庫表讀取推送數據,分批將推送任務提交至線程池任務隊列。
異步推送過程
- 分片:當面對百萬甚至千萬級數據量時,推送任務集中在單節點上,一方面容易造成單機過載,另一方面容易達到性能上限。為進一步提升系統效率,并實現負載均衡,我們使用分片廣播調度:將推送任務分布至集群中各機器上,分片執行。分片策略:各機器分批從用戶表中讀取相同數據量的用戶,表主鍵id%機器數,若命中當前機器號,則執行推送。
分片調度
- 斷點重試:用lastId記錄上一批次推送數據的結束位置,每臺機器將各自的lastId緩存至redis,用來標識執行進度。若任務異常中斷,將根據lastId進行斷點重試,避免全表掃描。
- 接口限速:消息推送依賴平臺能力,因此在提升效率的同時,會在RPC接口調用處通過架構組件設置全局QPS限制。
3.2 資源分配策略
無論線程池、機器數還是配置的QPS限速為多少,單時段的推送資源都有上限。因此,在面對同一時段多個配置執行推送時,必然要解決資源分配問題。主要考慮如下幾種策略:
- 按優先級順序
按優先級順序
這樣分配的好處在于,策略比較簡單,只需配置優先級即可解決資源分配問題。但同時會引入新的問題,例如:設置優先級時應該考慮哪些因素?當優先級相同時該如何處理等。此外,優先級低的配置可能始終沒機會被執行。
- 按數據量大小
每個時段執行推送前,先計算當前時段各命中配置的數據量大小,按比例分配可執行的推送量。
例如:共有3個配置,總數據量分別為:40、20、20,每個時段最大推送量為:30。
時段1:命中配置為前2個,則按比例劃分的數據量分別為:20、10。推送結束后,3個配置的余量分別為:20、10、20。
時段2:待推配置為3個,將余量按比例劃分后的數據量為:12、6、12。推送結束后剩余:8,4,8。
時段3:將剩余數據推送完畢。
按數據量大小
按數據量分配的好處在于,可以根據推送進度,實時動態分配占比,保證每個配置都有機會推送數據,更加公平。但是策略相對復雜,每次推送前需要重新掃表計算數據量,一定程度上影響推送性能。
- 均勻分配
每個時段推送前,根據當前命中的配置數,均勻分配推送量。例如:共有3個配置,總數據量分別為:40、20、20,每個時段最大推送量為:30。
時段1:待推配置為前2個,則均勻分配的數據量分別為:15、15。推送結束后,3個配置的余量分別為:25、5、20。
時段2:待推配置為3個,將余量均分30后的推送量為:10、5、10。推送結束后,第2個配置的5條數據全部推送完畢,其他配置的余量分別為:20、10。
時段3:剩余配置均分30后的推送量:15,10。剩余配置1的5條數據。
時段4:將配置1的數據全部推送。
均勻分配
綜上,對比優先級策略,均勻分配可以保證公平性,每個配置都有機會執行一定數據量的推送,此外,均勻分配有一個默認優先級:數據量越小越先推送完成,符合業務場景要求;相比按數據量策略推送,均勻分配不需要二次讀表計算數據量,效率更高。因此,我們最終選擇均勻分配策略。
3.3 問題解決
- 現象
系統上線后,面對千萬級的推送量,持續Full GC。
異常監控
- 原因
主線程不斷讀取庫表,獲取用戶信息,分批提交至線程池執行推送。由于執行推送的耗時大于數據讀取,導致線程池任務隊列累積,進而造成內存積壓。
- 改進
改用生產者-消費者模式
圖片
維護一個固定長度的阻塞隊列,隊列不空時,由消費者從隊列中取數并執行推送;隊列不滿時,由生產者掃表取數并放入隊列。當生產者速度過快,即隊滿時,生產者線程阻塞,等待消費者推送數據完成后,隊列不滿時,再次喚醒。
- 效果
- Full GC問題不復存在。
- 老年代使用明顯下降。
4 上線效果
目前,已將上述功能可視化至業務后臺,開放給多業務方。
圖片
至此,每產生一個推送需求,產品同學只需在后臺新建一條推送配置,并根據需要填寫各配置項,即可完成推送。
圖片
上線至今,已支持轉轉多業務方日常及活動推送,日均達百萬級推送量,最高可達兩千萬推送量。
5 總結
本文介紹了轉轉一體化推送平臺的實現,現已成為多業務日常推送的通用工具。未來會在諸如推送目標轉化率、推送結果定向通知等方面繼續完善和豐富系統能力。
關于作者
陳曦,轉轉訂單業務研發工程師。