日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

高效大数据开发之 bitmap 思想的应用

發布時間:2024/2/28 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高效大数据开发之 bitmap 思想的应用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:xmxiong,PCG 運營開發工程師

數據倉庫的數據統計,可以歸納為三類:增量類、累計類、留存類。而累計類又分為歷史至今的累計與最近一段時間內的累計(比如滾動月活躍天,滾動周活躍天,最近 N 天消費情況等),借助 bitmap 思想統計的模型表可以快速統計最近一段時間內的累計類與留存類。

一、背景

數據倉庫的數據統計,可以歸納為三類:增量類、累計類、留存類。而累計類又分為歷史至今的累計與最近一段時間內的累計(比如滾動月活躍天,滾動周活躍天,最近 N 天消費情況等),借助 bitmap 思想統計的模型表可以快速統計最近一段時間內的累計類與留存類。

二、業務場景

我們先來看幾個最近一段時間內的累計類與留存類的具體業務問題,作為做大數據的你建議先不要急著往下閱讀,認真思考一下你的實現方案:

1.統計最近 30 天用戶的累計活躍天(每個用戶在 30 天里有 N 天使用微視 app,N 為 1-30,然后將月活躍用戶的 N 天加總)?

2.統計最近 7 天的用戶累計使用時長?

3.統計最近 30 天有播放的累計用戶數?

4.統計最近 30 天活躍用戶有多少在最近 30 天里有連續 3 天及以上活躍?

5.統計 28 天前活躍用戶的 1、3、7、14、28 天留存率?

三、傳統解決方案

在進入本文真正主題之前,我們先來看看常規的解決思路:1.統計最近 30 天用戶的累計活躍天?

--用dau表(用戶ID唯一),取最近30天分區,sum(活躍日期)。 selectsum(imp_date)?active_datefromweishi_dau_active_tablewhereimp_date>=20200701and?imp_date<=20200730

2.統計最近 7 天的用戶累計使用時長?

--用dau表(用戶ID唯一),取最近7天分區,sum(使用時長)。 selectsum(log_time)?log_timefromweishi_dau_active_tablewhereimp_date>=20200701and?imp_date<=20200707

3.統計最近 30 天有播放的累計用戶數?

--用用戶播放表(用戶ID唯一),取最近30天分區,count(distinct if(播放次數>0,用戶ID,null))。 selectcount(distinct?if(play_vv_begin>0,qimei,null))?play_userfromweishi_play_active_tablewhereimp_date>=20200701and?imp_date<=20200730

4.統計最近 30 天活躍用戶有多少在最近 30 天里有連續 3 天及以上活躍?

--用dau表(用戶ID唯一),取最近30天分區,關聯兩次最近30天分區,關聯條件右表分別為imp_date-1,imp_date-2。 selectcount(distinct?a.qimei)?active_numfrom(?selectimp_date,qimeifromweishi_dau_active_tablewhereimp_date>=20200701and?imp_date<=20200730)ajoin?--第一次join,先取出連續2天的用戶,因為7月1日用戶與7月2號-1天關聯得上,表示一個用戶在1號和2號都活躍(?selectdate_sub(imp_date,1)?imp_date,qimeifromweishi_dau_active_tablewhereimp_date>=20200701and?imp_date<=20200730)bona.imp_date=b.imp_dateand?a.qimei=b.qimeijoin?--第二次join,取出連續3天的用戶,因為第一次join已經取出連續兩天活躍的用戶了,再拿這些7月1日用戶關聯7月3日-2天關聯得上,表示一個用戶在1號和3號都活躍,結合第一步join得出用戶至少3天連續活躍了(?selectdate_sub(imp_date,2)?imp_date,qimeifromweishi_dau_active_tablewhereimp_date>=20200701and?imp_date<=20200730)cona.imp_date=c.imp_dateand?a.qimei=c.qimei

當然這里也可以用窗口函數 lead 來實現,通過求每個用戶后 1 條日期與后 2 條日期,再拿這兩個日期分布 datediff 當前日期是否為日期相差 1 且相差 2 來判斷是否 3 天以上活躍,但是這個方法也還是避免不了拿 30 天分區統計,統計更多天連續活躍時的擴展性不好的情況 5.統計 28 天前活躍用戶的 1、3、7、14、28 天留存率?

--用dau表(用戶ID唯一),取統計天的活躍用戶 left join 1、3、7、14、28天后的活躍用戶,關聯得上則說明對應天有留存。 select'20200701'?imp_date,count(distinct?if(date_sub=1,b.qimei,null))/count(distinct?a.qimei)?1d_retain_rate,count(distinct?if(date_sub=3,b.qimei,null))/count(distinct?a.qimei)?3d_retain_rate,count(distinct?if(date_sub=7,b.qimei,null))/count(distinct?a.qimei)?7d_retain_rate,count(distinct?if(date_sub=14,b.qimei,null))/count(distinct?a.qimei)?14d_retain_rate,count(distinct?if(date_sub=28,b.qimei,null))/count(distinct?a.qimei)?28d_retain_ratefromweishi_dau_active_table?partition?(p_20200701)aleft?join(?selectdatediff(imp_date,'20200701')?date_sub,qimeifromweishi_dau_active_tablewheredatediff(imp_date,'20200701')?in?(1,3,7,14,28))bona.qimeib=b.qimei

四、傳統解決方案存在的問題

1.每天大量中間數據重復計算,比如昨天最近 30 天是 8 月 1 日~ 8 月 30 日,今天最近 30 天為 8 月 2 日~ 8 月 31 日,中間 8 月 2 日~ 8 月 30 日就重復計算了。

2.統計邏輯復雜,類似業務場景 4,困難點在于統計每一天活躍的用戶第二天是否還繼續活躍。

3.耗費集群資源大,場景 4 和場景 5 都用到了 join 操作,場景 4 還不止一個 join,join 操作涉及 shuffle 操作,shuffle 操作需要大量的網絡 IO 操作,因此在集群中是比較耗性能的,我們應該盡量避免執行這樣的操作。

4.以上統計邏輯可擴展性差,由于數據分析經常進行探索性分析,上面傳統方案能解決上面幾個問題,但是數據分析稍微改變一下需求,就得重新開發,例如增加一個 15 天留存,或者統計最近 2 周的活躍天等。

五、bitmap 原理

上面的業務場景能否在一個模型表很簡單就能統計出,且不需要數據重復計算,也不需要 join 操作,還能滿足數據分析更多指標探索分析呢?答案是肯定的,可以借助 bitmap 思想。

何為 bitmap?bitmap 就是用一個 bit 位來標記某個元素,而數組下標是該元素,該元素是否存在時用 bit 位的 1,0 表示。比如 10 億個 int 類型的數,如果用 int 數組存儲的話,那么需要大約 4G 內存,當我們用 int 類型來模擬 bitmap 時,一個 int 4 個字節共 4*8 = 32 位,可以表示 32 個數,原來 10 億個 int 類型的數用 bitmap 只需要 4GB / 32 = 128 MB 的內存。

六、具體實現過程

大數據開發參考 bitmap 思想,就是參考其通過數組下標表示該元素的思想,將最近 31 天活躍用戶是否活躍用逗號分隔的 0 1 串存儲下來,將最近 31 天的播放 vv、贊轉評等消費數也用逗號分隔的具體數值存儲下來,形成一個字符數組,數組每一個下標表示距離最新一天數據的天數差值,第一位下標為 0,表示距離今天最新一天數據間隔為 0 天,如下所示:

active_date_set 表示 31 天活躍集,0 表示對應下標(距離今天的 N 天前)不活躍,1 表示活躍;這個數據是 8 月 23 日統計的,1,0,0,1,…… 即用戶在 8 月 23 日,8 月 20 日有活躍,8 月 22 日,8 月 21 日并沒有活躍。play_vv_begin_set 表示 31 天播放 vv 集,0 表示對應下標(距離今天的 N 天前)沒有播放視頻,正整數表示當天的播放視頻次數;這里用戶雖然在 8 月 23 日,8 月 20 日有活躍,但是該用戶一天只播放了一次視頻就離開微視了。這樣做的好處一方面也是大大壓縮了存儲,極端狀態下用戶 31 天都來,那么就可以將 31 行記錄壓縮在一行存儲。

假如 1 天活躍用戶 1 億,且這些用戶 31 天都活躍,那么就可以將 31 億行記錄壓縮在 1 億行里,當然實際不會出現這樣的情況,因為會有一部分老用戶流失,一部分新用戶加入,按照目前微視的統計可以節省 80%多的存儲;另一方面可以更簡單快捷地統計每個用戶最近一個月在微視的活躍與播放、消費(贊轉評)等情況。

該模型表的詳細實現過程如下:?

1.該模型表的前 31 天需要初始化一個集合,將第一天的數據寫到該表,然后一天一天滾動壘起來,累計 31 天之后就得到這個可用的集合表了,也就可以例行化跑下去。

2.最新一天需要統計時,需要拿前一天的集合表,剔除掉相對今天來說第 31 天前的數據,然后每個集合字段將最后一位刪除掉 。

3.拿最新一天的增量數據(下面用 A 表替代) full?join 第 2 步處理后的前一天表(下面用 B 表替代)關聯。

這里有三種情況需要處理:

a.既出現在 A 表,也出現在 B 表,這種情況,只需直接拼接 A 表的最新值與 B 表的數組集即可(在微視里就是最近 30 天用戶有活躍,且在最新一天有留存);

b.只出現在 B 表(在微視里是最近 30 天活躍的用戶在最新一天沒留存),這時需要拿 “0,” 拼接一個 B 表的數組集,“0,” 放在第一位;

c.只出現在 A 表(在微視里是新用戶或者 31 天前活躍的回流用戶),這時需要拿 “1,”拼接一個 30 位長的默認數組集 “0,0,0,…,0,0” ,“1,” 放在第一位。經過如此幾步,就可以生成最新一天的集合表了,具體脫敏代碼如下:

select20200823?imp_date,nvl(a.qimei,b.qimei)?qimei,casewhen?a.qimei=b.qimei?then?concat(b.active_date_set,',',a.active_date_set)when?b.qimei?is?null?then?concat('0,',a.active_date_set)when?a.qimei?is?null?then?concat(b.active_date_set,',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')end?active_date_set,casewhen?a.qimei=b.qimei?then?concat(b.log_num_set,',',a.log_num_set)when?b.qimei?is?null?then?concat('0,',a.log_num_set)when?a.qimei?is?null?then?concat(b.log_num_set,',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')end?log_num_set,casewhen?a.qimei=b.qimei?then?concat(b.log_time_set,',',a.log_time_set)when?b.qimei?is?null?then?concat('0,',a.log_time_set)when?a.qimei?is?null?then?concat(b.log_time_set,',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')end?log_time_set,casewhen?a.qimei=b.qimei?then?concat(b.play_vv_begin_set,',',a.play_vv_begin_set)when?b.qimei?is?null?then?concat('0,',a.play_vv_begin_set)when?a.qimei?is?null?then?concat(b.play_vv_begin_set,',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')end?play_vv_begin_setfrom(?selectqimei,substr(active_date_set,1,instr(active_date_set,',',1,30)-1)?active_date_set,substr(log_num_set,1,instr(log_num_set,',',1,30)-1)?log_num_set,substr(log_time_set,1,instr(log_time_set,',',1,30)-1)?log_time_set,substr(play_vv_begin_set,1,instr(play_vv_begin_set,',',1,30)-1)?play_vv_begin_setfromweishi_31d_active_set_table?partition(p_20200822)awherelast_time>=20200723)afull?join(?selectqimei,'1'?active_date_set,cast(log_num?as?string)?log_num_set,cast(log_time?as?string)?log_time_set,cast(play_vv_begin?as?string)?play_vv_begin_setfromweishi_dau_active_table?partition(p_20200823)a)bona.qimei=b.qimei

初始化集合代碼相對簡單,只需保留第一位為實際數值,然后拼接一個 30 位的默認值 0 串,初始化脫敏代碼如下:

select20200823?imp_date,qimei,'1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0'?active_date_set,concat(cast(log_num?as?string),',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')?log_num_set,concat(cast(log_time?as?string),',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')?log_time_set,concat(cast(play_vv_begin?as?string),',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0')?play_vv_begin_setfromweishi_dau_active_table?partition(p_20200823)a

七、具體使用案例

在 hive 里對這些 0 1 集合串的使用是比較困難的,為了讓這個模型表的可用性更高,因此寫了幾個 UDF 函數來直接對數組集合進行簡單地運算,目前寫了如下幾個:str_sum()、str_count()、str_min()、str_max(),其中 str_sum、str_min、str_max 這幾個函數的參數一樣,第一個傳入一個數組集合字符串,第二位傳入一個整數,代表要計算最近 N 天的結果,第三個參數是傳入一個分隔符,在本模型里分隔符均為逗號“,”。

這幾個函數都是返回一個 int 值,str_sum 返回來的是最近 N 天的數值加總,str_min 返回該數組集合元素里最小的值,str_max 返回該數組集合元素里最大的值;str_count 前 3 個參數與前面三個函數一樣,第 4 個參數是傳入要統計的值,返回來的也是 int 值,返回傳入的統計值在數組集合出現的次數,具體使用方法如下,由于是自定義函數,在 tdw 集群跑的 sql 前面需加@pyspark:

以上函數的具體使用案例脫敏代碼如下:

@pysparkselectqimei,str_sum(active_date_set,30,',')?active_date_num??--每個用戶最近30天活躍天數,str_sum(play_vv_begin_set,30,',')?play_vv_begin??--每個用戶最近30天播放視頻次數,30?-?str_count(interact_num_set,30,',','0')?interact_date_num??--每個用戶最近30天有互動的天數,通過?30?-?互動天數為0?統計得到fromweishi_31d_active_set_table?partition(p_20200823)awherelast_time>20200724

當然除了上面幾種 udf 統計所需指標之外,也可以通過正則表達式進行使用,比如統計活躍天可以這樣統計:

--將數組集合里的'0'和','用正則表達式匹配去掉再來看剩下1的個數即可。 selectcount(qimei)?--月活,sum(length(regexp_replace(substr(active_date_set,1,60),'0|,','')))?active_date_num??--月活躍天fromweishi_31d_active_set_table?partition(p_20200823)awherelast_time>20200724

開篇前的幾個業務場景,也可以通過該表快速統計:1.統計最近 30 天用戶的累計活躍天?

@pyspark selectsum(active_date_num)?active_date_num??--滾動月活躍天,count(1)?uv??--滾動月活from(?selectqimei,str_sum(active_date_set,30,',')?active_date_numfromweishi_31d_active_set_table?partition(p_20200823)awherelast_time>20200724)a

2.統計最近 7 天的用戶累計使用時長?

@pyspark selectsum(log_time)?log_time??--滾動周活躍天,count(1)?uv??--滾動周活from(?selectqimei,str_sum(log_time_set,7,',')?log_timefromweishi_31d_active_set_table?partition(p_20200823)awherelast_time>20200817)a

3.統計最近 30 天有播放的累計用戶數?

@pyspark selectcount(1)?uv??--播放次數>0from(?selectqimei,str_sum(play_vv_begin_set,30,',','0')?play_vv_beginfromweishi_31d_active_set_table?partition(p_20200823)awherelast_time>20200724)awhereplay_vv_begin>0

4.統計最近 30 天活躍用戶有多少在最近 30 天里有連續 3 天及以上活躍?

--只是判斷活躍集合里面有連續3位?1,1,1,?即可selectcount(if(substr(active_date_set,1,60)?like?'%1,1,1,%',qimei,null))?active_date_numfromweishi_31d_active_set_table?partition(p_20200823)awherelast_time>20200724

5.統計 28 天前活躍用戶的 1、3、7、14、28 天留存率?

--不需要join操作,只需找到活躍日期集對應位是否1即可select'20200723'?imp_date,count(if(split(active_date_set,',')['29']='1',qimei,null))/count(1)?1d_retain_rate,count(if(split(active_date_set,',')['27']='1',qimei,null))/count(1)?3d_retain_rate,count(if(split(active_date_set,',')['23']='1',qimei,null))/count(1)?7d_retain_rate,count(if(split(active_date_set,',')['16']='1',qimei,null))/count(1)?14d_retain_rate,count(if(split(active_date_set,',')['2']='1',qimei,null))/count(1)?28d_retain_ratefromweishi_31d_active_set_table?partition(p_20200823)awherelast_time>20200723and?split(active_date_set,',')['30']='1'

八、總結

從上面 5 個業務場景可以看出來,只要有這樣一個借助 bitmap 思想統計的模型表,不管統計最近一段時間的累計(月活躍天、月播放用戶等)與統計 1 個月內的留存,都可以一條簡單語句即可統計,不需要 join 操作,每天例行化跑時不需要重復跑接近一個月的分區,1 個月內可以支持任意統計,比如只需最近 2 周的活躍天等,因此這樣的模型相對通用,另外如果業務需要用到 2 個月的數據,也可以將模型從 31 位擴展到 61 位。

當然任何事情不可能只有優點,而不存在缺點的情況,這里這個優化的模型只是參考了 bitmap 思想,并不是 bitmap 方案實現,雖然可以將 31 天活躍用戶壓縮 80%多存儲,但是每天都存儲 31 天活躍用戶的壓縮數據,因此相比之前只保留天增量表來說,還是增加了實際存儲空間,但是這個以存儲換計算的方案是符合數倉設計原則的,因為計算是用成本昂貴的 cpu 和內存資源,存儲是用成本低廉的磁盤資源,因此有涉及最近 N 天累計或者留存計算需求的朋友可以借鑒這樣的思路。

總結

以上是生活随笔為你收集整理的高效大数据开发之 bitmap 思想的应用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。