本文將介紹HBase的客戶端連線實現,並說明如何正確管理HBase的連線。
最近在搭建一個HBase的視覺化管理平臺,搭建完成後發現不管什麼查詢都很慢,甚至於使用api去listTable都要好幾秒。
經過一番排查發現,是每次請求的時候,都去臨時建立了一個connection,而建立connection非常耗時導致整體的rt上升。
因此,就深入瞭解了下如何正確管理HBase的connection,同時,也在優化過程中有些小細節的總結。
本文基於hbase 2.0.0版本的原始碼,github上3.0版本的原始碼已經有很大差異了,但是思想還是差不多的
1.HBase-client和HBase是如何連線的?
這個問題實際上在我之前的文章 深入HBase讀寫 中介紹過。
當HBase-client第一次請求讀寫的時候,需要三步走:
1)HBase-client從zk中獲取儲存meta table的位置資訊,知道meta table儲存在了哪個region server,然後快取這個位置資訊;
2)HBase-client會查詢這個儲存meta table的特定的region server,查詢meta table資訊,在table中獲取自己想要訪問的row key所在的region在哪個region server上。
3)客戶端直接訪問目標region server,獲取對應的row
所以,我們知道hbase-client實際上包含三部分連線:
- 跟zk連線,獲取相關元資訊
- 跟HMaster連線,做相關DDL操作
- 直接跟各個region server進行連線,進行增刪改查
2.HBase客戶端連線原理
常規寫法是這樣的
Connection connection = ConnectionFactory.createConnection(conf); try { Table table = connection.getTable(TableName.valueOf("tablename”)); // 插入資料 Put put = new Put(Bytes.toBytes("row")); put.addColumn(Bytes.toBytes("family"), Bytes.toBytes("qualifier"), Bytes.toBytes("value")); table.put(put); // 單行讀取 Get get = new Get(Bytes.toBytes("row")); Result res = table.get(get); // 刪除一行資料 Delete delete = new Delete(Bytes.toBytes("row")); table.delete(delete); }catch (IOException e) { //..... } finally { table.close(); connection.close(); }
我們不禁有這樣的疑問:
1)HBase沒有連線池嗎?
2)connection表示的是一個連線嗎?
3)connection每個執行緒都得建立嗎?執行緒安全嗎?
4)table每個執行緒都得建立嗎?執行緒安全嗎?
下面一一解答。
首先,Connection是執行緒安全的,而Table和Admin則不是執行緒安全的。
因此正確的做法是一個程式(或服務)使用一個Connection物件,而在不同的執行緒中使用單獨的Table和Admin物件。
Connection持有RpcClient,RpcClient管理了一個連線池poolMap
protected final PoolMap<ConnectionId, T> connections; //…. this.connections = new PoolMap<>(getPoolType(conf), getPoolSize(conf));
通過AbstractRpcClient的getConnection看到,連線T繼承RpcConnection,叫做NettyRpcConnection。
這裡順便通過getPoolType和getPoolSize看了下執行緒池的大小和型別。
在列舉類PoolType中有三種執行緒池型別Reusable, ThreadLocal, RoundRobin,使用者可以用hbase.client.ipc.pool.type指定執行緒池型別,通過hbase.client.ipc.pool.size指定執行緒池大小(預設是1)。
3.優化實踐
搞清楚上面的原理後,下面就可以開始優化我們的HBase管理平臺了。
只需要對每個HBase叢集的connection使用Map儲存下來,每次請求的時候拿出對應的connection進去相關操作即可。然後需要注意在系統退出的時候關閉所有的connection。
上程式碼:
public class ConnectionManager { private Map<String, Connection> connectionMap = new ConcurrentHashMap<>(); public Connection getConnection(String resourceId, Configuration configuration) { ResourceInfo resourceInfo = ResourceInfoCache.getResourceInfoByCache(resourceId); if (resourceInfo == null) { throw new IllegalArgumentException("error resourceid: " + resourceId); } String key = getClusterKey(resourceInfo); if (connectionMap.containsKey(key)) { return connectionMap.get(key); } synchronized (this) { //DCL檢查 if (connectionMap.containsKey(key)) { return connectionMap.get(key); } Connection connection = null; try { connection = ConnectionFactory.createConnection(configuration); } catch (IOException e) { return null; } connectionMap.put(key, connection); return connection; } } @PreDestroy public void doDestroy() { for (Map.Entry<String, Connection> entry : connectionMap.entrySet()) { Connection connection = entry.getValue(); if (connection != null) { try { connection.close(); } catch (IOException e) { //。。。。 } } } } }
這裡有幾個注意點:
- 將ConnectionManager註冊為bean,交給spring容器管理生命週期,同時保證單例。
- 使用@PreDestroy保證應用關閉時,能正確釋放所有連線,避免連線洩漏
- connectionMap使用ConcurrentHashMap保證執行緒安全
- DCL檢查,避免重複建立同一個connection,浪費資源;並且避免重複建立connection後,無法關閉導致連線洩漏。
在需要查詢時,只需要通過getConnection獲取已經存在的connection即可。
當然,如果是普通的應用使用HBase-client,一般只需要對一個HBase的叢集建立全域性唯一的一個Connection即可(一般交給spring容器管理),每次請求的時候,建立對應的Table進行CRUD。
看到這裡了,原創不易,點個關注、點個贊吧,你最好看了~
知識碎片重新梳理,構建Java知識圖譜:https://github.com/saigu/JavaKnowledgeGraph(歷史文章查閱非常方便)
掃碼關注我的公眾號“阿丸筆記”,第一時間獲取最新更新。同時可以免費獲取海量Java技術棧電子書、各個大廠面試題。