HbaseTemplete 链接管理问题分析 | 8月更文挑战

780 阅读3分钟

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

前言

最近项目用到Hbase, 之前没怎么接触过,在搭建项目的过程中遇到的一些问题,记录一下。
项目是用Spring-boot搭建的,看着正好有Spring官方依赖,spring-data-hadoop-hbase,省的自己重新造轮子了。我们测试环境Hbase版本,HBase 1.2.0,但是用HbaseTemplete发现一个问题每次查询都很慢?

1. HbaseTemplete 读写Hbase api

Spring封装的框架用起来其实很简单,虽然支持的API比较少,但是对于我的场景来说已经够用。

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-hadoop-hbase</artifactId>
    <version>2.5.0.RELEASE</version>
</dependency>

Hbase Configuration 配置信息zk地址和端口号

@Configuration
@EnableConfigurationProperties
public class HbaseConfig {
    @Value("${hbase.zookeeper.quorum}")
    private String zkHosts;
    @Value("${hbase.zookeeper.property.clientPort}")
    private String zkPort;

    @Bean
    public HbaseTemplate hbaseTemplate(){
        org.apache.hadoop.conf.Configuration config = new org.apache.hadoop.conf.Configuration();
        config.set("hbase.zookeeper.quorum", zkHosts);
        config.set("hbase.zookeeper.property.clientPort", zkPort);
        return new HbaseTemplate(config);
    }
}

直接查询获取对象

public <T> T get(String rowKey, Class<T> clazz) {
    String tableName = clazz.getSimpleName();
    T obj = hbaseTemplate.execute(tableName, table->{
        Get get = new Get(Bytes.toBytes(rowKey));
        Result result = table.get(get);
        T bean = clazz.newInstance();
        BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
        PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
        for (PropertyDescriptor t : pds) {
            String pName = t.getName();
            if ("class".equals(pName)) {
                continue;
            }
            Class pClazz = t.getPropertyType();
            Object inObj = pClazz.newInstance();
            parseColumn(pName, inObj, result);
            PropertyUtils.setProperty(bean, pName, inObj);
        }
        return bean;
    });
    return obj;
}

但是发现一个问题每次查询耗时的时间都很长,然后DEBUG进去发现hbaseTemplate 每次都会去创建Connection.

2. HbaseTemplete 源码分析

我们从new HbaseTemplate(config) 开始分析继承关系很简单,

image.png

public HbaseTemplate(Configuration configuration) {
   // Configuration赋值
   setConfiguration(configuration);
   // 初始化bean之后属性校验
   afterPropertiesSet();
}

我们可以看到从Bean Configuration 初始化创建到创建HbaseTemplate对象都是没有链接信息的。 继续到hbaseTemplate.execute(...)方法 创建链接的核心方法:HTableInterface table = getTable(tableName);

image.png 但是最后都会调用Htable.close() 函数关闭链接。 我们其实已经找到查询耗时的原因,因为这个Connection的时间是非常长的。

我们在看下Hbase connection 源码

3. Hbase Client 原理分析

分析到这里我们其实就很好奇Hbase Connection是如何管理的呢?和我们关系型数据库链接有没有区别,是不是需要创建一个连接池?

image.png Hbase Client三个不同的角色

  1. ZooKeeper:主要用于获得meta-region位置,集群Id、master等信息。
  2. HBase Master:主要用于执行HBaseAdmin接口的一些操作,例如建表等。
  3. HBase RegionServer:用于读、写数据。

当HBase-client第一次请求读写的时候,需要三步走:

1)HBase-client从zk中获取保存meta table的位置信息,知道meta table保存在了哪个region server,然后缓存这个位置信息;
2)HBase-client会查询这个保存meta table的特定的region server,查询meta table信息,在table中获取自己想要访问的row key所在的region在哪个region server上。
3)客户端直接访问目标region server,获取对应的row

4. 封装Hbase Connection单例

public class HbaseConnectionFactory {
    /**
     * Logger
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(HbaseConnectionFactory.class);

    private HbaseConnectionFactory() {
    }
    /**
     * Hbase链接
     */
    private volatile static Connection connection = null;

    /**
     * DCL 模式保证hbase链接成功
     *
     * @param configuration
     * @return Connection
     */
    public static Connection getConnection(Configuration configuration) {
        if (connection == null) {
            synchronized (HbaseConnectionFactory.class) {
                if (connection == null) {
                    try {
                        connection = ConnectionFactory.createConnection(configuration);
                        LOGGER.info("HbaseConnectionFactory create connection success!");
                    } catch (IOException e) {
                        LOGGER.error("HbaseConnectionFactory create connection failed!", e);
                        // todo 对外抛异常
                    }
                }
            }
        }
        return connection;
    }
}
public class HbaseOperationUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(HbaseOperationUtil.class);

    private Connection connection = null;

    public HbaseOperationUtil(Configuration configuration) {
        this.connection = HbaseConnectionFactory.getConnection(configuration);
    }


    /**
     * 查询namespace中所有的表名
     *
     * @param namespace
     * @return List<String>
     */
    public List<String> listTables(String namespace) {
        List<String> tableNameList = new ArrayList<>();
        // 获取namespace中所有的表名
        TableName[] tableNames = new TableName[0];
        try {
            tableNames = connection.getAdmin().listTableNamesByNamespace(namespace);
            LOGGER.info("HbaseOperationUtil listTables tableNames:{}", JSON.toJSONString(tableNames));
        } catch (IOException e) {
            LOGGER.error("HbaseOperationUtil listTables error.", e);
        }

        for (TableName tableName : tableNames) {
            tableNameList.add(tableName.toString());
        }
        return tableNameList;
    }
}

最后就可以直接操作hbaseclient api了

@Test
public void test2() {
    String rowKey = StringUtils.substring(MD5Hash.getMD5AsHex("urOs1H".getBytes()), 0, 3);
    Result result = hbaseOperationUtil.get("mk:testTable", rowKey);
  
    for(Cell cell:result.rawCells()){
        System.out.print("行健: "+new String(CellUtil.cloneRow(cell)));
        System.out.print("列簇: "+new String(CellUtil.cloneFamily(cell)));
        System.out.print(" 列: "+new String(CellUtil.cloneQualifier(cell)));
        System.out.print(" 值: "+new String(CellUtil.cloneValue(cell)));
        System.out.println("时间戳: "+cell.getTimestamp());
    }
}

5. 各种依赖问题

lALPDg7mRZOlf_jNAgTNBKw_1196_516.png 图上面是我遇到的一些依赖问题,比如第一个guava版本的问题,swagger UI依赖的版本比较高20几了,但是hbase-client 客户端版本依赖比较低,这就很尴尬向上不兼容,向下也不兼容。这种问题解决方法,就是将自己所有依赖的包都添加进来打成一个shaded包。类似C或者C++中静态编译和动态编译。

6. Java访问带有Kerberos认证的Hbase库

本地连接测试需要配置3个选项

    1. 本地hosts文件
    1. krb5.conf kerberos的相关配置信息,KDC对应的IP,默认的realm等
    1. hbase.keytab 存储密码相关的信息

代码这里就不贴了,就是在configuration初始化加载bean的时候设置,对应的参数值

详细原理可以参考下面:kerberos认证原理

参考文档

HBase基本架构及原理
HBase连接池
spring-data管理HbaseTemplate connection 坑之源码分析
HBase Connection的使用
Java访问带有Kerberos认证的HBase
kerberos认证原理