1. 程式人生 > >每天數百億使用者行為資料,美團點評怎麼實現秒級轉化分析?

每天數百億使用者行為資料,美團點評怎麼實現秒級轉化分析?

使用者行為分析是資料分析中非常重要的一項內容,在統計活躍使用者,分析留存和轉化率,改進產品體驗、推動使用者增長等領域有重要作用。美團點評每天收集的使用者行為日誌達到數百億條,如何在海量資料集上實現對使用者行為的快速靈活分析,成為一個巨大的挑戰。為此,我們提出並實現了一套面向海量資料的使用者行為分析解決方案,將單次分析的耗時從小時級降低到秒級,極大的改善了分析體驗,提升了分析人員的工作效率。

本文以有序漏斗的需求為例,詳細介紹了問題分析和思路設計,以及工程實現和優化的全過程。本文根據2017年12月ArchSummit北京站演講整理而成,略有刪改。

問題分析

下圖描述了轉化率分析中一個常見場景,對訪問路徑“首頁-搜尋-菜品-下單-支付”做分析,統計按照順序訪問每層節點的使用者數,得到訪問過程的轉化率。
統計上有一些維度約束,比如日期,時間視窗(整個訪問過程在規定時間內完成,否則統計無效),城市或作業系統等,因此這也是一個典型的OLAP分析需求。此外,每個訪問節點可能還有埋點屬性,比如搜尋頁上的關鍵詞屬性,支付頁的價格屬性等。從結果上看,使用者數是逐層收斂的,在視覺化上構成了一個漏斗的形狀,因此這一類需求又稱之為“有序漏斗”。
問題

這類分析通常是基於使用者行為的日誌表上進行的,其中每行資料記錄了某個使用者的一次事件的相關資訊,包括髮生時間、使用者ID、事件型別以及相關屬性和維度資訊等。現在業界流行的通常有兩種解決思路。

  1. 基於Join的SQL
select count (distinct t1.id1), count (distinct t2.id2), count (distinct t3.id3) 
from (select uuid id1, timestamp ts1 from data where timestamp >= 1510329600 and timestamp < 1510416000 and page = '首頁'
) t1 left join (select uuid id2, timestamp ts2 from data where timestamp >= 1510329600 and timestamp < 1510416000 and page = '搜尋' and keyword = '中餐') t2 on t1.id1 = t2.id2 and t1.ts1 < t2.ts2 and t2.ts2 - t1.ts1 < 3600 left join (select uuid id3, timestamp ts3 from data where timestamp >= 1510329600
and timestamp < 1510416000 and page = '菜品') t3 on t1.id1 = t3.id3 and t2.ts2 < t3.ts3 and t1.ts1 < t3.ts3 and t3.ts3 - t1.ts1 < 3600
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  1. 基於UDAF(User Defined Aggregate Function)的SQL
select
funnel(timestamp, 3600, '首頁') stage0,
funnel(timestamp, 3600, '首頁', '搜尋', keyword = '中餐') stage1, funnel(timestamp, 3600, '首頁', '搜尋', '菜品') stage2
from data
where timestamp >= 1510329600 and timestamp < 1510416000 group by uuid
  • 1
  • 2
  • 3
  • 4
  • 5

對於第一種解法,最大的問題是需要做大量join操作,而且關聯條件除了ID的等值連線之外,還有時間戳的非等值連線。當資料規模不大時,這種用法沒有什麼問題。但隨著資料規模越來越大,在幾百億的資料集上做join操作的代價非常高,甚至已經不可行。
第二種解法有了改進,通過聚合的方式避免了join操作,改為對聚合後的資料通過UDAF做資料匹配。這種解法的問題是沒有足夠的篩選手段,這意味著幾億使用者對應的幾億條資料都需要遍歷篩選,在效能上也難以接受。

那麼這個問題的難點在哪裡?為什麼上述兩個解法在實際應用中變得越來越不可行?主要問題有這麼幾點。
1. 事件匹配有序列關係。如果沒有序列關係就非常容易,通過集合的交集並集運算即可。
2. 時間視窗約束。這意味著事件匹配的時候還有最大長度的約束,所以匹配演算法的複雜度會進一步提升。
3. 屬性和維度的需求。埋點SDK提供給各個業務線,每個頁面具體埋什麼內容,完全由業務決定,而且取值是完全開放的,因此目前屬性基數已經達到了百萬量級。同時還有幾十個維度用於篩選,有些維度的基數也很高。
4. 資料規模。目前每天收集到的使用者行為日誌有幾百億條,對資源和效率都是很大的挑戰。

基於上述難點和實際需求的分析,可以總結出幾個實際困難,稱之為“壞訊息”。
1. 漏斗定義完全隨機。不同分析需求對應的漏斗定義完全不同,包括具體包含哪些事件,這些事件的順序等,這意味著完全的預計算是不可能的。
2. 附加OLAP需求。除了路徑匹配之外,還需要滿足屬性和維度上一些OLAP的上卷下鑽的需求。
3. 規模和效能的矛盾。一方面有幾百億條資料的超大規模,另一方面又追求秒級響應的互動式分析效率,這是一個非常激烈的矛盾衝突。

另一方面,還是能夠從問題的分析中得到一些“好訊息”, 這些也是在設計和優化中可以利用的點。
1. 計算需求非常單一。這個需求最終需要的就是去重計數的結果,這意味著不需要一個大而全的資料引擎,在設計上有很大的優化空間。
2. 併發需求不高。漏斗分析這類需求一般由運營或者產品同學手動提交,查詢結果用於輔助決策,因此併發度不會很高,這樣可以在一次查詢時充分調動整個叢集的資源。
3. 資料不可變。所謂日誌即事實,使用者行為的日誌一旦收集進來,除非bug等原因一般不會再更新,基於此可以考慮一些索引類的手段來加速查詢。
4. 實際業務特點。最後是對實際業務觀察得出的結論,整個漏斗收斂非常快,比如首頁是幾千萬甚至上億的結果,到了最下層節點可能只有幾千,因此可以考慮一些快速過濾的方法來降低查詢計算和資料IO的壓力。

如果用一句話總結這個問題的核心本質,那就是“多維分析序列匹配基礎上的去重計數”。具體來說,最終結果就是每層節點符合條件的UUID有多少個,也就是去重後的計數值。這裡UUID要符合兩個條件,一是符合維度的篩選,二是事件序列能匹配漏斗的定義。去重計數是相對好解的問題,那麼問題的重點就是如果快速有效的做維度篩選和序列匹配。

演算法設計

下圖是部分行為日誌的資料,前面已經提到,直接在這樣的資料上做維度篩選和序列匹配都是很困難的,因此考慮如何對資料做預處理,以提高執行效率。
資料1

很自然的想法是基於UUID做聚合,根據時間排序,這也是前面提到的UDAF的思路,如下圖所示。這裡的問題是沒有過濾的手段,每個UUID都需要遍歷,成本很高。
資料2

再進一步,為了更快更方便的做過濾,考慮把維度和屬性抽出來構成Key,把對應的UUID和時間戳組織起來構成value。如果有搜尋引擎經驗的話,很容易看出來這非常像倒排的思路。
資料3
這個資料結構還是存在問題。比如說要拿到某個Key對應的UUID列表時,需要遍歷所有的value才可以。再比如做時間序列的匹配,這裡的時間戳資訊被打散了,實際處理起來更困難。因此還可以在此基礎上再優化。

可以看到優化後的Key內容保持不變,value被拆成了UUID集合和時間戳序列集合這兩部分,這樣的好處有兩點:一是可以做快速的UUID篩選,通過Key對應的UUID集合運算就可以達成;二是在做時間序列匹配時,對於匹配演算法和IO效率都是很友好的,因為時間戳是統一連續存放的,在處理時很方便。
資料4

基於上述的思路,最終的索引格式如下圖所示。這裡每個色塊對應了一個索引的block,其中包括三部分內容,一是屬性名和取值;二是對應的UUID集合,資料通過bitmap格式儲存,在快速篩選時效率很高;三是每個UUID對應的時間戳序列,用於序列匹配,在儲存時使用差值或變長編碼等一些編碼壓縮手段提高儲存效率。
索引格式
在實際應用中,通常會同時指定多個屬性或維度條件,通過AND或OR的條件組織起來。這在處理時也很簡單,通過語法分析可以把查詢條件轉為一顆表達樹,樹上的葉子節點對應的是單個索引資料,非葉子節點就是AND或OR型別的索引,通過並集或交集的思路做集合篩選和序列匹配即可。

上面解決的是維度篩選的問題,另一個序列匹配的問題相對簡單很多。基於上述的資料格式,讀取UUID對應的每個事件的時間戳序列,檢查是否能按照順序匹配即可。需要注意的是,由於存在最大時間視窗的限制,匹配演算法中需要考慮回溯的情況,下圖展示了一個具體的例子。在第一次匹配過程中,由於第一層節點的起始時間戳為100,並且時間視窗為10,所以第二層節點的時間戳101符合要求,但第三層節點的時間戳112超過了最大截止時間戳110,因此只能匹配兩層節點,但通過回溯之後,第二次可以完整的匹配三層節點。
匹配演算法

通過上述的討論和設計,完整的演算法如下圖所示。其中的核心要點是先通過UUID集合做快速的過濾,再對過濾後的UUID分別做時間戳的匹配,同時上一層節點輸出也作為下一層節點的輸入,由此達到快速過濾的目的。
演算法設計

工程實現和優化

有了明確的演算法思路,接下來再看看工程如何落地。
首先明確的是需要一個分散式的服務,主要包括介面服務、計算框架和檔案系統三部分。其中介面服務用於接收查詢請求,分析請求並生成實際的查詢邏輯;計算框架用於分散式的執行查詢邏輯;檔案系統儲存實際的索引資料,用於響應具體的查詢。

這裡簡單談一下架構選型的方法論,主要有四點:簡單、成熟、可控、可調。
1.簡單。不管是架構設計,還是邏輯複雜度和運維成本,都希望儘可能簡單。這樣的系統可以快速落地,也比較容易掌控。
2.成熟。評估一個系統是否成熟有很多方面,比如社群是否活躍,專案是否有明確的發展規劃並能持續落地推進?再比如業界有沒有足夠多的成功案例,實際應用效果如何?一個成熟的系統在落地時的問題相對較少,出現問題也能參考其它案例比較容易的解決,從而很大程度上降低了整體系統的風險。
3.可控。如果一個系統持續保持黑盒的狀態,那隻能是被動的使用,出了問題也很難解決。反之現在有很多的開源專案,可以拿到完整的程式碼,這樣就可以有更強的掌控力,不管是問題的定位解決,還是修改、定製、優化等,都更容易實現。
4.可調。一個設計良好的系統,在架構上一定是分層和模組化的,且有合理的抽象。在這樣的架構下,針對其中一些邏輯做進一步定製或替換時就比較方便,不需要對程式碼做大範圍的改動,降低了改造成本和出錯概率。

基於上述的選型思路,服務的三個核心架構分別選擇了Spring,Spark和Alluxio。其中Spring的應用非常廣泛,在實際案例和文件上都非常豐富,很容易落地實現;Spark本身是一個非常優秀的分散式計算框架,目前團隊對Spark有很強的掌控力,調優經驗也很豐富,這樣只需要專注在計算邏輯的開發即可;Alluxio相對HDFS或HBase來說更加輕量,同時支援包括記憶體在內的多層異構儲存,這些特性可能會在後續優化中得到利用。
在具體的部署方式上,Spring Server單獨啟動,Spark和Alluxio都採用Standalone模式,且兩個服務的slave節點在物理機上共同部署。Spring程序中通過SparkContext維持一個Spark長作業,這樣接到查詢請求後可以快速提交邏輯,避免了申請節點資源和啟動Executor的時間開銷。
架構概覽

上述架構通過對資料的合理分割槽和資源的併發利用,可以實現一個查詢請求在幾分鐘內完成。相對原來的幾個小時有了很大改觀,但還是不能滿足互動式分析的需求,因此還需要做進一步的優化。
1. 本地化排程。儲存和計算分離的架構中這是常見的一種優化手段。以下圖為例,某個節點上task讀取的資料在另外節點上,這樣就產生了跨機器的訪問,在併發度很大時對網路IO帶來了很大壓力。如果通過本地化排程,把計算排程到資料的同一節點上執行,就可以避免這個問題。實現本地化排程的前提是有包含資料位置資訊的元資料,以及計算框架的支援,這兩點在Alluxio和Spark中都很容易做到。
優化1
2. 記憶體對映。常規實現中,資料需要從磁碟拷貝到JVM的記憶體中,這會帶來兩個問題。一是拷貝的時間很長,幾百MB的資料對CPU時間的佔用非常可觀;二是JVM的記憶體壓力很大,帶來GC等一系列的問題。通過mmap等記憶體對映的方式,資料可以直接讀取,不需要再進JVM,這樣就很好的解決了上述的兩個問題。
優化2
3. Unsafe呼叫。由於大部分的資料通過ByteBuffer訪問,這裡帶來的額外開銷對最終效能也有很大影響。Java lib中的ByteBuffer訪問介面是非常安全的,但安全也意味著低效,一次訪問會有很多次的邊界檢查,而且多層函式的呼叫也有很多額外開銷。如果訪問邏輯相對簡單,對資料邊界控制很有信心的情況下,可以直接呼叫native方法,繞過上述的一系列額外檢查和函式呼叫。這種用法在很多系統中也被廣泛採用,比如Presto和Spark都有類似的優化方法。
優化3

下圖是對上述優化過程的對比展示。請注意縱軸是對數軸,也就是說圖中每格代表了一個數據級的優化。從圖中可以看到,常規的UDAF方案一次查詢需要花幾千秒的時間,經過索引結構的設計、本地化排程、記憶體對映和Unsafe呼叫的優化過程之後,一次查詢只需要幾秒的時間,優化了3~4個數據級,完全達到了互動式分析的需求。
優化對比

這裡想多談幾句對這個優化結果的看法。主流的大資料生態系統都是基於JVM系語言開發的,包括Hadoop生態的Java,Spark的Scala等等。由於JVM執行機制帶來的不可避免的效能損失,現在也有一些基於C++或其它語言開發的系統,有人宣稱在效能上有幾倍甚至幾十倍的提升。這種嘗試當然很好,但從上面的優化過程來看,整個系統主要是通過更高效的資料結構和更合理的系統架構達到了3個數量級的效能提升,語言特性只是在最後一步優化中有一定效果,在整體佔比中並不多。

有一句雞湯說“以大多數人的努力程度而言,根本沒有到拼天賦的地步”,套用在這裡就是“以大多數系統的架構設計而言,根本沒有到拼語言效能的地步”。語言本身不是門檻,程式碼大家都會寫,但整個系統的架構是否合理,資料結構是否足夠高效,這些設計依賴的是對問題本質的理解和工程上的權衡,這才是更考量設計能力和經驗的地方。

總結

上述方案目前在美團點評內部已經實際落地,穩定執行超過半年以上。每天的資料有幾百億條,活躍使用者達到了上億的量級,埋點屬性超過了百萬,日均查詢量幾百次,單次查詢的TP95時間小於5秒,完全能夠滿足互動式分析的預期。
效果總結

整個方案從業務需求的實際理解和深入分析出發,抽象出了維度篩選、序列匹配和去重計數三個核心問題,針對每個問題都給出了合理高效的解決方案,其中結合實際資料特點對資料結構的優化是方案的最大亮點。在方案的實際工程落地和優化過程中,秉持“簡單、成熟、可控、可調”的選型原則,快速落地實現了高效架構,通過一系列的優化手段和技巧,最終達成了3~4個數量級的效能提升。

作者簡介

業銳,2015年加入美團,現任美團點評資料平臺查詢引擎團隊負責人。主要負責資料生產和查詢引擎的改進優化和落地應用,專注於分散式計算,OLAP分析,Adhoc查詢等領域,對分散式儲存系統亦有豐富經驗。

發現文章有錯誤、對內容有疑問,都可以關注美團點評技術團隊微信公眾號(meituantech),在後臺給我們留言。我們每週會挑選出一位熱心小夥伴,送上一份精美的小禮品。快來掃碼關注我們吧! 


相關推薦

天數使用者行為資料點評怎麼實現轉化分析

使用者行為分析是資料分析中非常重要的一項內容,在統計活躍使用者,分析留存和轉化率,改進產品體驗、推動使用者增長等領域有重要作用。美團點評每天收集的使用者行為日誌達到數百億條,如何在海量資料集上實現對使用者行為的快速靈活分析,成為一個巨大的挑戰。為此,我們提出並實現了一套面向海

資料案例】天數使用者行為資料點評怎麼實現轉化分析

6. 效果:上述方案目前在美團點評內部已經實際落地,穩定執行超過半年以上。每天的資料有幾百億條,活躍使用者達到了上億的量級,埋點屬性超過了百萬,日均查詢量幾百次,單次查詢的TP95時間小於5秒,完全能夠滿足互動式分析的預期。相比於原有sql方案,達到了3-4個數量級的效能提升。

深諳賦能之道點評如何構建生活服務終極藍圖?

gin 可視化 團購 一體化 責任 定位 作用 一個 收入 好萊塢大片裏有不少關於終極的定義,例如即將上映的《猩球崛起3:終極之戰》就在預告裏定義了終極的含義:特效驚人和主角凱撒即將大反攻。而在《終結者》系列中,施瓦辛格正是依靠不斷的進化走向機器人的終極,才能守衛和平。

“試一試”不靠譜點評不能永遠走在“試錯”的路上

美團有點煩,這邊股價跌跌不休,那邊又爆出裁員傳聞。 美團點評有點煩 近幾個月來,關於美團"缺錢"、"鉅額虧損"、"估值遇冷"、"騰訊流量紅利消失"等報道層出不窮。 近日,更有社交媒體爆出美團將進行裁員。據爆料者稱,美團App之前裁了一 個首頁的產品團隊100多人,以及大眾點評APP 20

估值上的拼多多為什麽被淘寶點名成新的假貨聚集地?

二線城市 基本上 原因 銷售 視頻 平衡 水平 移動互聯網 獨角獸 一個星期之前,阿裏巴巴對外發布了《2017年阿裏巴巴知識產權保護年度報告》,其中提及一批原淘寶網的制假售假商家已轉移至微信和拼多多。 事實真的如阿裏發布的報告所言嗎?下面何璽和大家一起來聊聊。 一、拼多多為

從10規模大促用雲效玩轉項目管理

信息 渠道 很多 理論 lis www. 周報 不出 綜合 摘要: 一個大的商業項目,如何能做到如期完工並準時交付,是有一套標準化的流程和體系來支撐的。項目管理並不是一個陌生的話題,就像《人月神話》裏面提到的阿波羅計劃、曼哈頓工程都是非常經典的案例。對於很多新同學來講,對項

一筆美元軍方訂單引發了美國科技公司大混戰

矽谷Live / 實地探訪 / 熱點探祕 / 深度探討 從今年三月起,一枚來自美國軍方的超級訂單 JEDI,在科技公司之間丟下了一顆“炸彈”。 這個訂單將持續十年,總價值超過百億美元。為了獲得這塊超級大蛋糕,幾家主流科技公

資料量下掌握這些Redis技巧你大概就穩住了全場

今天將會跟大家討論一些Redis在大資料中的使用,包括一些Redis的使用技巧和其他的一些內容。 首先給大家個地址: https://github.com/NewLifeX/NewLife.Redis 原始碼以及例項都在裡面,當然今天的內容也是按照裡面的例項來進行的,大家可以先進行下載。 這裡也附上R

轉:資料量下掌握這些Redis技巧你大概就穩住了全場

原文:https://www.douban.com/note/699241909/ 一、Redis封裝架構講解 實際上NewLife.Redis是一個完整的Redis協議功能的實現,但是Redis的核心功能並沒有在這裡面,而是在NewLife.Core裡面。 這裡可以開啟看一下,NewLife.Core

js根據資料條數使表格一行只顯示三條資料最後一行顯示多餘的資料

 個人思路,僅供參考! function show_spots_table(result) { //清空table表格 var spots = result.extend.pageInfo; var len = count(spots

持續投入僅僅是開始劉強東的又一次戰略博弈

  近年來,網際網路產業“下半場”理論逐步成為業界共識,如果說,網際網路的上半場主要是對使用者、流量進行爭奪,那麼在網際網路的下半場,技術實力,已經成為企業間互相博力的戰場。     恰逢財報季,京東集團Q3財報顯示,其前三季度在技術研發上投入超過86.4億元,比去年全年66億元的

度釋出“無人挖掘機”吹的牛實現後李彥巨集又立了3個flag

01 李彥巨集立的3個flag 7月4日百度 AI 開發者大會上,李彥巨集說:“曾經吹過的牛實現了,全球首款 L4 級量產自動駕駛巴士‘阿波龍’量產下線!”   如今,阿波龍已經安全運營了整整120天。   4個月後的現在,李彥巨集在百度世界大會上又

特徵提取演算法提取二進位制後面的特徵資料使用Java流實現

幫別人解決一個問題:在搞圖片特徵相似度檢索的東西,特徵提取演算法提取出來的二進位制特徵資料,想要讀取二進位制檔案的每一行固定位之後的資料有啥好的辦法沒? 首先讀取二進位制檔案,考慮到使用位元組流,但是不能解決如何判斷換行的問題,綜上,本人是先使用BufferedReader流讀取一行的資料,

springboot+mybatis+springmvc實現資料庫增加資料除錯時service實現類中mapper物件為空

問題:service實現類裡面執行到Mapper.save(Entity)時,捕捉到空指標異常 通過除錯,發現Mapper為空 解決途徑: 在瀏覽多個回答後,在論壇裡面看到有人回答說,controller層的方法中new 了*ServiceImpl()導致,如下圖 解決方法:註釋掉該條

點評業務之技術解密日均請求數十次的容器平臺

本文介紹美團點評的 Docker 容器叢集管理平臺(以下簡稱容器平臺)。該平臺始於 2015 年,基於美團雲的基礎架構和元件而開發的 Docker 容器叢集管理平臺。目前該平臺為美團點評的外賣、酒店、到店、貓眼等十幾個事業部提供容器計算服務,承載線上業務數百個,容器例項超過 3 萬個,日均線上請求

我如何用六個月時間拿到網易遊戲騰訊華為的offer的

2017年9月27日,武漢陰轉小雨,走出金盾酒店,看了眼灰濛濛的天,我長長舒了一口氣,這是半年來第一次放鬆心情,因為我知道,我的校招結束了。 阿里實習生面試 回想6個月之前,三月份,我第一次參加阿里菜

python中多執行緒的共享資料通過queue來實現內有生產者消費者經典模型的示例程式碼

queue:佇列,即先進先出,它有以下幾個方法: 1.判斷佇列的大小:size() 2.向佇列中新增:put() 3.向佇列中取出:get() 4.如果佇列規定了長度,用來判斷是否滿了:full() import threading,time import queu

我的秋招總結(搜狗京東科大訊飛新華三國家網際網路應急中心微盟ofo面經)

寫在前面的話: 一轉眼已經十一月下旬,也終於得空能夠把剛過去不久的秋招記錄一下,本人是軟體工程專業,工作職位投遞的主要是大資料開發和軟體開發。這篇文章會涉及一些公司的面經以及我個人的一些心得體會,希望能夠對能夠看到這篇文章的朋友有所幫助。 八月到十月,三個多月的時間裡,投

Repeater獲取勾選資料拼接成SQL實現批量刪除+儲存過程

批量刪除按鈕事件 int hs = 0;             for (int i = 0; i < this.Repeater1.Items.Count; i++)             {                 if (((CheckBox)(th

mysql 如果資料不存在則插入新資料否則更新的實現方法

CREATE TABLE `table_test` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `my_key` int(11) NOT NULL DEFAULT '0',   `value` varchar(21) NOT