HBase連接池 -- HTablePool被Deprecated以及可能原因是什麼

說明:
最近兩天在調研HBase的連接池,有了一些收穫,特此記錄下來。
本文先將官方文檔(http://hbase.apache.org/book.html)9.3.1.1節翻譯,方便大家閱讀,然後查閱了關鍵類HConnectionManager的Developer API(http://hbase.apache.org/devapidocs/index.html) 做了一些總結。 
最後介紹一些閱讀0.96、0.98及最新源碼的精彩發現。

歡迎轉載,請註明來源:
http://blog.csdn.net/u010967382/article/details/38046821

1.連接
HTable是HBase的client,負責從meta表中找到目標數據所在的RegionServers,當定位到目標RegionServers後,client直接和RegionServers交互,而不比再經過master。
HTable實例並不是線程安全的。當需要創建HTable實例時,明智的做法是使用相同的HBaseConfiguration實例,這使得共享連接到RegionServers的ZK和socket實例,例如,應該使用這樣的代碼:
HBaseConfiguration conf = HBaseConfiguration.create();
HTable table1 = new HTable(conf, "myTable");
HTable table2 = new HTable(conf, "myTable");
而不是這樣的代碼:
HBaseConfiguration conf1 = HBaseConfiguration.create();
HTable table1 = new HTable(conf1, "myTable");
HBaseConfiguration conf2 = HBaseConfiguration.create();
HTable table2 = new HTable(conf2, "myTable");


2.連接池
當面對多線程訪問需求時,我們可以預先建立HConnection,參見以下代碼:

Example 9.1. Pre-Creating a HConnection

<span style="background-color: rgb(255, 255, 0);"><span style="color: rgb(255, 0, 0);">// Create a connection to the cluster.
HConnection connection = HConnectionManager.createConnection(Configuration);
HTableInterface table = connection.getTable("myTable");
// use table as needed, the table returned is lightweight
table.close();
// use the connection for other access to the cluster
connection.close();</span></span>
構建HTableInterface實現是非常輕量級的,並且資源是可控的。

注意:
HTablePool是HBase連接池的老用法,該類在0.94,0.95和0.96中已經不建議使用,在0.98.1版本以後已經移除。

BTW:簡陋的官方文檔到此爲止。。。。Orz

3.HConnectionManager
該類是連接池的關鍵,專門介紹。
HConnectionManager是一個不可實例化的類,專門用於創建HConnection。
最簡單的創建HConnection實例的方式是HConnectionManager.createConnection(config),該方法創建了一個連接到集羣的HConnection實例,該實例被創建的程序管理。通過這個HConnection實例,可以使用HConnection.getTable(byte[])方法取得HTableInterface implementations的實現,例如:
            HConnection connection = HConnectionManager.createConnection(config);
            HTableInterface table = connection.getTable("tablename");
            try {
                // Use the table as needed, for a single operation and a single thread
            } finally {
                table.close();
                connection.close();
            }  

3.1構造函數
無,不可實例化。

3.2常用方法
(1)static HConnection  createConnection(org.apache.hadoop.conf.Configuration conf)
創建一個新的HConnection實例。
該方法繞過了常規的HConnection生命週期管理,常規是通過getConnection(Configuration)來獲取連接。調用方負責執行Closeable.close()來關閉獲得的連接實例。
推薦的創建HConnection的方法是:
        HConnection connection = HConnectionManager.createConnection(conf); 
        HTableInterface table = connection.getTable("mytable"); 
        table.get(...);
         ... 
        table.close(); 
        connection.close();

(2)public static HConnection getConnection(org.apache.hadoop.conf.Configuration conf)
根據conf獲取連接實例。如果沒有對應的連接實例存在,該方法創建一個新的連接。

注意:該方法在0.96和0.98版本中都被Deprecated了,不建議使用,但是在最新的未發佈代碼版本中又復活了!!!

3.3實例代碼
package fulong.bigdata.hbase;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.HConnection;
import org.apache.hadoop.hbase.client.HConnectionManager;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.util.Bytes;
public class ConnectionPoolTest {
    private static final String QUORUM = "FBI001,FBI002,FBI003";
    private static final String CLIENTPORT = "2181";
    private static final String TABLENAME = "rd_ns:itable";
    private static Configuration conf = null;
    private static HConnection conn = null;
    
    static{
        try {
            conf =  HBaseConfiguration.create();  
            conf.set("hbase.zookeeper.quorum"QUORUM);   
            conf.set("hbase.zookeeper.property.clientPort"CLIENTPORT);  
            conn = HConnectionManager.createConnection(conf);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }    
    public static void main(String[] args) throws IOException {
        HTableInterface htable = ConnectionPoolTest.conn.getTable(TABLENAME);
        try {
            Scan scan = new Scan();
            ResultScanner rs = htable.getScanner(scan);
            for (Result r : rs.next(5)) {
                for (Cell cell : r.rawCells()) {
                    System.out.println("Rowkey : " + Bytes.toString(r.getRow())
                            + "   Familiy:Quilifier : "
                            + Bytes.toString(CellUtil.cloneQualifier(cell))
                            + "   Value : "
                            + Bytes.toString(CellUtil.cloneValue(cell))
                            + "   Time : " + cell.getTimestamp());
                }
            }
        } finally {
            htable.close();
        }
        
    }
}

4.閱讀源碼的新發現
4.1消失的HConnectionManager.getConnection
從0.96和0.98版本HConnectionManager的源碼中可以看到
  static final Map<HConnectionKey, HConnectionImplementation> CONNECTION_INSTANCES;  
就是連接池,連接池中的每個連接用HConnectionKey來標識,然而,HConnectionManager源碼中所有涉及CONNECTION_INSTANCES的方法全都被Deprcated了。

我們來看已經被Deprecated的getConnection方法:
  /**
   * Get the connection that goes with the passed <code>conf</code> configuration instance.
   * If no current connection exists, method creates a new connection and keys it using
   * connection-specific properties from the passed {@link Configuration}; see
   * {@link HConnectionKey}.
   * @param conf configuration
   * @return HConnection object for <code>conf</code>
   * @throws ZooKeeperConnectionException
   */
  @Deprecated
  public static HConnection getConnection(final Configuration conf)
  throws IOException {
    HConnectionKey connectionKey = new HConnectionKey(conf);
    synchronized (CONNECTION_INSTANCES) {
      HConnectionImplementation connection = CONNECTION_INSTANCES.get(connectionKey);
      if (connection == null) {
        connection = (HConnectionImplementation)createConnection(conf, true);
        CONNECTION_INSTANCES.put(connectionKey, connection);
      } else if (connection.isClosed()) {
        HConnectionManager.deleteConnection(connectionKey, true);
        connection = (HConnectionImplementation)createConnection(conf, true);
        CONNECTION_INSTANCES.put(connectionKey, connection);
      }
      connection.incCount();
      return connection;
    }
  }  
該方法邏輯很簡單:
根據傳入的conf構建HConnectionKey,然後以HConnectionKey實例爲key到連接池Map對象CONNECTION_INSTANCES中去查找connection,如果找到就返回connection,如果找不到就新建,如果找到但已被關閉,就刪除再新建

我們來看HConnectionKey的構造函數:
  HConnectionKey(Configuration conf) {
    Map<String, String> m = new HashMap<String, String>();
    if (conf != null) {
      for (String property : CONNECTION_PROPERTIES) {
        String value = conf.get(property);
        if (value != null) {
          m.put(property, value);
        }
      }
    }
    this.properties = Collections.unmodifiableMap(m);
    try {
      UserProvider provider = UserProvider.instantiate(conf);
      User currentUser = provider.getCurrent();
      if (currentUser != null) {
        username = currentUser.getName();
      }
    } catch (IOException ioe) {
      HConnectionManager.LOG.warn("Error obtaining current user, skipping username in HConnectionKey", ioe);
    }
  }  
由以上源碼可知,接收conf構造HConnectionKey實例時,其實是將conf配置文件中的屬性賦值給HConnectionKey自身的屬性,換句話說,不管你new幾次,只要conf的屬性相同,new出來的HConnectionKey實例的屬性都相同
結論一:conf的屬性 --》 HConnectionKey實例的屬性

接下來,回到getConnection源碼中看到這樣一句話:
      HConnectionImplementation connection = CONNECTION_INSTANCES.get(connectionKey);
該代碼是以HConnectionKey實例爲key來查找CONNECTION_INSTANCES這個LinkedHashMap中是否已經包含了HConnectionKey實例爲key的鍵值對,這裏要注意的是,map的get方法,其實獲取的是key的hashcode,這個自己讀JDK源碼就能看到。
然而HConnectionKey已經重載了hashcode方法:
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    if (username != null) {
      result = username.hashCode();
    }
    for (String property : CONNECTION_PROPERTIES) {
      String value = properties.get(property);
      if (value != null) {
        result = prime * result + value.hashCode();
      }
    }
    return result;
  }  
在該代碼中,最終返回的hashcode取決於當前用戶名及當前conf配置文件的屬性。所以,只要conf配置文件的屬性和用戶相同,HConnectionKey實例的hashcode就相同!
結論二:conf的屬性 --》HConnectionKey實例的hashcode

再來看剛纔這句代碼:
      HConnectionImplementation connection = CONNECTION_INSTANCES.get(connectionKey);
對於get方法的參數connectionKey,不管connectionKey是不是同一個對象,只要connectionKey的屬性相同,那connectionKey的hasecode就相同,對於get方法而言,也就是同樣的key!!!
所以,可以得出結論三:conf的屬性 --》HConnectionKey實例的hashcode --》 get返回的connection實例
結論三換句話說說:
conf的屬性相同 --》 CONNECTION_INSTANCES.get返回同一個connection實例

然而,假設我們的HBase集羣只有一個,那我們的HBase集羣的conf配置文件也就只有一個(固定的一組屬性),除非你有多個HBase集羣另當別論。
在這樣一個機制下,如果只有一個conf配置文件,則連接池中永遠只會有一個connection實例!那“池”的意義就不大了!
所以,代碼中才將基於getConnection獲取池中物的機制Deprecated了,轉而在官方文檔中建議:
*******************************************************************************************************************
當面對多線程訪問需求時,我們可以預先建立HConnection,參見以下代碼:

Example 9.1. Pre-Creating a HConnection

<strong><span style="color: rgb(255, 0, 0); background-color: rgb(127, 255, 212);">// Create a connection to the cluster.
HConnection connection = HConnectionManager.createConnection(Configuration);
HTableInterface table = connection.getTable("myTable");
// use table as needed, the table returned is lightweight
table.close();
// use the connection for other access to the cluster
connection.close();</span></strong>
構建HTableInterface實現是非常輕量級的,並且資源是可控的。
*******************************************************************************************************************
(以上重新拷貝了一次官方文檔的翻譯)
如果大家按照官方文檔的建議做了,也就是預先創建了一個連接,以後的訪問都共享該連接,這樣的效果其實和過去的getConnection完全一樣,都是在玩一個connection實例

4.2 HBase的新時代
我查看了Git上最新版本的代碼(https://git-wip-us.apache.org/repos/asf?p=hbase.git;a=tree),發現getConnection復活了:
  /**
   * Get the connection that goes with the passed <code>conf</code> configuration instance.
   * If no current connection exists, method creates a new connection and keys it using
   * connection-specific properties from the passed {@link Configuration}; see
   * {@link HConnectionKey}.
   * @param conf configuration
   * @return HConnection object for <code>conf</code>
   * @throws ZooKeeperConnectionException
   */
  public static HConnection getConnection(final Configuration conf) throws IOException {
      return ConnectionManager.getConnectionInternal(conf);
  }  

這個不是重點,重點是最新版本代碼的pom:
39   <groupId>org.apache.hbase</groupId>
40   <artifactId>hbase</artifactId>
41   <packaging>pom</packaging>
42   <version>2.0.0-SNAPSHOT</version>
43   <name>HBase</name>
44   <description>
45     Apache HBase\99 is the Hadoop database. Use it when you need
46     random, realtime read/write access to your Big Data.
47     This project's goal is the hosting of very large tables -- billions of rows X millions of columns -- atop clusters
48     of commodity hardware.
49   </description>

HBase即將迎來2.0.0版本!!
HBase的下一個發佈版是否會像Hadoop2.0那樣來一個華麗麗的昇華,迎來衆多牛逼的新特性呢?
CHANGES.txt文檔中沒法得到最新的信息,最後一次更新還在2012年2月24日,看來開源大佬們也是愛編碼不愛寫文檔的主。。

參考 http://blog.csdn.net/u010967382/article/details/38046821
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章