1. 背景描述
目前在做音樂推薦項目,前期做排序模型優化,任務是使用模型對用戶的歷史音樂進行排序,有6800多萬個用戶,約40G的用戶數據,使用HBase作為數據倉庫。
利用HBase可以存儲多個版本數據的特性,數據運算完后入庫時,將用戶id作為rowkey,songInfo:songid的值為歌曲id,使用自定義時間戳,將排序模型輸出的歌曲得分(分值越高)作為時間戳。因為HBase數據存儲默按照時間戳降序存儲,這樣只要取出用戶的songInfo:songid的所有版本的值就能獲取該用戶的歷史音樂經過模型排序后的順序。
2. 問題描述
每次數據入庫時,使用oozie進行任務調度,先使用truncate命令將原表數據清空,然后根據用戶日志進行計算后入庫。也就是不管用戶的歷史音樂數據是否有發生改變,都將HBase中的用戶數據刪除,根據數據統計,每天的活動用戶約為20w占總用戶的1/340,換句話說,對339/340的用戶進行了沒有必要的重復計算,因為他們的歷史數據沒有發生改變。如果能使用增量入庫的方式,只獲取活躍用戶的日志數據進行計算並入庫,可以大大節約資源。
3. 解決方案
3.1 直接刪除行數據
最直接的想法是,直接在入庫前增加刪除語句就行,拿到日志有變化的用戶后,刪除用戶數據,然后入庫。所以直接新增了以下語句
Delete delete = new Delete(Bytes.toBytes(StringUtils.trimWhite(userid)));
table.delete(delete);
查看最后的入庫結果,入庫失敗,只入了幾百條數據,而且根本不是想要的效果。
3.2 刪除自定義時間戳的行數據
Delete對象在刪除前沒限制刪除的列簇和時間戳,就是刪除了該rowkey所對應的所有列簇的所有字段,所以刪除數據的操作沒問題,可是入庫的代碼沒有改動,之前入庫沒有問題。繼續了解HBase刪除機制后找到了原因。
其實問題出在了自定義時間戳上。在入庫的時間戳是根據模型計算出來的,遠小於當前的時間的時間戳,而刪除的時候沒有指定時間戳,HBase會默認使用服務器生成的當前時間的時間戳。而HBase的刪除操作並不是真正的刪除,可以看成是含有Delete標記的特殊put,只是先給數據打上標記,時間戳小於這個刪除時間的數據在下一次major compaction的時候才被真正的刪除。由於刪除后入庫的數據使用的是自定義時間戳且遠小於當前時間的時間戳,所以導致了入庫的數據被HBase刪除了。
自定義時間戳遠小於刪除時自動生成的時間戳,按理來說最后入庫結果應該一條數據都沒有(因為小於刪除時間戳的數據都被刪除了),但為什么最后還是入了幾百條的數據呢? 推測原因是因為在最后數據的put過程中發生了major compaction,HBase進行了真正的刪除,刪除數據后Delete標記也失效了,所以后面的put操作才會真正生效。
既然問題定位到了時間戳,那就在刪除的時候指定時間戳,還是使用自定的時間戳,但是不直接使用模型生成的分值,而是把歌曲按分值升序排序后從1開始編號,將 刪除時的時間戳+歌曲編號 作為入庫時的時間戳。在刪除的時候直接指定刪除時間戳為當前的時間戳,這樣之前的用戶數據就會被刪除,而新入庫的數據時間戳大於被刪除的時間戳,就會被保留。關鍵代碼如下:
long offset = new Date().getTime();
//判斷該用戶是否存在
Get get = new Get(Bytes.toBytes(StringUtils.trimWhite(userid)));
get.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes("songID"));
get.setMaxVersions(1);
Result result = table.get(get);
//System.out.println("這是result.rawCells()前的result對象"+result.toString());
if (!result.isEmpty()) {
//KeyValue[] kV = result.raw();
//offset = kV[0].getTimestamp();
//System.out.println("獲取的最新時間戳為: "+offset);
//offset = result.rawCells()[0].getTimestamp(); //報錯找不到該方法,可能是hbase版本太低
//刪除之前的數據
Delete delete = new Delete(Bytes.toBytes(StringUtils.trimWhite(userid)));
delete.deleteFamily(Bytes.toBytes(columnFamily),offset);
table.delete(delete);
}
參考文章:
[1] hbase時間戳修改帶來的問題總結
[2] HBase中數據的多版本特性潛在的意外
