用戶畫像系統中遇到的比較難的問題是什么?問題一: 我們在選擇如何存儲用戶標簽時,遇到了問題(標簽查詢速度慢,并且構建不夠靈活,標簽更新和刪除比較麻煩),比如之前用HDFS或者ES存儲,后來切換為ClikcHouse,并用BitMap存儲。
原因如下:
針對標簽的表示形式,存儲方式有很多,結構為`寬表,BitMap` 都可以,存儲選擇`HDFS,ES,ClickHouse 等` 也都可以,需要衡量的有兩點`1.標簽構建的靈活性和構建速度 2.標簽的查詢效率 `
`HDFS [Presot,Impala]:` 標簽的增加,刪除,更新不友好, 一個小變動,要重寫整個`Parquet`, 寫放大問題。 查詢效率還可以,但是不夠優秀。 支持查詢并發較小。
`ES:`標簽的構建的寫入速度一般, 新增和修改標簽需要對ES文檔結構更新,ES的DSL語法不友好,有一定學習成本。查詢效率還算優秀,同時支持高并發。 ES資源占用高,需要較好的硬件配置。
`ClickHouse[BitMap]` 標簽可以并行構建,查詢效率優秀,標簽的增加非常方便,標簽的更新和刪除可以實現,但是并不高效,并發查詢支持比Presto,Impala要好,但同樣不支持高并發,能夠滿足大部分場景需求。注意兩點`1. BitMap存儲的是用戶ID 2. BitMap使用了RoaringBitMap, 解決BitMap空間占用問題,不然1億這一個數也要占用11.9M空間`
用戶畫像系統中遇到的比較難的問題是什么?問題二:如何構建用戶的稠密向量的問題
如果我們直接將用戶的標簽轉換為稀疏向量來存儲,對于類別標簽使用`one-hot`編碼,但這樣會出現維度爆炸的問題,向量過于稀疏,向量之間的余弦相似度計算結果基本沒有意義,根本無法實現用戶相似度的計算。所以就開始思考如何將用戶表示為轉換為稠密向量,經過調研發現,Word2Vec可以將詞轉換為稠密向量,同時借助Word2Vec思想,也可以將物品轉換為向量Item2Vec,比如將一個Session內,用戶購買的物品或者點擊的物品列表,看成是一句話,每個物品看成是一個單詞,就可以借助Word2Vec的思想將物品轉換為稠密向量表示。(這里注意如果是文章,可以使用分詞,然后抽取關鍵詞,將詞通過Word2Vec轉換為向量的方式) ,我們再將用戶點擊或者購買的物品列表中物品向量加和求平均,就可以得到用戶的稠密向量。后來發現通過ALS模型`矩陣分解`的方式也可以得到用戶的稠密向量,兩者`表達的用戶向量含義`是不同的,一個是有濃重的物品屬性特征的,一個是有協同特征的向量。但是都可以作為用戶的向量表示方式。
HBase得二級索引的設計(或者Phoenix 二級索引-說說原理)
HBase二級索引的設計方案一般有如下幾種
1. 協處理器coprocessor方案。 原理就是自定義協處理器,實現`雙寫`,就是寫主表的時候,同時寫索引表[這里這個索引表是根據業務對查詢的需求建立的]。 比如我們要查詢的主表是A, 里面有RowKey,還有一列ColumnA. 如果想對ColumnA這一列建立索引,就自定義一個協處理器(觀察者模式),當我們寫入A表中一條數據,比如 行鍵rowkey(123),cloumnA列值:abc,這時協處理在索引表(自己建立,比如A_INDEX)中插入一條記錄 行鍵為剛才列A的值abc,列值為主表的rowkey(123). 查詢的時候,先查索引表得到rowkey,然后根據rowkey在主表中查。
2. ES 方案,將想要構建的二級索引的字段值存儲到ES中,查詢時先去ES根據條件查到rowkey,然后根據rowkey再去hbase查數據。
3. Phoenix 方案。 Phoenix構建構建索引的方式,本質也在HBase中建立索引表。只不建表的過程,索引維護的過程,Phoenix自己內部實現,暴露給用戶的只是SQL接口。
# 其實在HBase構建二級索引,萬變不離其宗,最終的方向都是構建索引字段與行鍵的映射關系,先更加索引表查行鍵,在根據行鍵,查最終數據。
怎么提高Flink的執行性能(代碼方面)
• 通用的優化方式
1. 盡早fliter掉一些不需要的數據以及避免一些不必要的序列化。
2. 避免使用深層嵌套數據類型。
3. 對于數據傾斜使用調整并行度或者雙層聚合的方式。
4. 一些基數較少的并且本身較長維度可以采用數據字典的方式減少網絡傳輸及內存占用、gc開銷。
• 數據類型和序列化
Flink支持java、scala基本數據類型,以及java Tuples、scala Case Class、Flink Value,對于這些數據類型,flink會采用自身的序列化反序列化器去做序列化操作,對于其他數據類型,flink會采用kyro方式序列化,kyro序列化方式效率會比flink自帶的方式低很多。因此在數據序列化方面我們可以做如下工作
1. 嘗試使用transient修飾不需要序列化的變量,或者修飾你可以在下游通過其他方式獲取到變量,這個可以減少序列化流程和網絡傳輸(但可能帶來更多的內存占用用和gc消耗)
2. 對于一些特殊的數據你可以嘗試重寫writeObject() 和 readObject() 來自己控制一些序列化方式,如果更高效的話
3. 如果使用了lambda或者泛型的話,顯式的指定類型信息讓flink類型提取系統識別到以提升性能。
• 多組相同keyby可使用DataStreamUtils
在多組keyby的場景可以采用DataStreamUtils.reinterpretAsKeyedStream的方式避免多次shuffle操作
• 盡量減少狀態的大小
1. 設置合適的state TTL, 清洗過期狀態,避免狀態無限增大。
2. 減少狀態字段數, 比如使用aggreteFunction 做窗口聚合時,可以只將要聚合的信息放入狀態,其他keyBy字段以及窗口信息,可以通過processWindowFunction的方式獲取,這樣就是 aggregateFunction + ProcessWindowFunction,agg函數獲取聚合信息,輸出的結果到processwindowFunction中取獲取窗口信息。
3. checkpoint頻率不宜過高,超時時間不要太長,可以異步化的地方盡量異步化
更多關于“大數據面試題”的問題,歡迎咨詢千鋒教育在線名師。千鋒已有十余年的培訓經驗,課程大綱更科學更專業,有針對零基礎的就業班,有針對想提升技術的好程序員班,高品質課程助理你實現java程序員夢想。