您的位置:首頁>科技>正文

中華萬年曆頭條數據聚合優化之路

業務介紹

中華萬年曆的頭條資料是根據推薦演算法聚合而成的資料, 包括ALS演算法資料、使用者畫像資料、時效資料、非時效資料、定投資料、驚喜資料、頻道資料、熱榜資料、使用者相關閱讀推薦資料等。 啟動方式分為冷開機和使用者畫像啟動。

冷開機:無用戶畫像或用戶畫像得分<8分。

用戶畫像:根據使用者流覽頭條資料給使用者打的一系列標籤, 標籤採用Long型的數位進行標記, 譬如娛樂285L,旅遊1127L。

時效資料:和時間相關的資料, 會隨著時間的推移自動消失, 譬如新聞、娛樂。

非時效資料:和時間不相關的資料,

會長期存在, 譬如養生。

定投數據:通過管理後臺手動投放的資料, 一般為固定位置資料, 如廣告、帖子。

驚喜數據:排除畫像之外的數據。

頻道資料:多個標籤下的資料組合而成的資料。 頻道是標籤的父類, 一個頻道對應包涵多個標籤, 標籤是使用者畫像組成的基本單位。

熱榜數據:根據使用者點擊即時上傳的日誌計算得分較高的資料。

使用者相關閱讀推薦資料:根據使用者點擊即時上傳的日誌計算相關聯的資料。

資料存儲

頭條的資料都是從合作方抓取的, 通過定時調用協力廠商API進行抓取。 抓取的資料經過頻道標籤分類後存儲到mysql資料庫。 頭條服務會每隔一段時間把資料庫裡面的資料reload到redis中, 然後再從redis中reload到本地記憶體中。

資料的聚合就是把記憶體中的資料按照演算法進行組裝。

為什麼要經過兩次的資料reload, 因為我們的介面服務是支援水準擴展的, 如果單一的從資料庫reload的話, 資料庫的連接壓力會隨著服務節點的增加而增大, 資料載入不一致的機率會也會增加。 使用redis進行中間過渡可以把資料庫的壓力分擔到redis, 畢竟redis的併發能力高於mysql, 存取速度也高於mysql。

資料reload到本地記憶體會經過篩選分類, 即每種資料在記憶體中都會有對應的一個資料池, 這些資料池是通過reload迴圈反覆運算分進去的。

數據池分為:

新池子:存放新抓取的非實效資料, 資料結構為Set

老池子:存放有點擊率、pv的資料, 資料結構為List

視頻池子:存放所有的視頻資料, 資料池結構為List

非實效標籤池子:存放標籤對應的非實效條目id,資料結構為Multimap

早期資料更新方式

資料更新主要存在兩個地方的更新:redis和local。 對新抓取的資料在api服務介面中採用spring quartz每隔一段時間從redis中讀取一次然後同步到local。 redis中的資料則是通過一個單獨的bg模組, 同樣採用spring quartz定時任務每隔一段時間從mysql中讀取, 然後同步到redis中。 除了新抓取的資料外, 在每個api服務中還有每秒更新pv、click的定時任務。

由於我們抓取的資料分為自動上架和手動上架, 手動上架需要運營人員審核通過後才能在用戶端展示,對自動上架不符合要求的資料也需要做下架處理, 按照上面的更新方式顯然不能立即生效。

值得思考的問題:

api節點較多怎麼保證每個本地記憶體中的資料是否一致

能否有針對性的更新, 不用每次都reload所有資料

能否分離api中的定時任務到bg模組

能否及時回應資料變化自動更新

遇到的問題

資料更新丟失。 bg在更新redis資料時是先添加原資料然後再建立索引, 原資料採用String資料結構, bean的ID作為key, 序列化的對象作為value。 索引採用Set資料結構, value存放的是bean的id。 每次更新資料時會刪除索引然後重新創建。 如果頭條介面服務正在reload資料的時候發生bg更新任務則會導致reload到local中的資料丟失。

reload時間過長。 頭條服務在啟動的時候不會立即初始化資料, 而是通過使用者觸發, 非同步的完成載入。 為了避免大量用戶併發reload操作採用Cache對操作進行緩存, 設置緩存時間的大小。 值得注意的是如果緩存設置的時間小於載入的時間則同樣會造成併發的reload。

佔用記憶體較大,耗費CPU。 隨著抓取的資料越來越多, 非實效的資料也越積越多, 最終導致記憶體中的資料越來越大, 從redis中讀取資料進行反序列化需要耗費大量的cpu。 雖然限制讀取資料的條數可以避免這個問題, 但是資料是糅合在一起的, 被限制的一部分資料可能是對使用者最有價值的資料。

業務資料分離

由於資料種類較多, 資料量較大, 每次變更一條資料重新reload全部資料會導致記憶體和CPU迅速上升, 尤其是全域的臨時變數替換、json的反序列化。 這樣做不僅重複載入, 而且還會因為其它資料載入的失敗而影響到所需要的資料, 沒有做到有針對性的更新。 尤其在定投廣告資料時, 廣告需要很長時間才能出現,或是因為沒有載入進來不出現,這樣就直接影響到了收入,肯定是不允許的。為了減少更新的資料量,把資料按照業務進行分離,每次更新一條資料只reload對應的資料種類。

更細細微性的資料更新可以針對到某一條。由於本系統資料已經按業務進行了拆分,資料量在合理的範圍內,做整體替換實現更加簡單。

業務資料分離是為了保證最小的資料變動,我們按照業務需求把一條sql拆分了多條sql,每條sql完成對應的資料載入,同樣記憶體中的資料也做了細化。記憶體中的資料和redis中的資料同步是通過redis的訂閱發佈實現的。

整體結構如圖:

推薦資料移轉到Redis

雖然資料分離解決了一部分reload的問題,但推薦資料是個大的資料塊,需要把所有非實效的資料都載入進來,記憶體壓力很大。當初把資料緩存在本地是為了提高用戶端的訪問效率,但當資料增加到一定程度時,每次進行資料替換都會產生佔用記憶體較大的臨時變數,老的變數會被java虛擬機器自動回收,所以在資料reload的過程中gc會變得更加頻繁。分析解決辦法:1、增加機器記憶體無疑需要增加成本;2、使用增量更新,即針對變化的資料直接在記憶體中進行修改,不做整體的reload替換,但這樣做又引出了新的問題,怎樣保證每個節點的資料是否一致,更新失敗怎麼處理,怎麼做到資料有效的監控;3、把資料移轉到redis。

如果按照方法2去實現的話等於是又要造一個redis,所以最終採用了把資料移轉到redis的方式。資料存儲主要分為基礎資料和索引資料,索引資料是有序的id Set集合,索引按照推薦策略進行了分類,如新聞、頻道、曝光、ctr等,通過調用大資料平臺來更新索引的分類和Set集合元素的score值,用以保證推薦資料的準確性。

為了減少redis的連接次數,每次推薦都會計算出足夠多的資料存放到使用者的閱讀緩存中,如果使用者閱讀緩存中的資料不夠了會重新觸發聚合計算。

資料抓取

頭條的資料來源是API介面抓取(經過授權),之前的方式都是針對每一種資料來源在bg模組中進行單獨開發,然後在xml中配置quartz定時運行任務,沒有做到資料監控和視覺化管理。如果要停止或修改某一個數資料來源的抓取任務必須停止整個bg服務然後再修改代碼或quartz設定檔。

修改後的資料抓取框架:

獨立出一個專門的資料抓專案ulike,通過後臺管理,實現任務的可配置化。

Mgr 後臺管理,管理資料來源配置和任務配置,查看抓取的資料以及監控資訊。

Mysql 存儲Mgr的管理資訊

Scheduler 負責任務調度

Redis 調度通過redis發佈訂閱傳給Processor命令,對任務進行操作。

Processor 負責處理抓取命令,業務處理。

Engine 對來源資料進行解析獲取系統所需要的資料。

推薦資料查詢優化

多個redis命令操作改為pipeline管道模式操作

一次計算多頁推薦資料進行緩存

反覆運算器模式訪問標籤索引資料,控制游標的位置,在使用者連續訪問超過 一定的時間後進行回位,保證查詢最新的推薦資料。

多執行緒非同步計算

查詢設計圖:

後記

頭條資訊對使用者的價值關鍵在於推薦的演算法,我們會根據大資料分析持續調整推薦演算法、優化聚合演算法,使使用者的體驗達到最佳。

作者介紹

fangjie,隨身雲資深後端工程師,目前負責中華萬年曆後端業務研發工作。曾作為主要研發人員參與中華萬年曆公眾提醒、頭條、生活圈等功能的開發。本文經作者授權發佈。

廣告需要很長時間才能出現,或是因為沒有載入進來不出現,這樣就直接影響到了收入,肯定是不允許的。為了減少更新的資料量,把資料按照業務進行分離,每次更新一條資料只reload對應的資料種類。

更細細微性的資料更新可以針對到某一條。由於本系統資料已經按業務進行了拆分,資料量在合理的範圍內,做整體替換實現更加簡單。

業務資料分離是為了保證最小的資料變動,我們按照業務需求把一條sql拆分了多條sql,每條sql完成對應的資料載入,同樣記憶體中的資料也做了細化。記憶體中的資料和redis中的資料同步是通過redis的訂閱發佈實現的。

整體結構如圖:

推薦資料移轉到Redis

雖然資料分離解決了一部分reload的問題,但推薦資料是個大的資料塊,需要把所有非實效的資料都載入進來,記憶體壓力很大。當初把資料緩存在本地是為了提高用戶端的訪問效率,但當資料增加到一定程度時,每次進行資料替換都會產生佔用記憶體較大的臨時變數,老的變數會被java虛擬機器自動回收,所以在資料reload的過程中gc會變得更加頻繁。分析解決辦法:1、增加機器記憶體無疑需要增加成本;2、使用增量更新,即針對變化的資料直接在記憶體中進行修改,不做整體的reload替換,但這樣做又引出了新的問題,怎樣保證每個節點的資料是否一致,更新失敗怎麼處理,怎麼做到資料有效的監控;3、把資料移轉到redis。

如果按照方法2去實現的話等於是又要造一個redis,所以最終採用了把資料移轉到redis的方式。資料存儲主要分為基礎資料和索引資料,索引資料是有序的id Set集合,索引按照推薦策略進行了分類,如新聞、頻道、曝光、ctr等,通過調用大資料平臺來更新索引的分類和Set集合元素的score值,用以保證推薦資料的準確性。

為了減少redis的連接次數,每次推薦都會計算出足夠多的資料存放到使用者的閱讀緩存中,如果使用者閱讀緩存中的資料不夠了會重新觸發聚合計算。

資料抓取

頭條的資料來源是API介面抓取(經過授權),之前的方式都是針對每一種資料來源在bg模組中進行單獨開發,然後在xml中配置quartz定時運行任務,沒有做到資料監控和視覺化管理。如果要停止或修改某一個數資料來源的抓取任務必須停止整個bg服務然後再修改代碼或quartz設定檔。

修改後的資料抓取框架:

獨立出一個專門的資料抓專案ulike,通過後臺管理,實現任務的可配置化。

Mgr 後臺管理,管理資料來源配置和任務配置,查看抓取的資料以及監控資訊。

Mysql 存儲Mgr的管理資訊

Scheduler 負責任務調度

Redis 調度通過redis發佈訂閱傳給Processor命令,對任務進行操作。

Processor 負責處理抓取命令,業務處理。

Engine 對來源資料進行解析獲取系統所需要的資料。

推薦資料查詢優化

多個redis命令操作改為pipeline管道模式操作

一次計算多頁推薦資料進行緩存

反覆運算器模式訪問標籤索引資料,控制游標的位置,在使用者連續訪問超過 一定的時間後進行回位,保證查詢最新的推薦資料。

多執行緒非同步計算

查詢設計圖:

後記

頭條資訊對使用者的價值關鍵在於推薦的演算法,我們會根據大資料分析持續調整推薦演算法、優化聚合演算法,使使用者的體驗達到最佳。

作者介紹

fangjie,隨身雲資深後端工程師,目前負責中華萬年曆後端業務研發工作。曾作為主要研發人員參與中華萬年曆公眾提醒、頭條、生活圈等功能的開發。本文經作者授權發佈。

Next Article
喜欢就按个赞吧!!!
点击关闭提示