前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >科学使用HBase Connection

科学使用HBase Connection

作者头像
大数据真好玩
发布2019-09-02 16:20:40
3.9K0
发布2019-09-02 16:20:40
举报
文章被收录于专栏:暴走大数据暴走大数据

为什么要写这篇小文章?因为偶然在技术交流群里看到了如下的问题。

这个问题的答案简单而不简单:HBase客户端是不需要维护连接池的,或者说,Connection对象已经帮我们做好了。但是,对Connection使用不当是HBase新手(包括很久很久之前的我自己)最容易犯的错误之一,常见错误用法有:

  • 每个线程开一个连接,线程结束时关闭;
  • 每次读写HBase时开一个连接,读写完毕后关闭;
  • 自行实现Connection对象的池化,每次使用时取出一个。

之前已经多次提到过,创建HBase连接是非常“贵”(expensive)的操作,并且创建过多的Connection会导致HBase拒绝连接。因此,最科学的方式就是在整个应用(进程)的范围内只维护一个共用的Connection,比如以单例的形式。在应用退出时,再关闭连接。

下面不妨来深入地看看Connection是怎么维护连接的,毕竟它与我们平时了解到的JDBC连接等有很大的不同。来看看org.apache.hadoop.hbase.client.Connection这个接口的源码。

/**
 * A cluster connection encapsulating lower level individual connections to actual servers and
 * a connection to zookeeper. Connections are instantiated through the {@link ConnectionFactory}
 * class. The lifecycle of the connection is managed by the caller, who has to {@link #close()}
 * the connection to release the resources.
 *
 * <p> The connection object contains logic to find the master, locate regions out on the cluster,
 * keeps a cache of locations and then knows how to re-calibrate after they move. The individual
 * connections to servers, meta cache, zookeeper connection, etc are all shared by the
 * {@link Table} and {@link Admin} instances obtained from this connection.
 *
 * <p> Connection creation is a heavy-weight operation. Connection implementations are thread-safe,
 * so that the client can create a connection once, and share it with different threads.
 * {@link Table} and {@link Admin} instances, on the other hand, are light-weight and are not
 * thread-safe.  Typically, a single connection per client application is instantiated and every
 * thread will obtain its own Table instance. Caching or pooling of {@link Table} and {@link Admin}
 * is not recommended.
 *
 * <p>This class replaces {@link HConnection}, which is now deprecated.
 * @see ConnectionFactory
 * @since 0.99.0
 */
@InterfaceAudience.Public
@InterfaceStability.Evolving
public interface Connection extends Abortable, Closeable {
  Configuration getConfiguration();

  Table getTable(TableName tableName) throws IOException;
  Table getTable(TableName tableName, ExecutorService pool)  throws IOException;

  public BufferedMutator getBufferedMutator(TableName tableName) throws IOException;
  public BufferedMutator getBufferedMutator(BufferedMutatorParams params) throws IOException;

  public RegionLocator getRegionLocator(TableName tableName) throws IOException;

  Admin getAdmin() throws IOException;

  @Override
  public void close() throws IOException;

  boolean isClosed();
}

这里特意把它的JavaDoc留下来了,因为这段文档的信息量比较大。我们可以得出如下结论:

  • Connection对象需要知道如何找到HMaster、如何在RegionServer上定位Region,以及感知Region的变动。所以,Connection需要同时与HMaster、RegionServer和ZK建立连接。
  • 创建Connection是重量级的,并且它是线程安全的。
  • 由Connection取得的Table和Admin对象是轻量级的,并且不是线程安全的,所以它们应该即用即弃。

我们几乎都是通过调用ConnectionFactory.createConnection()方法来创建HBase连接,该方法有多种重载,最底层的重载如下。

  static Connection createConnection(final Configuration conf, final boolean managed,
      final ExecutorService pool, final User user)
  throws IOException {
    String className = conf.get(HConnection.HBASE_CLIENT_CONNECTION_IMPL,
      ConnectionManager.HConnectionImplementation.class.getName());
    Class<?> clazz = null;
    try {
      clazz = Class.forName(className);
    } catch (ClassNotFoundException e) {
      throw new IOException(e);
    }
    try {
      Constructor<?> constructor =
        clazz.getDeclaredConstructor(Configuration.class,
          boolean.class, ExecutorService.class, User.class);
      constructor.setAccessible(true);
      return (Connection) constructor.newInstance(conf, managed, pool, user);
    } catch (Exception e) {
      throw new IOException(e);
    }
  }

该方法是通过反射实例化了ConnectionManager的内部类HConnectionImplementation对象。后面的代码就越发地复杂了(这就是为什么写源码阅读专题选了Spark而没选HBase),为了避免篇幅过长,只列出最关键的逻辑。

HBase中的RPC客户端由RpcClient接口的子类来实现,在HConnectionImplementation的构造方法中,调用RpcClientFactory.createClient()方法创建了它。

private RpcClient rpcClient;
this.rpcClient = RpcClientFactory.createClient(this.conf, this.clusterId, this.metrics);

这个方法也是通过反射来创建RPC客户端的(HBase里到处都是反射),实例化的类为BlockingRpcClient,它是AbstractRpcClient抽象类的子类。AbstractRpcClient中使用了一个名为PoolMap的结构来维护ConnectionId与连接池之间的映射关系,在构造方法中初始化。

protected final PoolMap<ConnectionId, T> connections;
this.connections = new PoolMap<>(getPoolType(conf), getPoolSize(conf));

PoolMap中的连接池的类型是Pool<T>,T则是连接对象的类型。Pool的具体类型由参数hbase.client.ipc.pool.type来确定,可选RoundRobinPool、ThreadLocalPool与ReusablePool三种,默认为第一种。连接池大小则由参数hbase.client.ipc.pool.size来确定,默认值为1。

ConnectionId也并不是一个简单的ID,而是服务器地址、用户ticket与服务名称三者的包装类。

  public ConnectionId(User ticket, String serviceName, InetSocketAddress address) {
    this.address = address;
    this.ticket = ticket;
    this.serviceName = serviceName;
  }

接下来注意到AbstractRpcClient.getConnection()方法。

  private T getConnection(ConnectionId remoteId) throws IOException {
    if (failedServers.isFailedServer(remoteId.getAddress())) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Not trying to connect to " + remoteId.address
            + " this server is in the failed servers list");
      }
      throw new FailedServerException(
          "This server is in the failed servers list: " + remoteId.address);
    }
    T conn;
    synchronized (connections) {
      if (!running) {
        throw new StoppedRpcClientException();
      }
      conn = connections.get(remoteId);
      if (conn == null) {
        conn = createConnection(remoteId);
        connections.put(remoteId, conn);
      }
      conn.setLastTouched(EnvironmentEdgeManager.currentTime());
    }
    return conn;
  }

该方法检查connections映射中是否已有对应ID的连接池,如果有,就直接返回;没有的话,就调用子类实现的createConnection()方法创建一个(类型为BlockingRpcConnection),将其放入connections映射并返回。由此可见,Connection对象确实替我们维护了所有的连接。

经由上面的简单分析,可以总结出如下的图。由此可见,Connection确实是重量级的玩意儿,有一个就够了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-08-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 大数据真好玩 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
TDSQL MySQL 版
TDSQL MySQL 版(TDSQL for MySQL)是腾讯打造的一款分布式数据库产品,具备强一致高可用、全球部署架构、分布式水平扩展、高性能、企业级安全等特性,同时提供智能 DBA、自动化运营、监控告警等配套设施,为客户提供完整的分布式数据库解决方案。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档