背景
用戶/內容畫像的對存儲的要求其實是比較高的:
能批量更新(比如更新所有用戶某個屬性)
大量隨機讀取(甚至可能沒有熱點數據)
隨機屬性更新/添加
可持久化
易於橫向擴展解決性能問題
上一次重度使用HBase已經是兩年前了。HBase能夠滿足上面五個要求,所以用HBase作爲畫像體系的主要存儲引擎便水到渠成。
問題
因爲有批量更新,隨機屬性的更新/添加,那麼必然會緩存失效,從而觸發磁盤IO導致讀取響應時間受到影響。在畫像體系裏,隨機讀取量大,比如召回了1000個id,然後你需要取這1000個id的屬性集合,並且要求響應時間能夠控制在100ms。基本只要碰到磁盤IO就歇菜了。所以現在我們希望HBase能夠把所有數據都緩存住。
HBase讀緩存特色
HBase的緩存目前我所瞭解的是Block Cache. Block Cache是什麼概念的呢,我們知道HBase的最小文件單元是HFile, HFile是有結構的,主要包含:
索引,可以是多層
數據
元數據
當然還有一個布隆過濾器,方便確定一個元素是不是在HFile裏。
並且只會有三個動作:
新增HFile
HFile 合併
HFile的分裂
這裏需要注意HFile一旦生成裏面的元素就不會被改變。
一個HFile的數據會被切分成多個Block,每個Block一般而言都會有一些元信息。當然這些切分其實是邏輯上的。Block Cache就是前面三部分的Cache. 在HBase裏,當打開一個HFile時,默認會cache住一些索引信息,文件信息,讀取時則會連數據都會Cache起來。當然你也可以通過參數讓HBase在打開時就把數據Cache住。
如果是傳統意義上的緩存,如果有更新,那麼必然會導致一個問題:緩存失效。但是HBase其實並沒有這個問題。一個簡單的Get請求,HBase的讀取方式是讀MemStore 和HFile,讀HFile的時候會看數據是不是已經在BlockCache裏。
MemStore,這個是寫緩存,但是是有結構的,可以直接查。
BlockCache, 這個是讀緩存,也是有結構的,可以直接查。
HFile,這個就是HDFS文件,會touch到IO。
假設我在讀取的時候,MemStore沒有進行flush,那麼可能不會觸碰到磁盤,雖然BlockCache的數據已經是老版本的了,MemStore裏卻有最新版本的數據,所以HBase簡單的到MemStore/BlockCache都拿一遍。第二次再來拿,假設正好碰到MemStore flush生成新的HFile,這個時候就觸發磁盤IO了。當然,其他如Compaction也會導致觸發磁盤。
解決方案
下面解決方案的前提是,可用於BlockCache的內存大於你的數據。在畫像體系裏,這點可以做到的,因爲內容和用戶都是有限的。
根據前面的描述,爲了能夠保證隨機讀不觸發磁盤IO操作,那麼我們在生成新HFile的同時,也需要讓它寫進BlockCache,HBase也提供了相關參數讓你完成這個功能:
hfile.block.index.cacheonwrite 寫HFile時把索引加入到Cache
hfile.block.bloom.cacheonwrite 寫HFile時把布隆過濾器加入到Cache
hbase.rs.cacheblocksonwrite 寫HFile時把數據也寫入到Cache裏
這裏我們基本知道寫Cache的幾個時機點:
打開HFile
讀HFile
寫HFile