您好,登錄后才能下訂單哦!
本篇內容介紹了“怎么用bitmap實現用戶畫像的標簽圈人功能”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
標簽有哪些類型呢?
枚舉類標簽: 描述性別,地理位置。這類標簽取值通常是可枚舉出來的。
時間類標簽: 描述業務觸達和流失時間信息。 注: 時間類標簽可存儲成數值。
數值類標簽: 比如賬戶金額,積分數量等。
所以本質上,標簽只有兩種: 離散枚舉和連續數值。
有了標簽后,如何在計算機中建模存儲呢?
最簡單最直觀的方式就是設置大寬表,即每個標簽一個字段。 通常一個小型的畫像系統,有幾百個標簽足夠。所以對于大部分場景寬表足夠簡單可依賴。
寬表一般存儲在Hive中,出于性能考慮,會存儲到Impala中。當數據量較大時,Impala也一般無法滿足查詢的性能需求。這是因為Impala沒有索引,每次查詢都是掃表。所以,為了能夠利用索引提升性能,大寬表一般會從Impala轉存到Elasticsearch中。
當一個用戶Id附著成百上千個標簽,按ES存儲方式,會相當耗費存儲資源,導入數據到ES也會成為性能瓶頸。 所以變通的方案是將所有的標簽存儲到ES的一個array字段中。但本質上,還是大寬表的方案。
大寬表的方案最大的問題: 新增標簽時間成本太大,所以畫像系統基本是T+1的實效性。 如果對響應時間沒有苛刻的要求,基于Hadoop生態的ad hoc查詢引擎構建寬表,比如Impala或presto是可以使用多張寬表來解決新曾標簽T+0生效問題,畢竟大數據系統,存儲資源還是很充足的。
可惜的是業務對系統的需求是: 更高,更快,更強,像體育運動一樣。
我們用ES存儲標簽,查詢速度快的原因是ES構建了倒排索引。我們構建標簽時,標簽數據的主體是用戶ID, 而在ES的世界,站在倒排索引的角度,標簽數據的主體是標簽,這完全是兩個對立面。
我們使用標簽圈人,本質上是集合的交并補運算。 所以,我們可以干脆再往前邁一步: 直接構建標簽-用戶ID
的映射關系,而非原始的用戶ID-標簽
。
這樣,整個數據結構就變成類似如下的樣式:
男: 張三,李四,王五...
由于一個標簽可以圈定上億的用戶,如何存儲這樣的結構? RoaringBitmap
。這樣存儲后,標簽圈人就脫離了SQL和ES語法,還原到最本質的集合運算:A and B or (C and D)
。
使用標簽-用戶ID
這種數據建模方式,有個很大的問題: 數值類標簽的處理。比如用戶積分。 通常有一種解決方法就是分段,然而這樣做損失了數據精度。變得不靈活了。還有一種解決方法是為每個值建立一個bitmap。 這樣做一則耗費空間,二則無法很好處理區間查詢的問題。
使用標簽-用戶ID
這種方式, bitmap存儲數據關系是標簽值等于XXX的用戶ID
, 提取核心點bitmap存儲的是等于關系
。 那么bitmap存儲大于
或者小于
關系也是可以的。
對于數值型標簽,我們重新定義存儲關系: bitmap(2) 表示value值大于2的所有用戶ID
。 同理, bitmap(5) 表示value值大于5的所有用戶ID
。這樣的話,計算value=(3,1000)之間的用戶,使用bitmap(3) andNot bitmap(999)
就可以了。很好地解決了區間查詢的問題。 依然遺留了一個問題: 需要為每個值準備一個bitmap。
這個問題的解決思路很巧妙: 多個bitmap組合表示一個數值。例如200, 拆分成個位,十位,百位
3個部分,每一部分用10個bitmap存儲。這樣就能夠把bitmap的數量控制在有限的數量里面。比如對于int整型,最多需要100個bitmap。
優化是沒有止盡的,我們還能走得更遠。如果數值采用二進制表示,那么每一位只需要2個bitmap, 一個Int類型最多需要64個bitmap。 采用二進制,存儲的規則可以如下設置:
bitmap(0)表示該位為0的用戶ID集合。 bitmap(1)表示該位為0或1的用戶ID集合。
由于對于二進制的某一位,取值只有0和1兩種可能,所以對于二進制,每一位只需要bitmap(0), 所以最多需要32+1=33個bitmap存儲。
綜上, 我們解決bitmap數量的問題,也解決了區間查詢的問題。但是多位二進制組合處理區間查詢,又引出了新的問題: 多個bitmap如何組合表示一個區間?
我們把問題再簡化一下,多個bitmap如何表示一個小于等于
的區間。 比如i<7
如何用bitmap表示? 再回顧bitmap的存儲規則:
bitmap(0)表示該位為0的用戶ID集合。 bitmap(1)表示該位為0或1的用戶ID集合。
我們按從右到左的順序給bitmap位取名字,下標從1開始。 例如01
,有兩位,分別是b2,b1。
這樣的話: i<7
= i<0111
, 用bitmap表示就是b4
。 再舉幾個例子:
i<5 = i < 0101, 用bitmap表示就是 (b4 and b2) or (b4 and b3)
為了理解這個過程,我自己畫了如下的橫向樹形圖:
1 1 0 0 1 0 0
來觀察這個規律,最后實現的代碼如下:
import lombok.Data; import org.roaringbitmap.RoaringBitmap; import java.util.*; public class RangeBitmapDemo2 { @Data private static class QueryCond{ private List<String> base = new ArrayList<>(); private List<List<String>> lowerRange = new ArrayList(); public void addBase(String val){ this.base.add(val); } public void addLowerRange(String val){ List<String> list = new ArrayList(); list.addAll(base); list.add(val); this.lowerRange.add(list); } } public static void query(int upper, int binaryLength){ String val =String.format("%"+binaryLength+"s", Integer.toBinaryString(upper)).replaceAll(" ","0"); //這里可以補空格 System.out.println("query: upper is "+val); QueryCond cond = new QueryCond(); char cur = val.charAt(0); if(cur=='0'){ cond.addBase("b"+binaryLength); } for(int i=1;i<val.length();i++){ cur = val.charAt(i); if(cur == '0'){ int back = i-1; while(back>=0 && val.charAt(back)=='1'){ cond.addLowerRange("b"+(binaryLength-back)); back -=1; } cond.addBase("b"+(binaryLength-i)); } } System.out.println("query cond: "+cond); } public static void main(String[] args) { for(int i=0;i<32;i++){ query(i, 6); } } }
打印的結果
query: upper is 000110 RangeBitmapDemo2.QueryCond(base=[b6, b5, b4, b1], lowerRange=[[b6, b5, b4, b2], [b6, b5, b4, b3]])
表示000110
的組合關系為(b6 & b5 & b4 & b3 & b2 & b1) or (b6 & b5 & b4 & b2) or (b6 & b5 & b4 & b3)
即整個結果由3個部分組合而成。
“怎么用bitmap實現用戶畫像的標簽圈人功能”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。