HBase 工具 | HBase SDK 3.0.0发布,让HBase的使用变得更简捷

共 45013字,需浏览 91分钟

 ·

2022-12-17 18:52

1. hbase-sdk 介绍

hbase-sdk是基于 hbase-client 和 hbase-thrift 的原生 API 封装的一款轻量级的 HBase 客户端操作工具包。针对 HBase 各版本 API(1.x~2.x)之间的差异,在其上制定了一层统一的接口。在兼容原有功能的同时,还为其扩展了 ORM 的特性。除此之外,hbase-sdk还提供了以类 SQL 的方式读写 HBase 表中数据的能力。

2. hbase-sdk 的优势

hbase-sdk 基于 HBase 的原生 API,封装了对 HBase 表及其数据的 DML 和 DDL 操作。同时,利用其 ORM 的特性,可实现 Java 数据实体类与 HBase 表进行绑定,与原生的 API 相比,其优势总结如下:
  1. 基于原生 API 中 Get/Put/Scan 等功能,重新定义了统一的操作接口,屏蔽了底层 HBase 各版本原生 API 间的差异,在面对集群跨大版本升级时,业务伙伴只需对应升级自己项目中的hbase-client的版本即可。
  2. 简化了原生 API 较为复杂的调用方式,在 ORM 特性的加持之下,没有 HBase API 调用经验的开发伙伴,也能快速完成对 HBase 表数据的读写业务。
  3. 对 HBase 的原生 thrift api 进行了池化封装,类似于 Jedis-pool,增强了 thrift api 生产环境中使用的稳定性。
  4. 使用 spring-boot-starter-hbase 可快速与 SpringBoot 无缝集成。
  5. 提供了类 SQL 的方式——HQL 来读写 HBase 表中的数据,进一步简化了原生 API 的使用方式。
API 文档地址: https://weixiaotome.gitee.io/hbase-sdk/如果你也觉得这个项目不错,可以帮忙点个star 。

3. 现有特性与未来规划

  • [d] 定义了统一的接口规范,消除了 HBase 不同版本原生 API 之间的差异
  • [d] ORM 特性,以注解方式快速实现表、列簇、字段模型与 java 实体类进行绑定
  • [d] 对 HBase 的原生 thrift API 进行池化封装,提供了 HBaseThriftPool 的功能
  • [d] HQL,以类 SQL 的形式读写 HBase 表中的数据
  • [d] 利用 spring-boot-starter-hbase 可无缝与 SpringBoot 集成
  • [ ] HBatis,类似于 myBatis,提供配置文件管理 HQL 的功能(规划中)
  • [ ] 客户端熔断,提供客户端 API 级别的主备集群切换,保障请求 HBase 接口服务的高可用(规划中)
  • [ ] thrift 连接池中连接数的动态扩所容能力(规划中)

4. 仓库地址

https://github.com/CCweixiao/hbase-sdk
https://gitee.com/weixiaotome/hbase-sdk
两边仓库地址是同步更新的,欢迎各位大佬在Github上帮忙点个star 。

5. 编译指南

克隆项目到本地,导入到 IDEA 中,首次加载项目,会从远程仓库拉取项目所需的依赖,还请耐心等待。
cd hbase-sdk
mvn clean install -Phbase-1.2 # hbase-client:1.2.x
mvn clean install -Phbase-1.4 # hbase-client:1.4.x
mvn clean install -Phbase-2.2 # hbase-client:2.x.x

或者跳过findbugs、checkstyle等检查

mvn clean package -Dmaven.test.skip=true -Dfindbugs.skip -Dcheckstyle.skip -Dmaven.javadoc.skip -Phbase-1.2

mvn clean package -Dmaven.test.skip=true -Dfindbugs.skip -Dcheckstyle.skip -Dmaven.javadoc.skip -Phbase-1.4

mvn clean package -Dmaven.test.skip=true -Dfindbugs.skip -Dcheckstyle.skip -Dmaven.javadoc.skip -Phbase-2.2
hbase-sdk的hbase-sdk-adapter模块下的各个子模块中已引入了具体的 hbase-shaded-client 的依赖,如有需要可以自行扩展你想使用的 hbase-client 的版本。

6. 项目结构概览

project
项目结构说明,主要介绍核心模块的作用。
├── hbase-sdk-adapter                       # HBase各版本不兼容API的adapter
│   ├── hbase-sdk-adapter-common
│   ├── hbase-sdk-adapter_1.2
│   ├── hbase-sdk-adapter_1.4
│   ├── hbase-sdk-adapter_2.2
│   └── pom.xml
├── hbase-sdk-common                            # 通用工具或接口
├── hbase-sdk-dsl                                   # HBase SQL
├── hbase-sdk-examples
│   ├── hbase-sdk-example
│   └── spring-boot-starter-hbase-example
├── hbase-sdk-template                      # hbase操作模版类API
├── hbase-sdk-thrift                            # HBase thrift API
└── spring-boot-starter-hbase           # spring-boot-starter-hbase
hbase-sdk的 UML 类图概览:
api-uml

7. 快速开始

hbase-sdk 的各个版本完成开发测试之后,都会发布到 maven 中央仓库之中,只是最新版本的代码有一定的延迟。如果你想在第一时间体验新的功能,可以选择克隆 Gitee 或 Github 仓库中的源码,在本地编译并运行测试用例。
hbase-sdk 如果你想在本地进行开发,请确保已经安装了 Java8 和 maven3.6+。同时建议在本地部署一个可连通的 HBase 开发环境。建议使用 docker 来快速搭建一个 HBase 的单机环境,可以参考博客:https://blog.csdn.net/feinifi/article/details/121174846
hbase-sdk 开发所用的工具为 IDEA,所以也极力推荐导入项目到 IDEA 中。

7.1 在普通项目中引入 hbase-sdk-template 依赖

创建一个基础的 Maven 工程,HBase SDK 已适配了 hbase-client 的 1.2/1.4/2.x 版本 API。
如果你的 HBase 版本是 1.2.x,可以使用如下依赖。
<dependency>
    <groupId>com.github.CCweixiao</groupId>
    <artifactId>hbase-sdk-template_1.2</artifactId>
    <version>3.0.0</version>
</dependency>
如果你的 HBase 版本是 1.4.x,可以使用如下依赖。
<dependency>
    <groupId>com.github.CCweixiao</groupId>
    <artifactId>hbase-sdk-template_1.4</artifactId>
    <version>3.0.0</version>
</dependency>
如果你的 HBase 版本是 2.x.x,可以使用如下依赖。
<dependency>
    <groupId>com.github.CCweixiao</groupId>
    <artifactId>hbase-sdk-template_2.2</artifactId>
    <version>3.0.0</version>
</dependency>
hbase-sdk目前最新的版本是3.0.0。你可以在 maven 中央仓库中搜索 CCweixiao 来获取 hbase-sdk 相关 jar 包的最新版本。https://mvnrepository.com/artifact/com.github.CCweixiao
或者在 git 仓库中查看最新的 release 版本。
当然,如果你想重新编译,扩展你需要的功能,也可以选择下载源码,修改项目根 pom.xml 文件中的hbase.version,按照编译指南中的编译命令来操作。

7.2 在SpringBoot 项目中引入 spring-boot-starter-hbase 依赖

创建一个基于Maven的 spring boot 工程,在项目 pom.xml 中加入 spring-boot-starter-hbase 的依赖。
如果你的 HBase 版本是 1.2.x,可以使用如下依赖。
<dependency>
    <groupId>com.github.CCweixiao</groupId>
    <artifactId>spring-boot-starter-hbase_1.2</artifactId>
    <version>3.0.0</version>
</dependency>
如果你的 HBase 版本是 1.4.x,可以使用如下依赖。
<dependency>
    <groupId>com.github.CCweixiao</groupId>
    <artifactId>spring-boot-starter-hbase_1.4</artifactId>
    <version>3.0.0</version>
</dependency>
如果你的 HBase 版本是 2.x.x,可以使用如下依赖。
<dependency>
    <groupId>com.github.CCweixiao</groupId>
    <artifactId>spring-boot-starter-hbase_2.2</artifactId>
    <version>3.0.0</version>
</dependency>

7.3 引入hbase-client的依赖

hbase-sdk没有把 hbase-client 的依赖打到自己的包中,所以,除了引入hbase-sdk的相关依赖之外,你还需要引入hbase-client的依赖,hbase-client的版本目前支持1.2.x1.4.x2.x.x,请按需引入。(建议使用 hbase-shaded-client)。
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-shaded-client</artifactId>
    <version>1.2.0</version>
</dependency>
or
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-shaded-client</artifactId>
    <version>1.4.3</version>
</dependency>
or
<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-shaded-client</artifactId>
    <version>2.2.6</version>
</dependency>

7.5 HBase 连接配置

普通 Java 项目
普通认证
// 普通认证
Properties properties = new Properties();
properties.setProperty("hbase.zookeeper.quorum""myhbase");
properties.setProperty("hbase.zookeeper.property.clientPort""2181");
// 请按需引入一些额外所需的客户端配置
properties.put("hbase.client.retries.number""3");
Kerberos 认证
Properties properties = new Properties();
properties.put("hbase.zookeeper.quorum""zk_host1,zk_host1,zk_host1");
properties.put("hbase.zookeeper.property.clientPort""2181");
properties.put("hbase.security.authentication""kerberos");
properties.put("kerberos.principal""hbase@HADOOP.LEO.COM");
properties.put("keytab.file""/etc/hbase/conf/hbase.keytab");
properties.put("hbase.regionserver.kerberos.principal""hbase/_HOST@HADOOP.LEO.COM");
properties.put("hbase.master.kerberos.principal""hbase/_HOST@HADOOP.LEO.COM");
// 指定kdc服务相关的配置方式有如下两种:
// 方式一:指定krb5.conf路径
properties.put("java.security.krb5.conf""/etc/krb5.conf");
// 方式二:指定java.security.krb5.realm和java.security.krb5.kdc
properties.put("java.security.krb5.realm""HADOOP.LEO.COM");
properties.put("java.security.krb5.kdc""你自己的kdc服务地址");
// 一些额外的客户端参数
properties.put("hbase.client.retries.number""3");

IHBaseAdminTemplate adminTemplate adminTemplate = new HBaseAdminTemplateImpl.Builder().properties(properties).build();
System.out.println(adminTemplate.listTableNames());
Spring Boot 项目
普通认证
application.yaml
spring:
  datasource:
    hbase:
      zk-host-list: zk_host1,zk_host2,zk_host3
      zk-client-port: 2181 # (可选,默认2181)
      dfs-root-dir: /hbase # (可选,默认/hbase)
      zk-node-parent: /hbase  # (可选,默认/hbase)
      security-auth-way: simple # (可选,默认simple)
      client-properties: hbase.client.retries.number=3;key1=value2
server:
  port: 8088
Kerberos 认证
spring:
  datasource:
    hbase:
      zk-host-list: zk_host1,zk_host2,zk_host3
      zk-client-port: 2181 # (可选,默认2181)
      dfs-root-dir: /hbase # (可选,默认/hbase)
      zk-node-parent: /hbase  # (可选,默认/hbase)
      security-auth-way: kerberos
      kerberos-principal: hbase@HADOOP.LEO.COM
      keytab-file-path: /etc/hbase/conf/hbase.keytab
      rs-kerberos-principal: hbase/_HOST@HADOOP.LEO.COM
      master-kerberos-principal: hbase/_HOST@HADOOP.LEO.COM
      # krb5-conf-path和krb5-realm、krb5-kdc-server-addr任选一种配置KDC的方式
      krb5-conf-path: /etc/krb5.conf
      krb5-realm:
      krb5-kdc-server-addr:
      client-properties: hbase.client.retries.number=3;key1=value2
server:
  port: 8088

7.6 创建 API 操作模版实现类

普通项目
// 表中数据读写API的操作模版类
IHBaseTableTemplate tableTemplate = new HBaseAdminTemplateImpl.Builder()
                .properties(properties).build();
// 管理员API操作模版类
IHBaseAdminTemplate adminTemplate = new HBaseAdminTemplateImpl.Builder()
                .properties(properties).build();
// HQL API操作模版类
IHBaseSqlTemplate sqlTemplate = new HBaseSqlTemplateImpl.Builder()
                .properties(properties).build()
SpringBoot 项目
@Autowired依赖注IHBaseTableTemplate、IHBaseAdminTemplate、IHBaseSqlTemplate
@Service
public class UserService {
    @Autowired
    private IHBaseTableTemplate tableTemplate;
    @Autowired
    private IHBaseAdminTemplate adminTemplate;
    @Autowired
    private IHBaseSqlTemplate sqlTemplate;
}

8. 集群管理

HBaseAdminTemplate 封装了 HBaseAdmin 的常用操作,比如 namespace 的管理、表的管理、以及快照管理等等,后续这些 API 将会更加完善。
admin-api

8.1 创建 namespace

@Test
public void createNameSpace() {
    NamespaceDesc namespaceDesc = new NamespaceDesc();
    namespaceDesc.setNamespaceName("test_nn");
    namespaceDesc.addNamespaceProp("createdBy""leojie");
    adminTemplate.createNamespaceAsync(namespaceDesc);
}

8.2 创建表

@Test
public void testCreateTable() {
    ColumnFamilyDesc f1 = new ColumnFamilyDesc.Builder()
            .familyName("f1")
            .build();
    ColumnFamilyDesc f2 = new ColumnFamilyDesc.Builder()
            .familyName("f2")
            .timeToLive(3600)
            .versions(3)
            .build();
    HTableDesc tableDesc = new HTableDesc.Builder()
            .defaultTableDesc("test_nn:test_table")
            .maxFileSize(51400000L)
            .addTableProp("hbase.hstore.block.storage.policy""HOT")
            .addColumnFamilyDesc(f1)
            .addColumnFamilyDesc(f2)
            .build();
    adminTemplate.createTable(tableDesc);
}

8.3 更多操作

可以参考相关 API 文档或hbase-template模块下的测试用例

9. 数据读写

类似于 Hibernate,你也可以使用 hbase-sdk 框架所提供的 ORM 特性,来实现对 HBase 表中数据的读写操作。
api-data

9.1 创建数据模型类

public class CityTag {
    private String tagName;

    public CityTag(String tagName) {
        this.tagName = tagName;
    }
        // 省略Getter/Setter/toString
}
@HBaseTable(namespaceName = "default", tableName = "t2", defaultFamilyName = "info")
public class CityModel {
    @HBaseRowKey
    private String cityId;
    private String cityName;
    private String cityAddress;
    @HBaseColumn(familyName = "detail")
    private Integer cityArea;
    @HBaseColumn(familyName = "detail", toUpperCase = true)
    private Integer totalPopulation;
    @HBaseColumn(familyName = "detail", columnName = "cityTagList")
    private List<CityTag> cityTagList;
    // 省略Getter/Setter/toString
}
@HBaseTable注解用于定义 HBase 的表信息
@HBaseTable(namespaceName = "default", tableName = "t2", defaultFamilyName = "info")
1)namespaceName:用于指定该表的命名空间,默认值:default
2)tableName:用于指定该表的表名,如果不指定,表名则为类名的组合单词拆分转小写加'_'拼接,如:CityModel 对应的表名为:city_model。3)defaultFamilyName:如果有字段不特别配置(@HBaseRowKey 注解中的 familyName)列簇名,则使用此处配置的默认列簇名。
@HBaseRowKey注解用于定义某一个属性字段是用作存储 rowKey 数据的,是必须要设置的,如:
@HBaseRowKey
private String cityId;
该注解表示 cityId 字段为 rowKey。
@HBaseColumn注解用于定义 HBase 的列簇和列名信息,如:
@HBaseColumn(familyName = "detail", columnName = "TOTAL_POPULATION",  toUpperCase = true)
private Integer totalPopulation;
1)familyName:指定列簇名,如果不指定,则使用 defaultFamilyName 配置的列簇名。
2)columnName:指定列名,不指定则默认使用字段名的组合单词拆分转小写加'_'拼接,如:isVip,对应的字段名是:is_vip
3)toUpperCase:定义字段名是否转大写,如:isVip -> IS_VIP,默认值:false,不做转换。

9.2 保存数据

@Test
 public void testSaveUser() {
     CityModel cityModel = new CityModel();
     cityModel.setCityId("10001");
     cityModel.setCityName("上海");
     cityModel.setCityAddress("上海市");
     cityModel.setCityArea(10000);
     cityModel.setTotalPopulation(200000);                                   cityModel.setCityTagList(tagNameList.stream().map(CityTag::new).collect(Collectors.toList()));
     CityModel city = tableTemplate.save(cityModel);
}
除此之外,保存数据时也可以不必构造数据模型类,而选择直接构造 map 数据模型。
@Test
public void testToSave() {
    Map<String, Object> data = new HashMap<>();
    data.put("info1:addresses", Arrays.asList("广州""深圳"));
    data.put("info1:username""leo");
    data.put("info1:age"18);
    data.put("INFO2:IS_VIP"true);
    data.put("info1:pay"10000.1d);
    data.put("info1:create_by""tom");
    data.put("info1:create_time", System.currentTimeMillis());
    Map<String, Object> contactInfo = new HashMap<>(2);
    contactInfo.put("email""2326130720@qq.com");
    contactInfo.put("phone""18739577988");
    contactInfo.put("address""浦东新区");
    data.put("info1:contact_info", contactInfo);
    hBaseTemplate.save("TEST:LEO_USER""10002", data);
    System.out.println("用户数据保存成功!");
}

9.3 批量保存数据

@Test
public void testToSaveBatch() {
    Map<String, Map<String, Object>> data = new HashMap<>(2);

    Map<String, Object> data1 = new HashMap<>(3);
    data1.put("info1:username""kangkang");
    data1.put("info1:age"18);
    data1.put("INFO2:IS_VIP"true);

    Map<String, Object> data2 = new HashMap<>(3);
    data2.put("info1:username""jane");
    data2.put("info1:age"18);
    data2.put("INFO2:IS_VIP"false);

    data.put("12003", data1);
    data.put("11004", data2);

    hBaseTemplate.saveBatch("TEST:LEO_USER", data);
    System.out.println("用户数据批量保存成功!");
}

9.4 根据 RowKey 查询

@Test
public void testGetJavaBean() {
    Optional<CityModel> a10001 = tableTemplate.getRow("a10001", CityModel.class);
    Optional<CityModel> a10001F = tableTemplate.getRow("a10001""info", CityModel.class);
    System.out.println(a10001.orElse(new CityModel()));
    System.out.println(a10001F);
}
查询数据返回 Map
@Test
public void testGetRowToMap() {
    Map<String, String> d1 = tableTemplate.getRowToMap("t1""1001"true);
    JSONArray objects = JSON.parseArray(d1.get("f3:tags"));
    Map<String, String> d2 = tableTemplate.getRowToMap("t1""1002"false);
    List<String> rows = new ArrayList<>(2);
    rows.add("1001");
    rows.add("1002");
    Map<String, Map<String, String>> d3 = tableTemplate.getRowsToMap("t1", rows, true);
    System.out.println(d1);
    System.out.println(d2);
    System.out.println(d3);
}

9.5 自定义 RowMapper

如果你需要自定义转换 Result 对象,则可以使用 RowMapper
@Test
public void testGetRowMapper() {
    CityModel cityModel = tableTemplate.getRow("t2""a10001"new RowMapper<CityModel>() {
        @Override
        public <R> CityModel mapRow(R r, int rowNum) throws Exception {
            Result result = (Result) r;
            if (result == null) {
                return null;
            }
            CityModel c = new CityModel();
            c.setCityId(Bytes.toString(result.getRow()));
            c.setCityName(Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("city_name"))));
            c.setCityAddress(Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("city_address"))));
            c.setCityArea(Bytes.toInt(result.getValue(Bytes.toBytes("detail"), Bytes.toBytes("city_area"))));
            c.setTotalPopulation(Bytes.toInt(result.getValue(Bytes.toBytes("detail"), Bytes.toBytes("TOTAL_POPULATION"))));
            String value = Bytes.toString(result.getValue(Bytes.toBytes("detail"), Bytes.toBytes("cityTagList")));
            JSONArray jsonArray = JSON.parseArray(value);
            List<CityTag> tags = new ArrayList<>(jsonArray.size());
            for (int i = 0; i < jsonArray.size(); i++) {
                tags.add(jsonArray.getObject(i, CityTag.class));
            }
            c.setCityTagList(tags);
            return c;
        }
    }).orElse(new CityModel());
    System.out.println(cityModel);
}

9.6 scan 查询

普通 scan 查询
@Test
public void testScanWithLimit() {
    ScanQueryParamsBuilder scanQueryParamsBuilder = new ScanQueryParamsBuilder.Builder()
                .familyName("info")
                .columnNames(Arrays.asList("city_name""city_address""cityTagList"))
                .limit(2)
                .build();
    List<CityModel> cityModels = tableTemplate.scan(scanQueryParamsBuilder, CityModel.class);
    System.out.println(cityModels);
}
根据起止 row 查询数据
@Test
public void testScanWithStartAndEndRow() {
    // 不包含endRow的数据
    ScanQueryParamsBuilder scanQueryParamsBuilder = new ScanQueryParamsBuilder.Builder()
                .startRow("a10001")
                .stopRow("a10002")
                .build();
    List<CityModel> cityModels = tableTemplate.scan(scanQueryParamsBuilder, CityModel.class);
    System.out.println(cityModels);
}
指定过滤器的 scan 查询
@Test
public void testScanWithFilter() {
    ScanQueryParamsBuilder scanQueryParamsBuilder = new ScanQueryParamsBuilder.Builder()
            .filter(new IHBaseFilter<Filter>() {
                @Override
                public Filter customFilter() {
                    List<Filter> filters = new ArrayList<>(2);
                    // 筛选row key 大于b20001的数据
                    Filter rowFilter = new RowFilter(CompareFilter.CompareOp.GREATER,
                            new BinaryComparator("b20001".getBytes()));
                    // 筛选列前缀city_address的数据
                    ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("city_address"));
                    // 筛选列值与深圳市相等的数据
                    ValueFilter valueFilter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator(Bytes.toBytes("深圳市")));
                    // 多过滤器,注意顺序
                    filters.add(rowFilter);
                    filters.add(columnPrefixFilter);
                    filters.add(valueFilter);
                    // 需所有条件全部通过
                    FilterList andFilterList = new FilterList(FilterList.Operator.MUST_PASS_ALL, filters);
                    // 满足其中一个条件即可
                    FilterList orFilterList = new FilterList(FilterList.Operator.MUST_PASS_ONE, filters);
                    return orFilterList;
                }
            })
            .build();
    List<CityModel> cityModels = tableTemplate.scan(scanQueryParamsBuilder, CityModel.class);
    System.out.println(cityModels);
}

9.6 删除数据

@Test
public void testDeleteData() {
    hBaseTemplate.delete("TEST:LEO_USER""12003");
    hBaseTemplate.delete("TEST:LEO_USER""11004""INFO2");
    hBaseTemplate.delete("TEST:LEO_USER""10001""info1""addresses");
    System.out.println("数据删除完成");
}
批量删除数据
@Test
public void testDeleteBatch() {
    hBaseTemplate.deleteBatch("TEST:LEO_USER", Arrays.asList("10001""10002"));
    hBaseTemplate.deleteBatch("TEST:LEO_USER", Collections.singletonList("10003"), "INFO2");
    hBaseTemplate.deleteBatch("TEST:LEO_USER", Collections.singletonList("10004"),
            "info1""age""username");
}

10. HQL

hbase-sdk 从 2.0.6 版本开始,开始提供 HQL 功能,并在 3.0.0 版本中得到极大优化,一种以类 SQL 的方式读写 HBase 表中的数据,进一步降低了普通 API 的使用复杂度。HQL 的操作依赖HBaseSqlTemplate来完成,因此在使用之前,需先构造好HBaseSqlTemplate的对象实例。
hql

10.1 构造 HBaseSqlTemplate 的示例

// 1. 创建HBase SQL操作的模版类HBaseSqlTemplate
private HBaseSqlTemplate hBaseSqlTemplate = new HBaseTableTemplateImpl.Builder()
                .properties(getProperties()).build();

//  把HBase的连接配置信息存储在Properties中
Properties getProperties() {
    Properties properties = new Properties();
    properties.setProperty("hbase.zookeeper.quorum""myhbase");
    properties.setProperty("hbase.zookeeper.property.clientPort""2181");
    return properties;
}

10.2 创建并注册HBaseTableSchema

// 2. 创建HBaseTableSchema
HBaseTableSchema tableSchema = new HBaseTableSchema.Builder("test:test_sql")
        .addColumn("f1""id")
        .addColumn("f1""name")
     // 指定列类型,不指定默认是ColumnType.StringType
        .addColumn("f1""age", ColumnType.IntegerType)
        .addColumn("f2""address")
     // 指定一个字段是row key
        .addRow("row_key")
        .scanBatch(100)
        .scanCaching(1000)
        .deleteBatch(100)
        .scanCacheBlocks(false)
        .build();

// 3. 注册HBaseTableSchema至HBaseSqlContext中
HBaseSqlContext.registerTableSchema(tableSchema);

tableSchema.printSchema();
打印 schema
printSchema

10.3 Insert

插入一条数据
insert into test:test_sql ( f1:id , f1:name , f1:age , f2:address ) values ( '10001' , 'a_leo' , 15 , 'bj' ) where rowKey = 'a10001'
调用 insert 保存数据
sqlTemplate.insert(hql);
插入数据时还可以指定一些内置的 rowkey function
-- 对rowKey进行md5
insert into test:test_sql ( f1:id , f1:name , f1:age , f2:address ) values ( '11111' , 'a_leo' , 15 , 'bj' ) where rowKey = md5 ( 'a1111' )

-- 对rowKey md5取前4位作为前缀用|与原row拼接后形成新的rowKey
-- md5_prefix暂时不支持对参数列表的解析
insert into test:test_sql ( f1:id , f1:name , f1:age , f2:address ) values ( '11111' , 'a_leo' , 15 , 'bj' ) where rowKey = md5_prefix ( 'a1111' )
row key function 暂时还不支持对参数列表的解析,暂时只能使用 function ( 'row key 值' )的形式。
查看新保存的数据
select * from test:test_sql where rowKey = md5 ( 'a1111' )
select * from test:test_sql where rowKey = md5_prefix ( 'a1111' )
row_function

10.4 Select

select 的调用方法如下:
String hsql = "select * from test:test_sql where rowKey = md5_prefix ( 'a1111' )";
HBaseDataSet dataSet = sqlTemplate.select(hsql);
dataSet.show()
1. 根据 rowKey 查询数据
select * from test:test_sql where rowKey = 'a10001'
select_by_row
2. 查询一批数据,即:in row keys
select * from test:test_sql where rowKey in ( 'a10001' , 'a10002' , 'a10003' )
in_row_keys
3. 根据 startRowKey 和 endRowKey 扫描数据
select * from test:test_sql where ( startKey = 'a10001' , endKey = 'b20006' )
-- b20006不会被包含进去
select_limit
4. 查询不同版本数据
先保存三个版本数据
save_data_3_version
查询多版本数据
select f1:name from test:test_sql where rowKey = 'row_1000' and maxVersion = 3
select-some-versions
5. 列值过滤查询
列值过滤查询时需指定 row 的过滤条件,以避免引发全表扫描
select * from test:test_sql where ( startKey = 'a10001' , endKey = 'a10006' ) and f1:age <= 18
select_by_filter_col
6. limit
select * from test:test_sql where ( startKey = 'a10001' , endKey = 'a10006' ) and f1:age <= 18 limit 2
select_limit
7. 查询一批 rowkey 数据
select * from test:test_sql where rowKey in ( 'a10001' , 'a10002' , 'a10003' )
select-in-rows

10.5 delete

原始数据
source-data
删除某个 row key 的某个字段
delete f1:age from test:test_sql where rowKey = 'b20004'
delete-one-col
指定时间戳删除数据
原始数据如下:
原始数据
指定时间戳来删除数据,由图示可知,时间戳为 1670579504803 的数据已被删除
delete f1:age from test:test_sql where rowKey = 'row_10001' and ts = 1670579504803
delete_data
delete 的调用方式:
@Test
public void testDeleteSql(){
    String hql = "delete f1:age from test:test_sql where rowKey = 'row_10001'";
    sqlTemplate.delete(hql);
}

11. HBaseThriftAPI

hbase-client中的 API 会直接连接 zookeeper,如果客户端对 Connection 滥用,可能会造成 zookeeper 的连接被耗尽。
hbase-thrift不仅具有跨语言的特性,同时也会在底层避免我们直接创建对 zk 的连接。
但如果直接使用 hbase thrift 的原生 API,你可能会遇到以下几种情况:
  1. 频繁创建 TSocket 连接,增加不必要的开销
  2. 某一时间段内可能频繁创建过多的 TSocket,造成本地短连接过多
  3. 创建完一个 TSocket,间隔时间过长不使用,会被服务端主动断开
为了解决上述问题,hbase-sdk 中采取对 hbase thrift 中的 TSocket 进行连接池封装。

11.1 创建 thrift api 操作模版类——HBaseThriftTemplate

hbase-sdk中 HBase Thrift API 连接池的实现是基于 commons-pool2 的,类似 jedis-pool,相关代码在hbase-sdk-thrift模块中。
使用 thrift api 之前,请先创建 HBaseThriftTemplate 的对象
HBaseThriftTemplate thriftTemplate = HBaseThriftTemplateFactory.getInstance("localhost"9090);

HBaseThriftTemplate thriftTemplate = HBaseThriftTemplateFactory.getInstance("localhost"909010);

HBaseThriftTemplate thriftTemplate = HBaseThriftTemplateFactory.getInstance("localhost"9090, config);
HBaseThriftTemplate 可接受的参数类型:
  1. thrift server host
  2. thrift server port
  3. poolSize 连接池大小,设置后,连接池中核心和最大参数都将是此值
  4. HBaseThriftPoolConfig config
config 为连接池配置类,其默认配置如下,可按需进行修改设置:
public class HBaseThriftPoolConfig extends GenericObjectPoolConfig {
    public HBaseThriftPoolConfig() {
        // 连接池中的最大连接数,默认1,根据服务端可以容纳的最大连接数和当前并发数进行合理设置
        setMaxTotal(1);
        // 连接池中确保的最少空闲连接数
        setMinIdle(1);
        // 连接池中允许的最大空闲连接数
        setMaxIdle(1);
        // 连接池用尽后,调用者是否等待,为true时,maxWaitMillis才生效
        setBlockWhenExhausted(true);
        // 连接池用尽后,调用者的最大等待时间,毫秒,默认-1,表示永不超时
        setMaxWaitMillis(6000);
        // 每次从资源池中拿/归还连接是否校验连接的有效性,默认false,避免每次使用或归还连接与服务端进行一次连接开销
        setTestOnBorrow(false);
        setTestOnReturn(false);
        // 开启JMX监控
        setJmxEnabled(true);
        // 是否开启空闲连接检测,默认false,建议true
        setTestWhileIdle(true);
        // 空闲连接的检测周期,毫秒,默认-1不进行检测,此处周期设置为3分钟
        setTimeBetweenEvictionRunsMillis(180 * 1000);
        // 空闲连接检测时,每次检测资源的个数,设置为-1,就是对所有连接进行检测
        setNumTestsPerEvictionRun(-1);
        // 连接池中连接的最小空闲时间,默认600000毫秒,10分钟
        setMinEvictableIdleTimeMillis(600 * 1000);
        //硬闲置  3秒没有占用设置为闲置, 检测线程直接剔除闲置,保持的最小空闲数,会被剔除且重新生成 硬闲置设置之后,软闲置设置无效
        //setMinEvictableIdleTimeMillis(3000);
        //软闲置  3秒没有占用设置为闲置, 当空闲连接>最小空闲数,才执行剔除闲置连接,否则维持最小空闲数,即使闲置了也不会剔除
        //setSoftMinEvictableIdleTimeMillis(3000);
    }
}

11.2 保存数据

构造数据模型类
@HBaseTable(namespaceName = "test", tableName = "t1", defaultFamilyName = "info")
public class UserModel {
    @HBaseRowKey
    private String userId;
    @HBaseColumn()
    private String nickName;
    @HBaseColumn(familyName = "detail", columnName = "detailAddress")
    private String detailAddress;
    @HBaseColumn(familyName = "detail", toUpperCase = true)
    private double detailPay;
    // 省略getter、setter
}
或选择直接保存 Map 中的数据
Map<String, Object> data = new HashMap<>();
data.put("info:nick_name""会飞的猪");
data.put("detail:DETAIL_PAY"1234.5);
data.put("detail:detailAddress""上海黄浦区");
thriftTemplate.save("test:t1""u10002", data);
此处有一个注意事项:
Map<String, Object> data 中的数据保存时,所有值均会被转成 String 类型后存储,而模型类是区分属性类型的。所以,模型类转换 Map<String, Object> data 中的数据时,可能会遇到字节操作的异常。

11.3 查询数据

@Test
public void testGetRow() {
    Optional<UserModel> userModel = thriftTemplate.getRow("u10001", UserModel.class);
    System.out.println(userModel);

    Map<String, String> data = thriftTemplate.getRowToMap("test:t1""u10002"false);
    System.out.println(data);
}
查询多条 row key,即:in row keys
@Test
public void testGetRows() {
    thriftTemplate.getRows(Arrays.asList("u10001""u21000""u22000"), UserModel.class);

    thriftTemplate.getRows(Arrays.asList("u10001""u21000""u22000"), "detail", UserModel.class);

    thriftTemplate.getRows(Arrays.asList("u10001""u21000""u22000"), "detail", Collections.singletonList("detailAddress"), UserModel.class);
}
Map 结构数据保存时,会统一把数据转换为字符串类型,所以,当用 java 实体类绑定时,可能出现报错情况。

11.4 Scan 数据

全表扫描所有数据,并设置 limit
@Test
public void testScanWithLimit() {
    ScanQueryParamsBuilder queryParams = new ScanQueryParamsBuilder.Builder()
            .limit(2)
            .build();
    // Map 保存的数据,与模型类保存的数据,非string类型不能互通
    List<UserModel> userModelList = thriftTemplate.scan(queryParams, UserModel.class);
    System.out.println(userModelList);
}
根据起止 row key 扫描数据,不包含 stopRow
@Test
public void testScanWithStarAndRow() {
    ScanQueryParamsBuilder queryParams = new ScanQueryParamsBuilder.Builder()
            .startRow("u10001")
            .stopRow("u21000")
            .build();

    List<UserModel> userModelList = thriftTemplate.scan(queryParams, UserModel.class);
    System.out.println(userModelList);
}
设置过滤器扫描,列名为 nick_前缀,且列对应值 ascii 码表比对:>= 不会飞的猪 2 的数据被筛选出来:
@Test
public void testScanWithFilter() {
    // 设置过滤器扫描,列名为nick_前缀,且列对应值ascii码比:不会飞的猪2大的被筛选出
    ScanQueryParamsBuilder queryParams = new ScanQueryParamsBuilder.Builder()
            .filter(new IHBaseFilter<String>() {
                @Override
                public String customFilter() {
                    return "ColumnPrefixFilter('nick_') AND ValueFilter(>=, 'binary:不会飞的猪2')";
                    }
                })
                .build();

    List<UserModel> userModelList = thriftTemplate.scan(queryParams, UserModel.class);
    System.out.println(userModelList);
}
更多 API 的使用可以参考源码中的测试用例以及相关的 API 文档。

12. 特别鸣谢

HQL 的语法设计以及 antlr4 的语法解析,有参考 alibaba 的开源项目 simplehbase,在此特别感谢。simplehbase 感觉是一个被遗弃的项目,针对的 HBase 版本是 0.94, 已经有超过 6 年没有维护了。
hbase-sdk 在 simplehbase 的基础上进行重组和解耦,以兼容hbase-sdk原有的框架设计,并便于以后的扩展。

13. hbase-sdk 目前的不足

HQL 的 antlr4 解析功能不太完善,对语法的要求比较严格,多一个空格少一个空格貌似都会引起语法错误。需后续优化。
可能还有一些隐藏 BUG,未被发现。
可能存在性能不足的地方可以被进一步优化。
接口的定义,类的继承和一些设计模式的应用上,还有进一步优化的空间。
因此欢迎各位大佬试用和参与开发,多多提出您宝贵的建议。

14. 未来计划

  • HBatis,类似于 MyBatis 的 ORM 框架,以 XML 管理 SQL 的方式维护集群数据的读写操作
  • 集成 Hystrix 熔断框架,实现 API 层面的主备集群自动切换功能
  • Thrift 连接池自动扩所容的能力
  • 还有更多

15. 更新日志

v3.0.0 2022-12-10

  • 对 hbase-sdk 项目做了大量重构,使 API 的抽象程度更高,同时丰富了原有的功能,也修复诸多 BUG。
  • 基于 reflectasm 重构反射工具类,引入缓存等等,以提升 ORM 映射字段的效率。
  • HQL 功能优化
  • 工具类优化
  • 引入 findbugs、checkstyle 等插件,并优化打包方式

v2.0.7 2020-12-30

  • HBase Thrift API 上线,以及提供 Thrift API 的连接池实现

v2.0.6 2020-11-29

  • HQL 功能上线

v2.0.5 2020-11-14

  • 新增功能与代码优化

v2.0.3 2020-10-08

  • 大量重构和优化

v1.0.5 2020-09-07

  • 完善基础 API 的功能
  • 完成 ORM 特性
  • 模块拆分
  • ......

浏览 25
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报