• 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 HBase99 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
  • 相关阅读:
    OSS系列【springboot集成Minio】
    Linux系列【使用LVM对目录进行扩容】
    Linux系列【使用lvm对磁盘进行分区管理】
    Linux系列【根目录空间满了,如何快速扩容?】
    开发工具系列【finalshell连接时间长了,忘记密码,如何查看密码?】
    OSS系列【java调用Minio实现文件上传下载】
    Linux系列【解决vi命令不能用的问题】
    Linux系列【linux下boot目录满了怎么清理?】
    OSS系列【centos7单机搭建Minio】
    Windows系列【win10桌面的文件夹无法拖动,怎么办?】
  • 原文地址:https://www.cnblogs.com/joqk/p/3956167.html
Copyright © 2020-2023  润新知