1. 程式人生 > 其它 >HBase開發篇 _ 一文說清HBase Connection的使用

HBase開發篇 _ 一文說清HBase Connection的使用

目錄導讀

目錄

1. 引言

對於很多初次接觸HBase的夥伴,在使用其客戶端API來構建Connection連線物件的時候,有可能會陷入以下幾個誤區。

  • 類比druid等mysql資料庫連線池,自己封裝一個Connection物件的資源池,每次使用都從池中取出一個Connection物件;
  • 在多執行緒的工作環境中,每個執行緒都會建立一個Connection物件,造成Connection物件被頻繁建立,快速消耗;
  • 每次訪問HBase的時候臨時建立一個Connection物件,使用完之後呼叫close方法,關閉連線;

參考HBase技術社群文章連線HBase的正確姿勢中對Connection的原始碼分析,我們可知:

  1. HBase客戶端中的Connection物件並不是簡單對應一個socket連線。
  2. HBase客戶端的Connection包含了對Zookeeper、HBase Master、HBase RegionServer三種socket連線的封裝。因此,Connection物件每次被創建出來的開銷是很大的,使用完畢之後斷開,會帶來嚴重的效能損耗。
  3. 在HBase中Connection類已經實現了對連線的管理功能,所以我們不需要自己在Connection之上再做額外的管理。另外,Connection是執行緒安全的,而Table和Admin則不是執行緒安全的,因此正確的做法是,在一個JVM程序中共用一個Connection物件,而在不同的執行緒中使用單獨的Table和Admin物件。

在併發系統中,多個執行緒每個都去建立一個Connection物件,那麼你會馬上面臨如下窘境:

上圖所示的是HBase Zookeeper的連線被大量佔用,某一個客戶端連線Zookeeper的連線數超過了60,大量連線建立的請求被拒絕。這樣會增加ZK的壓力,也會導致客戶端系統效能急劇下降。

2. 單例模式維護HBase的Connection

在普通的Java程式中,如果沒有併發場景的存在,我們可以簡單地使用下面這種方式來建立Connection的物件。

///所有程序共用一個connection物件
connection = ConnectionFactory.createConnection(config);
...
///每個執行緒使用單獨的table物件
Table table = connection.getTable(TableName.valueOf("test"));           
try {
   ...
} finally {
   table.close();
}

然而,在多執行緒中的場景中,我們又該如何來管理我們的連線物件呢?聰明的你,一定會首先想到單例模式。

單例模式是一種簡單的設計模式,它的優點是隻生成一個例項,可以保證在同一個JVM程序中,物件只存在一個。所以能節約系統資源,減少效能開銷,同時能夠嚴格控制使用者對它的訪問。

關於單例模式的實現,我知道的至少有十種,常見的有餓漢式懶漢式雙重檢測鎖式靜態內部類式列舉單例

上述實現方式各有優劣,但使用時需要注意,執行緒是否安全和平衡效率。關於其具體的實現細節,網上有很多文章可供參考,這裡不做詳細羅列,只舉例雙重檢測鎖式的單例實現方式,在管理HBase客戶端連線物件中的應用。具體實現程式碼:

public class SingleConnectionFactory {
    private static final Logger LOGGER = LoggerFactory.getLogger(SingleConnectionFactory.class);
    
  	private volatile static Connection connection;
    
  	private SingleConnectionFactory() {}
    
  	public static Connection getConnection(Configuration configuration) {
        if (connection == null) {
            synchronized (SingleConnectionFactory.class) {
                if (connection == null) {
                    try {
                        connection = ConnectionFactory.createConnection(configuration);
                        LOGGER.info("the connection of HBase is created successfully.");
                    } catch (IOException e) {
                        LOGGER.error("the connection of HBase is created failed.");
                        throw new HBaseSdkConnectionException(e);
                    }
                }
            }
        }
        return connection;
    }
}

上述單例Connection物件建立工廠類,在多執行緒的測試環境中亦可保證同一個JVM程序中,只有一個Connection物件被創建出來,觀察ZK客戶端連線數監控,幾乎無波動。

3. 多例模式中維護HBase的Connection

cuckoo-cloud(布穀鳥,微服務版大資料元件統一管理平臺,目前已整合hbase-manager和kafka-manager的功能)中遷移我們的hbase-manager應用時,遇到這樣一個問題。

cuckoo-cloud平臺上會管理我們的多個HBase叢集,這些HBase叢集的連線資訊被儲存進資料庫中,可以進行動態維護。在設計HBase的連線管理功能時,如果採用單例模式,那麼,無論切換任意一個叢集,始終操作的是最開始被初始化連線的叢集;如果放棄單例模式,Connection物件又會被濫用。

所以,我需要一個容器,它能儲存不同叢集的連線物件,且每個物件在一個JVM程序中只保留一個。

我曾嘗試建立ThreadLocal變數,利用同一個ThreadLocal所包含的物件,在不同的Thread中保留不同的副本。這樣,每一個Thread內都有自己的例項副本,且該副本只能由當前Thread來使用,即保證了在多執行緒環境中只保留一個物件,又規避了多執行緒環境中的併發安全問題,可折騰了一圈,最終以失敗告終。(不知是我學藝不精,還是ThreadLocal不適合這種應用場景?)

接著又嘗試構建guava單例快取池,利用快取池中key的唯一性來保證多個被儲存的物件唯一。興致勃勃寫好程式碼,一上線測試,問題未有半點改善。

關於ThreadLocal和guava快取池的應用場景和相關細節,還請參考網上的優秀文章。

最後想到了多例模式(一開始多例模式是腦海中虛構的概念,我想,既然有單例,那就應該存在多例。一百度,還真有這種設計模式存在)。可是能搜到的網上貼出來的多例實現程式碼都是基於建立多個固定物件的,但我需要的是動態建立物件,最終的實現效果如下:

public class MultipleConnectionFactory {
    private static final Logger LOGGER = LoggerFactory.getLogger(MultipleConnectionFactory.class);

    private volatile static Map<String, Connection> connectionMap;

    private MultipleConnectionFactory() {

    }

    public static Connection getConnection(Configuration configuration) {
        String cluster = configuration.get(HConstants.ZOOKEEPER_QUORUM);

        if (connectionMap == null || !connectionMap.containsKey(cluster)) {
            synchronized (MultipleConnectionFactory.class) {
                if (connectionMap == null || !connectionMap.containsKey(cluster)) {
                    try {
                        if (connectionMap == null) {
                            connectionMap = new HashMap<>(2);
                        }
                        if (!connectionMap.containsKey(cluster)) {
                            Connection connection = ConnectionFactory.createConnection(configuration);
                            LOGGER.info("the connection of HBase cluster [{}] is created successfully.", cluster);
                            connectionMap.put(cluster, connection);
                        }
                    } catch (IOException e) {
                        LOGGER.error("the connection of HBase is created failed.");
                        throw new HBaseSdkConnectionException(e);
                    }
                }
            }
        }
        return connectionMap.get(cluster);
    }
}

類比雙重檢測鎖式的單例實現方式,在此使用ZK的連線地址為key,來保證每一個叢集的連線物件在同一個JVM程序中唯一存在。

4. ConnectionFactory.createConnection方法中的連線池引數

ConnectionFactory.createConnection(Configuration conf, ExecutorService pool, User user)

呼叫此方法時,可以傳入三個引數,conf是連線配置相關,user是使用者認證相關,在此不必細說。ExecutorService pool的作用是什麼呢?

參考文章中的解釋是,HBase客戶端連線池。

HBase訪問一條資料的過程中,需要連線Zookeeper、HBase Master、HBase RegionServer,HBase客戶端的Connection包含了對以上三種socket連線的封裝。

Connection物件和實際的socket連線之間的對應關係如下圖:

在HBase客戶端的程式碼中,真正對應socket連線的是RpcConnection物件。HBase中使用PoolMap這種資料結構來儲存客戶端到HBase伺服器之間的連線。PoolMap封裝了ConcurrentHashMap<>的結構,key是ConnectionId(封裝了伺服器地址和使用者ticket),value是一個RpcConnection物件的資源池。當HBase需要連線一個伺服器時,首先會根據ConnectionId找到對應的連線池,然後從連線池中取出一個連線物件。

HBase中提供了三種資源池的實現,分別是Reusable,RoundRobin和ThreadLocal。具體實現可以通過hbase.client.ipc.pool.type配置項指定,預設為Reusable。連線池的大小也可以通過hbase.client.ipc.pool.size配置項指定,預設為1。

關於pool的用意,扒拉了半天原始碼,巢狀實在太深也太複雜,為了保證文章完整性,只能引用參考文章,(如有侵權,請私信我刪除)

如果對ExecutorService pool有特殊需求,可以構造相應的連線池,通過配置合理的連線池的大小,來提升系統請求HBase叢集的效能。

5. 總結

上述內容從實際的應用場景出發,全面介紹了HBase客戶端連線的正確使用,以規避可能存在的風險。但是,在有些使用場景中,就算是使用單例了模式來建立Connection的連線物件,也會有ZK連線耗盡的風險。

例如:在Flink或Spark等分散式計算引擎中,如果executor或併發數設定的過高,也會佔用大量的ZK連線,畢竟,每個計算節點上的一個計算程式就是一個單獨的JVM程序。此時,可以使用HBase Thrift的池化連線技術,具體可參考,HBase實踐篇 | 為HBase的Thrift 客戶端API設計連線池

關於HBase客戶端API的二次開發封裝,也可以參考hbase-sdk

https://gitee.com/weixiaotome/hbase-sdk

裡面封裝了HBaseAdminTemplateHBaseTemplate(ORM框架)HBaseSqlTemplate(HBase SQL API)Spring Boot整合HBase Thrift連線池API等功能,詳情請參考hbase-sdk的使用文件。