作者 | 交易中臺團隊
導讀
隨著公司內容生態的蓬勃發展,內容產出方和流量提供方最關注的“收益結算”的工作,也就成為重中之重。本文基于內容分潤結算業務為入口,介紹了實現過程中的重難點,比如千萬級和百萬級數據量下的技術選型和最終實現,滿足了業務需求的同時,最終實現了高效,準確的資金結算,文章旨在拋磚引玉,希望能給讀者帶來思考和幫助。
01 業務介紹全文5185字,預計閱讀時間13分鐘。
什么是內容分潤平臺呢?簡單來說,百家號等平臺負責內容的生產和引入,手百等渠道方負責內容的分發,鳳巢等廣告平臺負責在此流量上進行變現。而分潤平臺,則是根據上述各方提供的數據,通過核心策略模型,賦予作者、媒體、小程序主和用戶,合理的、差異化的、有競爭力的分潤收益,以吸引更加優質的內容和流量的入駐和合作。通過這種多方相互協作模式,實現互惠共贏的目的。
1.1 三大功能點
針對上述的業務特點,結算系統需要包含三大功能,用于支撐內容分潤業務的準確性、合規性、及時性。
功能一:結算模型
這是我們最關鍵的功能,它負責將出色的文章轉化為作者的分潤收益。該模型的輸入數據包括數據中臺生成的用戶維度的日分潤明細和日補貼明細,而輸出則是每月的結算賬單,這些賬單會被發送到統一業務平臺用于付款。在這個過程中,我們經歷了一系列步驟,包括每日的計算、每月的總結、預提、計提和賬單生成等,所有這些步驟都是按照不同的維度逐層計算和聚合的,最終實現了賬單的付款。
功能二:C端內容交易平臺
這個功能主要面向用戶,旨在幫助作者及時查看他們的收益,并進一步激勵他們的創作動力。作者只需登錄平臺,即可查看每日的預估收益、文章的分發情況、瀏覽量等數據,還可以查看每月實際的付款賬單,提供發票等相關數據。
功能三:O端管理端平臺
為了確保資金結算更加合規和準確,整個結算體系引入了運營管理和反作弊等不同角色。這些角色在管理端負責資金管控、發票審核、黑名單管理等各種操作,以確保整個過程的合規性。
1.2 名詞解釋
PALO:百度數據倉庫,是基于開源ApacheDoris構建的企業級MPP云數據倉庫,可有效地支持在線實時數據分析。
BNS(BAIdu Naming Service):是指百度名字服務。BNS提供服務名稱或服務組名稱到服務所有運行實例的映射,你可以根據一個名字(服務名或服務組) 獲取服務的信息,包括實例的主機名和IP、實例的運行狀態、端口、負載、實例自定義配置標簽以及其他實例自定義信息。用于滿足服務交互中常見的資源定位、IP白名單維護、查詢服務下的機器列表、負載均衡以及其他任何依賴于這些信息的開發、測試和運維需求。目前BNS已經在全百度各業務線中廣泛使用,UB、RAL等框架的支持和各語言SDK也已經發布。
02 業務架構2.1 架構分層介紹
圖1是整個內容分潤的業務架構。內容分潤結算面向數據中臺,業務方,用戶(作者)和運營管理提供服務。

△圖1.內容分潤結算平臺系統架構
2.2 關鍵匯總文件
對于數據中臺,我們是直接下游,同時在整個內容分潤流程的流程中,我們扮演的是最末端的角色。百家號、問一問、百度文庫等業務會將作者的內容分發數據、廣告貢獻等給到數據中臺,數據中臺按照各種分潤計算模型歸一化數據結構,產出三份較為詳細的明細文件,包括日分潤明細,日內容分發明細,日補貼明細。
日分潤明細:作者內容分發或流量貢獻所獲得的分潤詳情,明細中包括分潤金額,文章分發渠道,父子賬號等字段。
日補貼明細:基于運管管理的二次資金分配詳情。
日內容分發明細:作者的內容分發貢獻報表。
數據中臺會將這些數據以離線文件的形式提供給我們,結算系統每日基于配置規則,進行離線計算,最終將數據進行降維匯總。后續每月月初,基于這些匯總數據,做二次匯聚產出用戶收益賬單。
2.3 服務提供方式
結算系統根據外部需求,提供多種接入方式。面對業務方,結算系統提供API、網頁嵌入模式接入方式。若業務有其自建平臺,可將結算系統提供的網頁嵌入其平臺內部,用于展示用戶的收入信息或上傳發票等。若無自建平臺,也可API形式接入。新用戶在業務側申請入駐作者后,業務調用結算系統API完成用戶注冊,開通計費單元,維護財務信息等。后續作者在內容分潤平臺查看其收入,文章分發報表,重新維護財務信息等。若有重要變更或通知,系統通過站內信方式通知作者。
系統整體支持三種賬號體系,面向作者提供兩類百度常用賬號登錄方式,面向管理端提供內網賬號登錄方式,基于此賬戶體系做了靈活權限控制,不同用戶登錄管理端,看到的可操作菜單欄各不相同,避免出現越權操作。同時基于此賬號體系,能靈活獲取上下級,構建了自動化的審批流程。
結算系統的平穩、合規、高效運行離不開各類協同生態的合力支持。反作弊能力貫穿整個內容分潤的始終,著力于打擊黑產,識別作弊用戶。OCR、發票平臺為發票識別,發票鑒定提供了通用服務。財務的各類審核,業務的多維度監管則進一步為資金結算的合規安全保駕護航。各類角色、各個系統協同合作,促成了目前內容分潤結算系統。
03 技術難點和細節
上文以整體的視角介紹了內容分潤結算系統的架構設計,下面我們將枚舉幾種業務場景構建過程中的技術選型,來詳細介紹該系統的技術落地。
3.1 千萬級數據日度任務的技術選型
場景:每日上游會給我們產出明細數據,數據為細粒度,量級為大幾千萬級別,格式為AFS文件(離線文件),需要基于某些過濾規則和計算規則做二次匯聚,后續支持多維度查詢,作者端展示報表。
3.1.1 DB批處理方案
最初任務是在物理機上通過sql批處理,任務串行執行,簡單明了,同時成功同時失敗。但隨著數據量持續遞增,串行執行可能面臨著實效性問題?;谠嫉腄B思路,我們構建了基于DDBS(關系型分布式數據庫系統)的解決方案,全部依賴于DB,因匯聚是基于用戶維度,所以基于子賬號uid計算shardingKey分表,過濾規則也落入庫中,后續使用表之間連接過濾,相同分表中的同子用戶數據匯聚。使用在線服務,按照分表規則,啟動多線程執行任務,實時寫入日匯總數據表。具體方案如圖2。

△圖2.基于DDBS的解決方案
3.1.2 離線計算
利用SPARK天然的分布式計算能力,采用離線計算方案,匯聚時使用SPARK計算?;谏嫌翁峁┑碾x線文件,構建RDD1文件,后續基于一些過濾規則過濾數據和然后基于集合規則,使用reduceBykey聚合,產出新的RDD2文件。這個RDD2文件就是我們后續使用的日表數據。因有各類在線查詢需求,需持久化到數據庫中,又因產出的日表需支持各角色多維度查詢,調研后采用PALO數據倉庫,具體方案如圖3所示。

△圖3.基于SPARK+PALO+DB解決方案
對比兩種方案后,我們最終選擇方案二實施。方案二的優點比較突出:1.SPARK集群自帶分布式計算能力,無需我們按照方案一方式自行實現分布式計算;2.數據存儲于PALO,相比于傳統的MySQL,在大批量數據和多維度報表場景,PALO性能優勢更加明顯。3.方案一有一個最大也是我們最踩坑的性能問題,實時大批量寫入DDBS數據庫導致較高的主從延遲,影響了其他業務場景。
3.2 百萬級數據的月度任務
場景:基于上述場景會產出月表,數據量大約在百萬級別,遵循月度出賬計算模型,產出最終的預提數據。日度任務和月度任務的最主要區別在于日度任務計算過程密集,月度任務過濾過程密集。
月度產出計提任務實際就是計算用戶本月收入以及本月可結算的收入,可結算收入=以前累積未結算金額+本月收入。目前該任務輸入的數據量相對較少,且以過濾為核心,因此此類任務未采用SPARK計算。而各類過濾規則與當前用戶各種屬性息息相關,因此任務圍繞用戶uid展開,采用以用戶uid為底表,先通過各類策略過濾uid,后置再計算的方案。數據量雖然相對日度任務較少,但畢竟在百萬級別,如果使用單一線程處理所有用戶,速度會極其緩慢,所以必須拆分任務,使用并行計算的方式提升效率,而如何拆分任務,如何保障任務全部執行是月度任務模型需要考慮的核心問題。
3.2.1 冪等的分布式數據批處理框架master節點
我們設計了主從任務模型,用于支持上述任務拆分執行,主結點先置啟動,用于數據備份、初始化出賬任務,以及調度從節點。從結點則等待主結點啟動子任務指令,啟動后獲取子任務執行。具體模型如下圖4,5所示。

△圖4.主節點生命周期
圖5描述了主節點的生命周期,主節點收到出賬指令后,優先做的是賬戶余額類表的數據備份,這個動作歸因于我們月度任務的特殊性,月度任務產出的數據表在其他時間不會更新,即上個月出賬結束后,賬戶余額類的相關表會在下一次出賬完畢才更新。
備份表的環節非常重要:
1.是可以在月度任務結束后做數據總額驗證工作;
2.是可以用于兜底,一旦月度任務產出數據異常,也可回退到備份數據,重新啟動任務。
主節點任務的第二步則是確認出賬任務的用戶uid范圍,我們系統為了既支持C端用戶體系,也支持商家賬號體系,重新設計了一套內部用戶id,不論是用戶賬號還是商家賬號的id均會唯一映射成一個內部uid,后文提到的該任務的uid均為內部uid。內部uid為自增id,因此查詢數據庫,即可獲取到最大uid和最小uid,也就確定了我們本次任務的uid范圍。在redis中設置兩個key代表uid的最值。至此,出賬任務的前置準備工作就完成了。主節點獲取執行子任務配置的BNS,基于BNS解析出所有實例,發送子出賬任務指令,子實例獲取到指令后,啟動N個線程執行任務,即假設有M個子實例,那最終就是M*N個線程同時執行任務。從主節點的任務可看出,該任務無其特殊性,即主節點實際和從結點是平等關系,任何實例都可成為主,也可成為從,這就為調度任務進一步提高了靈活性。
3.2.2 woker節點的任務流程

△圖5.從節點生命周期
圖5以上述實例中的一個線程作為示例,詳細描述了線程啟動后,執行的子任務的過程。首先獲取目前的最大uid和最小uid,最大uid為主節點固定值,最小uid則是一個游標。若最小uid已經大于最大uid,則代表所有uid已經處理完畢,線程結束。若不滿足上述條件,則繼續執行任務,利用redis的incryBy指令,將最小uid向前移動N個數值,這N個uid就是本次子任務的執行范圍。拿到uid后,先將uid變為N條任務批量落入Job表,并設置初始化狀態。落庫失敗,引入報警機制。落庫成功后,按照出賬模型,啟動過濾規則。所有被過濾的用戶uid均批量寫入job表,設置任務結束狀態,并且標記過濾原因,便于后續運營查詢。過濾規則執行完畢,剩余uid十不存一,此時我們利用sql計算本月用戶結算金額。計算完畢,寫入jobDB的臨時產出表,設置job任務完結態,此時一輪子任務就執行完畢。線程繼續重復執行上述過程,直至所有線程均結束,代表出賬任務執行完畢。
3.2.3 出賬確認任務
所有任務執行完畢后,主節點會收到出賬任務確認指令。

△圖6.出賬確認任務
該任務的主要目的就是確認所有uid均執行完畢,無疏漏,具體如圖6所示。上文提到,子任務執行時,都是先置落庫job表的,確認任務的第一步:掃描job表,看是否有非完結態的任務,若有,則啟動子任務,重新執行這批數據。確認任務第二步:獲取job表中所有執行的uid數量和需要執行任務的uid數量,確認數量是否一致,若不一致,重新執行出賬任務,任務基于uid和業務期間重入,已經被執行的任務會被跳過。多次兜底策略執行完畢后,數據總量校驗一致后,會將臨時月度產出數據寫入正式DB,清理臨時數據。之所以設置臨時表:1.是為了數據校驗工作,若數據校驗異常,可快速清理該表,重新啟動任務;2.若直接寫入正式線上庫,大量數據的并發寫入會導致數據庫的主從延遲,會影響其他線上實時業務場景。后置寫入實現了另類的『讀寫分離』,任務過程中僅讀正式表,任務完畢臨時表往正式表寫入數據。
04 總結
本文主要介紹了在構建結算系統過程中的幾個技術重點和難點,而要維護整套系統的平穩運行,不僅有這些技術重點,也有看似微不足道但卻環環相扣的細枝末節,保障每個環節不掉鏈子是運維工作的重要一環,后續我們將著力于提升運維效率,節省人力成本,向著運維自動化、智能化改造。另外目前的技術方案取決于我們的數據量級,未來業務蓬勃發展,業務架構也會持續迭代,期待我們向著更加完備的架構前進。
——END——






