Pick!閒魚億級商品庫中的秒級實時選品

闲鱼技术發表於2019-03-21

業務背景

在電商運營工作中,營銷活動是非常重要的部分,對使用者增長和GMV都有很大幫助。對電商運營來說,如何從龐大的商品庫中篩選出賣家優質商品並推送給有需要的買家購買是每時每刻都要思索的問題,而且這個過程需要儘可能快和實時。保證快和實時就可以提升買賣雙方的使用者體驗,提高使用者粘性。

馬赫

為了解決上面提到的問題,閒魚研發了馬赫系統。馬赫是一個實時高效能的商品選品系統,解決在億級別商品中通過規則篩選優質商品並進行投放的場景。馬赫系統最大的特點是快而實時,體現在命中規模為100w的規則可以在10分鐘之內完成打標;商品本身變更導致的規則命中結果同步時間為1秒鐘。運營可以通過馬赫系統快速篩選商品向使用者投放,閒魚的流量也可以精準投給符合條件的商品並且將流量利用到最大化。

那麼馬赫系統是如何解決這一典型的電商問題的呢,馬赫系統和流計算有什麼關係呢,這是下面要詳細說明的部分。

流計算

流計算是持續、低延遲、事件觸發的資料處理模型。流計算模型是使用實時資料整合工具,將資料實時變化傳輸到流式資料儲存,此時資料的傳輸變成實時化,將長時間累積大量的資料平攤到每個時間點不停地小批量實時傳輸;流計算會將計算邏輯封裝為常駐計算服務,一旦啟動就一直處於等待事件觸發狀態,當有資料流入後會觸發計算迅速得到結果;當流計算得到計算結果後可以立刻將資料輸出,無需等待整體資料的計算結果。Pick!閒魚億級商品庫中的秒級實時選品閒魚實時選品系統使用的流計算框架是Blink,Blink是阿里巴巴基於開源流計算框架Flink定製研發的企業級流計算框架,可以認為是Flink的加強版,現在已經開源。Flink是一個高吞吐、低延遲的計算引擎,同時還提供很多高階功能。比如它提供有狀態的計算,支援狀態管理,支援強一致性的資料語義以及支援Event Time,WaterMark對訊息亂序的處理等特性,為閒魚實時選品系統的超低延時選品提供了有力支援。Pick!閒魚億級商品庫中的秒級實時選品

0Blink之state

State是指流計算過程中計算節點的中間計算結果或後設資料屬性,比如在aggregation過程中要在state中記錄中間聚合結果,比如Apache Kafka作為資料來源時候,我們也要記錄已經讀取記錄的offset,這些State資料在計算過程中會進行持久化(插入或更新)。所以Blink中的State就是與時間相關的,Blink任務的內部資料(計算資料和後設資料屬性)的快照。 馬赫系統會在State中儲存商品合併之後的全部資料和規則執行結果資料。當商品發生變更後,馬赫系統會將商品變更資訊與State儲存的商品資訊進行合併,並將合併的資訊作為入參執行所有規則,最後將規則執行結果與State儲存的規則執行結果進行Diff後得到最終有效的執行結果。所以Blink的State特性是馬赫系統依賴的關鍵特性。

0Blink之window

Blink的Window特性特指流計算系統特有的資料分組方式,Window的建立是資料驅動的,也就是說,視窗是在屬於此視窗的第一個元素到達時建立。當視窗結束時候刪除視窗及狀態資料。Blink的Window主要包括兩種,分別為滾動視窗(Tumble)和滑動視窗(Hop)。

滾動視窗有固定大小,在每個視窗結束時進行一次資料計算,也就是說滾動視窗任務每經過一次固定週期就會進行一次資料計算,例如每分鐘計算一次總量。Pick!閒魚億級商品庫中的秒級實時選品滑動視窗與滾動視窗類似,視窗有固定的size,與滾動視窗不同的是滑動視窗可以通過slide引數控制滑動視窗的新建頻率。因此當slide值小於視窗size的值的時候多個滑動視窗會重疊,此時資料會被分配給多個視窗,如下圖所示:Pick!閒魚億級商品庫中的秒級實時選品Blink的Window特性在資料計算統計方面有很多使用場景,馬赫系統主要使用視窗計算系統處理資料的實時速度和延時,用來進行資料統計和監控告警。

0Blink之UDX

UDX是Blink中使用者自定義函式,可以在任務中呼叫以實現一些定製邏輯。Blink的UDX包括三種,分別為:

  • UDF - User-Defined Scalar Function UDF是最簡單的自定義函式,輸入是一行資料的任意欄位,輸出是一個欄位,可以實現資料比較、資料轉換等操作。

  • UDTF - User-Defined Table-Valued Function UDTF 是表值函式,每個輸入(單column或多column)返回N(N>=0)Row資料,Blink框架提供了少量的UDTF,比如:STRINGSPLIT,JSONTUPLE和GENERATE_SERIES3個built-in的UDTF。

  • UDAF - User-Defined Aggregate Function UDAF是聚合函式,輸入是多行資料,輸出是一個欄位。Blink框架Built-in的UDAF包括MAX,MIN,AVG,SUM,COUNT等,基本滿足了80%常用的集合場景,但仍有一定比例的複雜業務場景,需要定製自己的聚合函式。

馬赫系統中使用了大量的UDX進行邏輯定製,包括訊息解析、資料處理等。而馬赫系統最核心的商品資料合併、規則執行和結果Diff等流程就是通過UDAF實現的。

秒級選品方案

選品系統在專案立項後也設計有多套技術方案。經過多輪討論後,最終決定對兩套方案實施驗證後決定最終實現方案。

第一套方案是基於PostgreSQL的方案,PostgreSQL可以很便捷的定義Function進行資料合併操作,在PostgreSQL的trigger上定義執行規則邏輯。基於PostgreSQL的技術實現較複雜,但能滿足功能需求。不過效能測試結果顯示PostgreSQL處理小資料量(百萬級)效能較好;當trigger數量多、trigger邏輯複雜或處理億級別資料時,PostgreSQL的效能會有較大下滑,不能滿足秒級選品的效能指標。因此基於PostgreSQL的方案被否決(在閒魚小商品池場景中仍在使用)。

第二套方案是基於Blink流計算方案,通過驗證發現Blink SQL很適合用來表達資料處理邏輯而且Blink效能很好,綜合對比之後最終選擇Blink流計算方案作為實際實施的技術方案。

為了配合使用流計算方案,馬赫系統經過設計和解耦,無縫對接Blink計算引擎。其中資料處理模組是馬赫系統核心功能模組,負責接入商品相關各類資料、校驗資料、合併資料、執行規則和處理執行結果並輸出等步驟,所以資料處理模組的處理速度和延時在很大程度上能代表馬赫系統資料處理速度和延時。接下來我們看下資料處理模組如何與Blink深度結合將資料處理延遲降到秒級。

Pick!閒魚億級商品庫中的秒級實時選品資料處理模組結構如上圖,包含資料接入層、資料合併層、規則執行層和規則執行結果處理層。每層都針對流計算處理模式進行了單獨設計。

0資料接入層

Pick!閒魚億級商品庫中的秒級實時選品資料接入層是資料處理模組前置,負責對接多渠道各種型別的業務資料,主要邏輯如下:
  • 資料接入層對接多個渠道多種型別的業務資料;

  • 解析業務資料並做簡單校驗;

  • 統計各渠道業務資料量級並進行監控,包括總量和同比變化量;

  • 通過後設資料中心獲取欄位級別的Metadata配置。後設資料中心是用來儲存和管理所有欄位的MetaData配置資訊元件。Metadata配置代表欄位後設資料配置,包括欄位值型別,值範圍和值格式等基礎資訊;

  • 根據Metadata配置進行欄位級別資料校驗;

  • 按照馬赫定義的標準資料正規化組裝資料。

這樣設計的考慮是因為業務資料是多種多樣的,比如商品資訊包括資料庫的商品表記錄、商品變更的MQ訊息和演算法產生的離線資料,如果直接通過Blink對接這些業務資料來源的話,需要建立多個Blink任務來對接不同型別業務資料來源,這種處理方式太重,而且資料接入邏輯與Blink緊耦合,不夠靈活。

資料接入層可以很好的解決上述問題,資料接入層可以靈活接入多種業務資料,並且將資料接入與Blink解耦,最終通過同一個Topic發出訊息。而Blink任務只要監聽對應的Topic就可以連續不斷的收到業務資料流,觸發接下來的資料處理流程。

0資料合併層

Pick!閒魚億級商品庫中的秒級實時選品資料合併是資料處理流程的重要步驟,資料合併的主要作用是將商品的最新資訊與記憶體中儲存的商品資訊合併供後續規則執行使用。資料合併主要邏輯是:

  • 監聽指定訊息佇列Topic,獲取業務資料訊息;

  • 解析訊息,並將訊息內容按照欄位重新組裝資料,格式為 {key:[timestamp,value]},key是欄位名稱,value是欄位值,timestamp為欄位資料產生時間戳;

  • 將組裝後的資料和記憶體中儲存的歷史資料根據timestamp進行欄位級別資料合併,合併演算法為比較timestamp大小取最新欄位值,具體邏輯見下圖。

    Pick!閒魚億級商品庫中的秒級實時選品

資料合併有幾個前提:

  1. 記憶體可以儲存存量資料; 這個是Blink提供的特性,Blink可以將任務執行過程中產生的存量資料儲存在記憶體中,在下一次執行時從記憶體中取出繼續處理。

  2. 合併後的資料能代表商品的最新狀態; 這點需要一個巧妙設計:商品資訊有很多欄位,每個欄位的值是陣列,不僅要記錄實際值,還要記錄當前值的修改時間戳。在合併商品資訊時,按照欄位進行合併,合併規則是取時間戳最大的值為準。

舉例來說,記憶體中儲存的商品ID=1的資訊是{"desc": [1, "描述1"], "price": [4, 100.5]},資料流中商品ID=1的資訊是{"desc": [2, "描述2"], "price": [3, 99.5]},那麼合併結果就是{"desc": [2, "描述2"], "price": [4, 100.5]},每個欄位的值都是最新的,代表商品當前最新資訊。

當商品資訊發生變化後,最新資料由資料接入層流入,通過資料合併層將資料合併到記憶體,Blink記憶體中儲存的是商品當前最新的全部資料。

0規則執行層


Pick!閒魚億級商品庫中的秒級實時選品規則執行層是資料處理流程核心模組,通過規則運算得出商品對各規則命中結果,邏輯如下:

  • 規則執行層接受輸入為經過資料合併後的資料;

  • 通過後設資料中心獲取欄位級別Metadata配置;

  • 根據欄位Metadata配置解析資料;

  • 通過規則中心獲取有效規則列表,規則中心是指建立和管理規則生命週期的元件;

  • 迴圈規則列表,執行單項規則,將規則命中結果儲存在記憶體;

  • 記錄執行規則丟擲異常的資料,並進行監控告警。

這裡的規則指的是運營建立的業務規則,比如商品價格大於50且狀態為線上。規則的輸入是經過資料合併後的商品資料,輸出是true或false,即是否命中規則條件。規則代表的是業務投放場景,馬赫系統的業務價值就是在商品發生變更後儘快判斷是否命中之前未命中的規則或是不命中之前已經命中的規則,並將命中和不命中結果儘快體現到投放場景中。

規則執行需利用Blink強大算力來保證快速執行,馬赫系統當前有將近300條規則,而且還在快速增長。這意味著每個商品發生變更後要在Blink上執行成百上千條規則,閒魚每天有上億商品發生變更,這背後需要的運算量是非常驚人的。

0執行結果處理層

讀者讀到這裡可能會奇怪,明明經過規則執行之後直接把執行結果輸出到投放場景就可以了,不需要執行結果處理層。實際上執行結果處理層是資料處理模組最重要的部分。Pick!閒魚億級商品庫中的秒級實時選品

因為在實際場景中,商品的變更在大部分情況只會命中很少一部分規則,而且命中結果也很少會變化。也就是說商品對很多規則的命中結果是沒有意義的,如果將這些命中結果也輸出的話,只會增加操作TPS,對實際結果沒有任何幫助。而篩選出有效的執行結果,這就是執行結果處理層的作用。執行結果處理層邏輯如下:

  • 獲取商品資料的規則執行結果;

  • 按照是否命中規則解析執行結果;

  • 將執行結果與記憶體中儲存的歷史執行結果進行diff,diff作用是排除新老結果中相同的命中子項,邏輯見下圖。

Pick!閒魚億級商品庫中的秒級實時選品

執行結果處理層利用Blink記憶體儲存商品上一次變更後規則執行結果,並將當前變更後規則執行結果與記憶體中結果進行比較,計算出有效執行結果。舉例來說,商品A上一次變更後規則命中結果為{"rule1":true, "rule2":true, "rule3":false, "rule4":false},當前變更後規則命中結果為{"rule1":true, "rule2":false, "rule3":false, "rule4":true}。因為商品A變更後對rule1和rule3的命中結果沒有變化,所以實際有效的命中結果是{"rule2":false, "rule4":true},通過執行結果處理層處理後輸出的是有效結果的最小集,可以極大減小無效結果輸出,提高資料處理的整體效能和效率。

0難點解析

雖然閒魚實時選品系統在立項之初經過預研和論證,但因為使用很多新技術框架和流計算思路,在開發過程中遇到一些難題,包括設計和功能實現方面的,很多是設計流計算系統的典型問題。我們就其中一個問題與各位讀者探討-規則公式轉換。

這個問題的業務場景是:運營同學在馬赫系統頁面上篩選商品欄位後儲存規則,服務端是已有的老系統,邏輯是根據規則生成一段SQL,SQL的where條件和運營篩選條件相同。SQL有兩方面的作用,一方面是作為離線規則,在離線資料庫中執行SQL篩選符合規則的離線商品資料;另一方面是轉換成線上規則,在Blink任務中對實時商品變更資料執行規則以判斷是否命中。 因為實時規則執行使用的是MVEL表示式引擎,MVEL表示式是類Java語法的,所以問題就是將離線規則的SQL轉換成線上規則的Java表示式,兩者邏輯需一致,並且需兼顧效能和效率。問題的解決方案很明確,解析SQL後將SQL操作符轉換成Java操作符,並將SQL特有語法轉成Java語法,例如A like '%test%'轉成A.contains('test')。 這個問題的難點是如何解析SQL和將解析後的語義轉成Java語句。經過調研之後給出了簡單而優雅的解決方案,主要步驟如下:

  • 使用Druid框架解析SQL語句,轉成一個二叉樹,單獨取出其中的where條件子樹;

  • 通過後序遍歷演算法遍歷where條件子樹;

    • 將SQL操作符換成對應的Java操作符; 目前支援且、或、等於、不等於、大於、大於等於、小於、小於等於、like、not like和in等操作。

    • 將SQL語法格式轉成Java語法; 將in語法改成Java的或語法,例如A in ('hello', 'world')轉成(A == 'hello') || (A == 'world')。

實際執行結果如下:Pick!閒魚億級商品庫中的秒級實時選品


說在最後

馬赫系統上線以來,已經支援近400場活動和投放場景,每天處理近1.4億條訊息,峰值TPS達到50000。馬赫系統已經成為閒魚選品投放的重要支撐。

本文主要闡述馬赫系統中資料處理的具體設計方案,說明整體設計的來龍去脈。雖然閒魚實時選品系統針對的是商品選品,但資料處理流計算技術方案的輸入是MQ訊息,輸出也是MQ訊息,不與具體業務繫結,所以資料處理流計算技術方案不只適用於商品選品,也適合其他類似實時篩選業務場景。希望我們的技術方案和設計思路能給你帶來一些想法和思考,也歡迎和我們留言討論,謝謝。

參考資料

  • 閒魚實時選品系統:https://mp.weixin.qq.com/s/8ROsZniYD7nIQssC14mn3w

  • Blink:https://github.com/apache/flink/tree/blink

  • PostgreSQL:https://www.postgresql.org/

  • druid:https://github.com/alibaba/druid

相關文章