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及最新源碼的精彩發現。
 

1.連接

HTable是HBase的client,負責從meta表中找到目標數據所在的RegionServers,當定位到目標RegionServers后,client直接和RegionServers交互,而不比再經過master。
HTable實例並不是線程安全的。如果有過多的線程嘗試與單個HTable實例通信,那么寫緩沖器可能會崩潰,這是可以使用HTablePool類進行操作。
當需要創建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

// 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();

構建HTableInterface實現是非常輕量級的,並且資源是可控的。

注意:
HTablePool是HBase連接池的老用法,該類在0.94,0.95和0.96中已經不建議使用,在0.98.1版本以后已經移除。
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 com.bigdata.dbhbase096;

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 = "compute1";
    private static final String CLIENTPORT = "2181";
    private static final String TABLENAME = "minifiletable4";
    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();
        }
        
    }
}
View Code

 

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

// 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();
構建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:

  <groupId>org.apache.hbase</groupId>

40   <artifactId>hbase</artifactId>

   <packaging>pom</packaging>

 <version>2.0.0-SNAPSHOT</version>

  <name>HBase</name>

   <description>

     Apache HBase\99 is the Hadoop database. Use it when you need

     random, realtime read/write access to your Big Data.

    This project's goal is the hosting of very large tables -- billions of rows X millions of columns -- atop clusters

     of commodity hardware.

  </description>
HBase即將迎來2.0.0版本!!
HBase的下一個發布版是否會像Hadoop2.0那樣來一個華麗麗的升華,迎來眾多牛逼的新特性呢?
CHANGES.txt文檔中沒法得到最新的信息,最后一次更新還在2012年2月24日,看來開源大佬們也是愛編碼不愛寫文檔的主。。
 
參考:http://blog.csdn.net/u010967382/article/details/38046821


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM