HBase資料塊編碼壓縮機制調優及客戶端API 新版本最佳實踐-OLAP商業環境實戰
版權宣告:本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。QQ郵箱地址:[email protected],如有任何技術交流,可隨時聯絡。
網上的Hbase調優資料參差不齊,實在是不忍卒讀,有些都是拼湊且版本過時的東西,我這裡決定綜合所有優質資源進行整合,寫一份最全,最有深度,不過時的技術部落格。辛苦成文,各自珍惜,謝謝!版權宣告:禁止轉載,歡迎學習,侵權必究!
1 HBase 資料塊編碼Key優化
資料塊編碼主要是針對 Key/Value 中的 Key 進行編碼,減少 Key 儲存所佔用的空間,因為很多 Key 的字首都是重複的。舉例如下:
1.1 字首編碼(Prefix)
如果使用字首編碼作為資料塊編碼方式,那麼它只會儲存第一個 Key 的完整字串,後面的 key 只儲存跟第一個 key 的差異字元,重新編碼過的資料如下所示。如下例所示:
最上面的key值為:
myrow001:mycf:col1
針對於key而言,後續的可以之儲存差異值:
myrow001:mycf:col2就變為2
myrow001:mycf:col3就變為3
myrow002:mycf:col3就變為 2:mycf:col1 (變為最上面)
myrow003:mycf:col3就變為 3
複製程式碼
1.2 差異編碼(Diff)
差異編碼方式預設是不啟用的。為什麼?因為太慢了,每條資料都要這樣計算一下,獲取資料的速度很慢。除非你要追求極致的壓縮比,但是不考慮讀取效能的時候可以使用它,比如你想要把這部分資料當作歸檔資料的時候,可以考慮使用差異編碼。
差異編碼(Diff)比字首編碼更進一步,差異編碼甚至把以下欄位也一起進行了差異化的編碼。
鍵長度(KeyLen);
值長度(ValueLen);
時間戳(Timestamp),也即是Version;
型別(Type),也即是鍵型別。
複製程式碼
採用了差異編碼後的 KeyValue 結構為:
1 byte:標誌位;
1-5 bytes:Key 長度(KeyLen);
1-5 bytes:Value 長度(ValLen);
1-5 bytes:字首長度(Prefix Len);
... bytes:剩餘的部分;
... bytes:真正的 Key 或者只是有差異的 key 字尾部分;
1-8 bytes:時間戳(timestamp)或者時間戳的差異部分;
1 byte:Key 型別(type);
... bytes:值(value)。
複製程式碼
版權宣告:本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。QQ郵箱地址:[email protected],如有任何技術交流,可隨時聯絡。
1.3 快速差異編碼(Fast Diff)
快速差異編碼(Fast Diff)借鑑了 Diff 編碼的思路,也考慮到了差異編碼速度慢的致命缺陷。快速差異編碼的 KeyValue 結構跟差異編碼一模一樣,只有 Flag 的儲存規則不一樣,並且優化了 Timestamp 的計算。Fast Diff 的實現比 Diff 更快,也是比較推薦的演算法
不過這個“快速”只是相對本來的差異演算法而言的,由於還是有很多計算過程存在,所以快速差異演算法的速度依然屬於比較慢的。
1.4 字首樹編碼(Prefix Tree)
字首樹編碼(Prefix Tree)是字首演算法的變體,它是 0.96 版本之後才加入的特性。字首樹編碼最大的作用就是提高了隨機讀的能力,但是其複雜的演算法相對地降低了寫入的速度,消耗了更多的 CPU 資源,使用時需要在資源的消耗和隨機讀的效能之間進行取捨。
2 基於Value的壓縮器啟用
壓縮器的作用是可以把 HBase 的資料按壓縮的格式儲存,這樣可以更節省磁碟空間。當然這完全是可選的,不過推薦大家還是安裝 Snappy 壓縮器,這是 HBase 官方目前排名比較高的壓縮器。
hbase> alter 'mytable',{NAME =>'mycf',COMPRESSION=>'snappy'}
Snappy 是 Google 開發的壓縮器,有以下特點:
快速:壓縮速度達到 250MB/s;
穩定:已經用於 Google 多個產品長達數年;
健壯:Snappy 的解壓器可以保證在資料被損壞的時候也不會太糟;
免費開源。
複製程式碼
3 客戶端API 新版本最佳實踐
3.1 Connection 連線例項出世
推薦將 Configuration 做為單例;Connection 隨建隨用,用完及時關閉。
public static void main(String[] args) throws URISyntaxException, IOException {
//獲取配置檔案
Configuration config = HBaseConfiguration.create();
config.addResource(new Path(ClassLoader.getSystemResource("hbase-site.xml").toURI()));
config.addResource(new Path(ClassLoader.getSystemResource("core-site.xml").toURI()));
//建立連線
try (Connection connection = ConnectionFactory.createConnection(config); Admin admin = connection.getAdmin()) {
//定義表名
TableName tableName = TableName.valueOf("tb1");
//定義列族
ColumnFamilyDescriptor myCf = ColumnFamilyDescriptorBuilder.of("cf1");
//定義表
TableDescriptor table = TableDescriptorBuilder.newBuilder(tableName).setColumnFamily(myCf).build();
//執行建立表動作
admin.createTable(table);
} catch (Exception ex) {
ex.printStackTrace();
}
}
複製程式碼
3.2 new HTable已廢棄,新用法
// 已廢棄,不推薦使用
// HTable table = new HTable(config, "mytable");
// 官方推薦
try (Connection connection = ConnectionFactory.createConnection(config)) {
connection.getTable(TableName.valueOf("tb1"));
}
複製程式碼
3.3 checkAndPut(資料一致性)方法
Put put =new Put(Bytes.toBytes("qinkaixinRowkeys"))
put.addColumn(Bytes.toBytes("mycf"),Bytes.toBytes("name"),Bytes.toBytes("ted"));
boolean result = table.checkAndput(
Bytes.toBytes("row3"),
Bytes.toBytes("mycf"),
Bytes.toBytes("name"),
Bytes.toBytes("ted"),
put)
複製程式碼
3.4 increment方法
保證原子性的情況下,把資料庫中的某個列的數字進行數學運算
Table table = connection.getTable(TableName.valueOf("tb1"));
Increment inc = new Increment(Bytes.toBytes("row1"));
inc.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("age"), 1L);
table.increment(inc);
複製程式碼
3.5 Mutation 方法
把新增一列的時候同時刪除另一列的操作放在同一個原子操作中
Delete delete =new Delete(Bytes.toBytes("qinkaixinRowkeys"))
delete.addColumn(Bytes.toBytes("mycf"),Bytes.toBytes("name"),Bytes.toBytes("ted"));
Put put =new Put(Bytes.toBytes("qinkaixinRowkeys"))
put.addColumn(Bytes.toBytes("mycf"),Bytes.toBytes("name"),Bytes.toBytes("ted"));
RowMutations rowMutations =new RowMutations(Bytes.toBytes("ted"));
rowMutations.add(delete);
rowMutations.add(put);
table.mutateRow(rowMutations);
複製程式碼
3.6 BufferedMutator批量操作(batch)()
客戶端寫緩衝區就是一個在客戶端 JVM 裡面的快取機制,可以把多個 Put 操作攢到一起通過單個 RPC 請求傳送給客戶端,目的是節省網路握手帶來的 IO 消耗。這個緩衝區可以通過呼叫 HTable.setAutoFlush(false) 來開啟。 最新版的 API 中 setAutoFlush 被廢棄了,每個表自帶的 writeBuffer 也被廢棄了,但是客戶端寫緩衝區還是存在的,只是轉而使用 BufferedMutator 物件。
最好不要把針對同一個單元格的 Put 和 Delete 放到同一個 actions 列表裡面,因為 HBase 不一定是順序地執行這些操作的,你可能會得到意想不到的結果。
connection.getTable(TableName.valueOf("tb1")
Table table = connection.getBufferedMutator(TableName.valueOf("mytable"));
bm.mutate(put);
bm.flush();
bm.close();
複製程式碼
3.7 批量 put 操作
HBase 提供了專門針對批量 put 的操作方法:void put(List puts);其實內部也是用 batch 來實現的。需要注意的是,當一部分資料插入成功,但是另一部分資料插入失敗,比如某個 RegionServer 伺服器出現了問題,這時會返回一個 IOException,操作會被放棄,不過插入成功的資料不會被回滾。
對於插入失敗的資料,伺服器會嘗試著再次去插入或者換一個 RegionServer,當嘗試的次數大於定義的最大次數會丟擲 RetriesExhaustedWithDetailsException 異常,該異常包含了很多錯誤資訊,包括有多少操作失敗了,失敗的原因以及伺服器名和重試的次數。
3.8、Scan 快取優化
現在的 HBase 在掃描的時候已經預設開啟了快取。
每一次的 next() 操作都會產生一次完整的 RPC 請求,而這次 RPC 請求可以獲取多少資料是通過 hbase-site.xml 中的 hbase.client.scanner.caching 引數配置的。比如你如果配置該項為 1,那麼當你遍歷了 10 個結果就會發送 10 次請求,顯而易見這是比較消耗效能的,尤其是當單條的資料量較小的時候。
hbase-site.xml引數調優:
<property>
<name>hbase.client.scanner.caching</name>
<value>100</value>
</property>
複製程式碼
4 總結
本文解決了連個問題包括HBase資料塊編碼壓縮機制調優以及客戶端API 新版本最佳實踐
版權宣告:本套技術專欄是作者(秦凱新)平時工作的總結和昇華,通過從真實商業環境抽取案例進行總結和分享,並給出商業應用的調優建議和叢集環境容量規劃等內容,請持續關注本套部落格。QQ郵箱地址:[email protected],如有任何技術交流,可隨時聯絡。
參考文件及連線:Hbase不睡覺書 及官方文件資料
複製程式碼
秦凱新 於深圳 201801102252