瀏覽記錄系統主要用來記錄京東使用者的實時瀏覽記錄,並提供實時查詢瀏覽資料的功能。線上使用者存取一次商品詳情頁,瀏覽記錄系統就會記錄使用者的一條瀏覽資料,並針對該瀏覽資料進行商品維度去重等一系列處理並儲存。然後使用者可以通過我的京東或其他入口查詢使用者的實時瀏覽商品記錄,實時性可以達到毫秒級。目前本系統可以為京東每個使用者提供最近200條的瀏覽記錄查詢展示。
整個系統架構主要分為四個模組,包括瀏覽資料儲存模組、瀏覽資料查詢模組、瀏覽資料實時上報模組和瀏覽資料離線上報模組:
考慮到需要儲存近千億條的使用者瀏覽記錄,並且還要滿足京東線上使用者的毫秒級瀏覽記錄實時儲存和前臺查詢功能,我們將瀏覽歷史資料進行了冷熱分離。Jimdb純記憶體操作,存取速度快,所以我們將使用者的(T-4)瀏覽記錄資料儲存到Jimdb的記憶體中,可以滿足京東線上活躍使用者的實時儲存和查詢。而(T+4)以外的離線瀏覽資料則直接推播到Hbase中,儲存到磁碟上,用來節省儲存成本。如果有不活躍的使用者查詢到了冷資料,則將冷資料複製到Jimdb中,用來提高下一次的查詢效能。
熱資料採用了JIMDB的有序集合來儲存使用者的實時瀏覽記錄,使用使用者名稱做為有序集合的KEY,瀏覽商品SKU作為有序集合的元素,瀏覽商品的時間戳作為元素的分數,然後針對該KEY設定過期時間為4天。
這裡的熱資料過期時間為什麼選擇4天?
這是因為我們的巨量資料平臺離線瀏覽資料都是T+1上報彙總的,等我們開始處理使用者的離線瀏覽資料的時候已經是第二天,在加上我們自己的業務流程處理和資料淨化過濾過程,到最後推播到Hbase中,也需要執行消耗十幾個小時。所以熱資料的過期時間最少需要設定2天,但是考慮到巨量資料任務執行失敗和重試的過程,需要預留2天的任務重試和資料修復時間,所以熱資料過期時間設定為4天。所以當用戶4天內都沒有瀏覽新商品時,使用者檢視的瀏覽記錄則是直接從Hbase中查詢展示。
冷資料則採用K-V格式儲存使用者瀏覽資料,使用使用者名稱作為KEY,使用者瀏覽商品和瀏覽時間對應Json字串做為Value進行儲存,儲存時需要保證使用者的瀏覽順序,避免進行二次排序。其中使用使用者名稱做KEY時,由於大部分使用者名稱都有相同的字首,會出現資料傾斜問題,所以我們針對使用者名稱進行了MD5處理,然後擷取MD5後的中間四位作為KEY的字首,從而解決了Hbase的資料傾斜問題。最後在針對KEY設定過期時間為62天,實現離線資料的過期自動清理功能。
查詢服務模組主要包括三個微服務介面,包括查詢使用者瀏覽記錄總數量,查詢使用者瀏覽記錄列表和刪除使用者瀏覽記錄介面。
1.如何解決限流防刷問題?
基於Guava的RateLimiter限流器和Caffeine本地快取實現方法全域性、呼叫方和使用者名稱三個維度的限流。具體策略是當呼叫發第一次呼叫方法時,會生成對應維度的限流器,並將該限流器儲存到Caffeine實現的本地快取中,然後設定固定的過期時間,當下一次呼叫該方法時,生成對應的限流key然後從本地快取中獲取對應的限流器,該限流器中保留著該呼叫方的呼叫次數資訊,從而實現限流功能。
2.如何查詢使用者瀏覽記錄總數量?
首先查詢使用者瀏覽記錄總數快取,如果快取命中,直接返回結果,如果快取未命中則需要從Jimdb中查詢使用者的實時瀏覽記錄列表,然後在批次補充商品資訊,由於使用者的瀏覽SKU列表可能較多,此處可以進行分批查詢商品資訊,分批數量可以動態調整,防止因為一次查詢商品數量過多而影響查詢效能。由於前臺展示的瀏覽商品列表需要針對同一SPU商品進行去重,所以需要補充的商品資訊欄位包括商品名稱、商品圖片和商品SPUID等欄位。針對SPUID欄位去重後,在判斷是否需要查詢Hbase離線瀏覽資料,此處可以通過離線查詢開關、使用者清空標記和SPUID去重後的瀏覽記錄數量來判斷是否需要查詢Hbase離線瀏覽記錄。如果去重後的時候瀏覽記錄數量已經滿足系統設定的使用者最大瀏覽記錄數量,則不再查詢離線記錄。如果不滿足則繼續查詢離線的瀏覽記錄列表,並與使用者的實時瀏覽記錄列表進行合併,並過濾掉重複的瀏覽SKU商品。獲取到使用者完整的瀏覽記錄列表後,在過濾掉使用者已經刪除的瀏覽記錄,然後count列表的長度,並與系統設定的使用者最大瀏覽記錄數量做比較取最小值,就是該使用者的瀏覽記錄總數量,獲取到使用者瀏覽記錄總數量後可以根據快取開關來判斷是否需要非同步寫入使用者總數量快取。
3.查詢使用者瀏覽記錄列表
查詢使用者瀏覽記錄列表流程與查詢使用者瀏覽記錄總數量流程基本一致。
商詳伺服器端將使用者的實時瀏覽資料通過Kafka使用者端上報到Kafka叢集的訊息佇列中,為了提高資料上報效能,使用者瀏覽資料主題分成了50個分割區,Kafka可以將使用者的瀏覽訊息均勻的分散到50個分割區佇列中,從而大大提升了系統的吞吐能力。
瀏覽記錄系統則通過Flink叢集來消費Kafka佇列中的使用者瀏覽資料,然後將瀏覽資料實時儲存到Jimdb記憶體中。Flink叢集不僅實現了橫向動態擴充套件,進一步提高Flink叢集的吞吐能力,防止出現訊息積壓,還保證了使用者的瀏覽訊息恰好消費一次,在異常發生時不會丟失使用者資料並能自動恢復。Flink叢集儲存實現使用Lua指令碼合併執行Jimdb的多個命令,包括插入sku、判斷sku記錄數量,刪除sku和設定過期時間等,將多次網路IO操作優化為1次。
為什麼選擇Flink流式處理引擎和Kafka,而不是商詳伺服器端直接將瀏覽資料寫入到Jimdb記憶體中呢?
首先,京東商城做為一個7x24小時服務的電子商務網站,並且有著5億+的活躍使用者,每一秒中都會有使用者在瀏覽商品詳情頁,就像是流水一樣,源源不斷,非常符合分散式流式資料處理的場景。
而相對於其他流式處理框架,Flink基於分散式快照的方案在功能和效能方面都具有很多優點,包括:
第二,京東每天都會有很多的秒殺活動,比如茅臺搶購,預約使用者可達上百萬,在同一秒鐘就會有上百萬的使用者重新整理商詳頁面,這樣就會產生流量洪峰,如果全部實時寫入,會對我們的實時儲存造成很大的壓力,並且影響前臺查詢介面的效能。所以我們就利用Kafka來進行削峰處理,而且也對系統進行了解耦處理,使得商詳系統可以不強制依賴瀏覽記錄系統。
這裡為什麼選擇Kafka?
這裡就需要先了解Kakfa的特性。
Kafka為什麼這麼快?
Kafka通過零拷貝原理來快速行動資料,避免了核心之間的切換。
Kafka可以將資料記錄分批傳送,從生產者到檔案系統到消費者,可以端到端的檢視這些批次的資料。
批次處理的同時更有效的進行了資料壓縮並減少I/O延遲。
Kafka採取順序寫入磁碟的方式,避免了隨機磁碟定址的浪費。
目前本系統已經經歷多次大促考驗,且系統沒有進行降級,使用者的實時瀏覽訊息沒有積壓,基本實現了毫秒級的處理能力,方法效能TP999達到了11ms。
離線資料上報處理流程如下:
商詳前端通過子午線的API將使用者的PV資料進行上報,子午線將使用者的PV資料寫入到資料市集的使用者PV分割區表中。
資料抽數任務每天凌晨2點33分從瀏覽記錄系統Mysql庫的使用者已刪除瀏覽記錄表抽數到資料市集,並將刪除資料寫入到使用者刪除瀏覽記錄表。
離線資料計算任務每天上午11點開始執行,先從使用者PV分割區表中提取近60天、每人200條的去重資料,然後根據使用者刪除瀏覽記錄表過濾刪除資料,並計算出當天新增或者刪除過的使用者名稱,最後儲存到離線資料分割區表中。
離線資料出庫任務每天凌晨2點從離線資料分割區表中將T+2的增量離線瀏覽資料經過資料淨化和格式轉換,將T+2活躍使用者的K-V格式離線瀏覽資料推播到Hbase叢集。
作者:京東零售 曹志飛
來源:京東雲開發者社群