Hbase Java API简单实践(附源代码解释)

详细代码及链接

  • maven依赖:hbase-client,slf4j-api,slf4j-nop(不需要hbase-server包)
  • resource中加入hdfs-site.xml配置文件(不需要core-site.xml)
  • resource中放置log4j.properties文件(HBase安装目录下conf文件中的log4j.properties)
  • 完整项目链接 ————(https://github.com/AtTops/Practice-Item/tree/per-taotao/HbaseTest)
  • 完整代码如下(SomeHbaseAPI类与APITest类)


    SomeHbaseAPI.java
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 王海
 * @version V1.0
 * @package per.wanghai
 * @Description
 * @Date 2017/10/29 23:23
 */
public class SomeHbaseAPI {
    private final Logger logger = LoggerFactory.getLogger(SomeHbaseAPI.class);

    protected void listTable(Admin admin) throws IOException {
        // 获得HTableDescriptors
        // (所有namespace的表,相当于scan META)
        HTableDescriptor[] tableDescriptor = admin.listTables();
        System.out.println("您的HBase有以下表:");
        for (int i = 0; i < tableDescriptor.length; i++) {
            System.out.println("表" + i + ":" + tableDescriptor[i].getNameAsString());
        }
    }

    /**
     * @param columnFamilies(这是一个变长参数,“Varargs”机制只允许一个变长参数,且必须放在最后)详见参考2
     * @throws IOException
     * @Description 该方法创建一个table实例
     */
    protected void createTable(Admin admin, TableName tableName, String... columnFamilies) throws IOException {
        try {
            if (admin.tableExists(tableName)) {
                // "{}"是slf4j的占位符(其一大特色)
                // DEBUG < INFO < WARN < ERROR < FATAL
                logger.warn("表:{}已经存在!", tableName.getNameAsString());
            } else {
                // 标注2:关于HTableDescriptor:
                HTableDescriptor tableDescriptor = new HTableDescriptor(tableName);
                for (String columnFamily : columnFamilies) {
                    tableDescriptor.addFamily(new HColumnDescriptor(columnFamily));
                }
                admin.createTable(tableDescriptor);
                logger.info("表:{}创建成功!", tableName.getNameAsString());
            }
        } finally {
            if (admin != null) {
                admin.close();
            }
        }
    }

    /**
     * @throws IOException
     * @Description 一行一行的插入数据
     * TODO:批量插入可以使用 Table.put(List<Put> list)
     */
    protected void putOneByOne(Connection connection, TableName tableName,
                               byte[] rowKey, String columnFamily, String column, String data) throws IOException {
        Table table = null;
        try {
            // 创建一个table实例
            table = connection.getTable(tableName);
            // HBase中所有的数据最终都被转化为byte[]
            // (rowKey已经在testCurd方法中转换为byte[])
            Put p = new Put(rowKey);
            // 查看源码知:put的add方法已经被弃用
            p.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column), Bytes.toBytes(data));
            table.put(p);
            logger.info("表:{}已更新!!!", tableName.getNameAsString());
        } finally {
            if (table != null) {
                table.close();
            }
        }
    }

    /**
     * @param connection
     * @param tableName
     * @param str:一个字符串数组(rowkey,family,qualifier,value.循环)
     * @throws IOException
     */
    protected void putList(Connection connection, TableName tableName, String[] str) throws IOException {
        // 每个put操作,我们放入四个数据
        int count = str.length / 4;
        // 我们希望数据量是4的倍数,因为剩下的我们将不会写入
        int remainder = str.length % 4;
        if (remainder > 0) {
            logger.warn("数据可能并不会像您预期的那样完全写入,如有必要,请检查下您的数据量!");
        }
        Table table = null;
        try {
            // 创建一个table实例
            table = connection.getTable(tableName);
            List<Put> puts = new ArrayList<>();
            for (int i = 0; i < count; i++) {
                Put put = new Put(Bytes.toBytes(str[4 * i]));
                put.addColumn(Bytes.toBytes(str[4 * i + 1]), Bytes.toBytes(str[4 * i + 2]), Bytes.toBytes(str[4 * i + 3]));
                puts.add(put);
            }
            table.put(puts);
            logger.info("表:{}已使用putList方法更新!!!", tableName.getNameAsString());
        } finally {
            if (table != null) {
                table.close();
            }
        }
    }

    /**
     * @throws IOException
     * @Description 扫描表
     * 想获取部分行的数据,与putList方法类似,用List<Get>即可
     */
    protected void scan(Connection connection, TableName tableName) throws IOException {
        Table table = null;
        try {
            table = connection.getTable(tableName);
            /*
            行的数目很大时,同时在一次请求中发送大量数据,会占用大量的系统资源并消耗很长时间,
            所以ResultScanner类把扫描操作转换为类似的get操作,它将每一行数据封装成一个Result实例,
            并将所有的Result实例放入一个迭代器中
             */
            ResultScanner rsScan1;
            ResultScanner rsScan2;

            // 这次操作返回表中所有的数据
            Scan scan1 = new Scan();
            rsScan1 = table.getScanner(scan1);
            for (Result r : rsScan1) {
                System.out.println(r);
                // 打印出来的Value是bytes类型
            }
            rsScan1.close();
            // 注:扫描器也使用同样的租约超时机制,保护其不被失效的客户单阻塞太久
            // 超时时间配置:hbase.regionserver.lease.period

            // 同样,也可以addfamily:
            Scan scan2 = new Scan();
            scan2.addFamily(Bytes.toBytes("commonInfo"));
            rsScan2 = table.getScanner(scan2);
            for (Result r : rsScan2) {
                System.out.println(r);
            }
            rsScan2.close();
        } finally {
            if (table != null) {
                table.close();
            }
        }
    }

    /**
     * @throws IOException
     * @Description 根据row key获取表中的该行数据
     */
    protected void getOneRow(Connection connection, TableName tableName, byte[] rowKey) throws IOException {
        Table table = null;
        try {
            table = connection.getTable(tableName);
            // 这种方法获取指定rowkey的所有信息(然后可以使用不同的方法获取指定信息)
            // 用rowKey来实例化get对象,
            Get all = new Get(rowKey);
            // Result类不是线程安全的
            // 更多的使用方法见标注4
            Result result = table.get(all);

            // 可以使用addColumn指定columnFamily与qualifier
            // 标注3:更多缩小获取范围的方法

            /* 这里使用addFamily获取指定列族的所有列的信息(一行)
            Get part = new Get(rowKey);
            part.addFamily(Bytes.toBytes("commonInfo"));
            Result result = table.get(part);
            ...
            ...
            */

            /*通过getValue获取指定信息
            不推荐用字符串拼接的方式,字符串拼接会不断的创建新的对象,
            而原来的对象就会变为垃圾被GC回收掉,如果拼接得次数多,这样执行效率会很低底。
            (见下方Cell中使用StringBuffer)
            String city = Bytes.toString(result.getValue(Bytes.toBytes("commonInfo"),Bytes.toBytes("city")));
            String age = Bytes.toString(result.getValue(Bytes.toBytes("concelInfo"),Bytes.toBytes("age")));
            System.out.println("city: " + city + "\t" + "age: " + age);
            */

            // rawCells()返回cell[];注意:Cell接口中的getFamily、getValue等方法已经被废弃

            // 推荐:使用CellUtil中的一些列方法
            for (Cell cell : result.rawCells()) {
                /* 与上方的String拼接不同,这样的String拼接不会创建多个String对象
                System.out.println(
                "RowNum : " + "\t" + Bytes.toString(CellUtil.cloneRow(cell))
               + ", Family : " + "\t" + Bytes.toString(CellUtil.cloneFamily(cell))
               + ", Qualifier : " + "\t" + Bytes.toString(CellUtil.cloneQualifier(cell))
               + ", Value : " + "\t" + Bytes.toString(CellUtil.cloneValue(cell))
                );
                */

                // 采用StringBuffer:(因为其是可变的字符串对象,所以不会再创建新变量)
                StringBuffer sbuffer = new StringBuffer()
                        .append("RowNum : \t")
                        .append(Bytes.toString(CellUtil.cloneRow(cell)))
                        .append(", Family : \t")
                        .append(Bytes.toString(CellUtil.cloneFamily(cell)))
                        .append(", Qualifier : \t")
                        .append(Bytes.toString(CellUtil.cloneQualifier(cell)))
                        .append(", Value : \t")
                        .append(Bytes.toString(CellUtil.cloneValue(cell)));
                System.out.println(sbuffer);
            }
        } finally {
            if (table != null) {
                table.close();
            }
        }
    }

    /**
     * @throws IOException
     * @Description 删除表中的数据
     */
    protected void myDeleteTable(Admin admin, TableName tableName) throws IOException {
        try {
            if (admin.tableExists(tableName)) {
                // 必须先disable, 再delete
                myDisableTable(admin, tableName);
                // admin的很多方法在子类HBaseAdmin中实现
                // TODO:没看出该父类通过何种方式调用的子类方法
                admin.deleteTable(tableName);
                logger.info("表:{}已删除!!!", tableName.getNameAsString());
            } else {
                logger.info("表:{}并不存在!!!", tableName.getNameAsString());
            }
        } finally {
            if (admin != null) {
                admin.close();
            }
        }
    }

    protected void myDisableTable(Admin admin, TableName tableName) throws IOException {
        try {
            // admin的很多方法在子类HBaseAdmin中实现
            if (admin.tableExists(tableName)) {
                admin.disableTable(tableName);
                logger.info("表:{}已禁用!!!", tableName.getNameAsString());
            }
        } finally {
            if (admin != null) {
                admin.close();
            }
        }
    }
}



APITest.java

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;

/**
 * @author 王海[https://github.com/AtTops]
 * @version V1.0
 * @package PACKAGE_NAME
 * @Description
 * @Date 2017/10/31 11:43
 */
public class APITest {
    public static void main(String[] args) {
        new APITest().testCrud();
    }

    private void testCrud() {
        SomeHbaseAPI caller = new SomeHbaseAPI();
        /*
         * 标注1:详解HBaseConfiguration
         */
        // 创建一个configuration对象 —— 告诉客户端必要的配置信息
        Configuration config = HBaseConfiguration.create();
        // 创建一个连接到集群的connection
        Connection connection = null;
        /* Admin是一个接口类,其很多方法在子类HBaseAdmin中实现
         0.99版本开始:HBaseAdmin不再是客户端API。它被标记为InterfaceAudience.Private,
         表示是一个HBase内部类。
         使用Connection.getAdmin()获取Admin的实例,而不是直接构建一个HBaseAdmin。
         可以用来create、drop、list、enabl、disable表;add、drop 表的column families,以及一些其他的管理操作。*/
        try {
            connection = ConnectionFactory.createConnection(config);
            //
            Admin admin = connection.getAdmin();
            // 该方法传递一个String类型参数,返回TableName实例
            TableName tableName = TableName.valueOf("myHBaseTable");
            // 表不存在会报:TableNotFoundException

            // 获取lists of table
            caller.listTable(admin);
            // 创建HBase表
            caller.createTable(admin, tableName, "commonInfo", "concelInfo");
            // rowkey要设计得尽量的短,数据的持久化文件HFile中是按照KeyValue存储的,
            // 如果rowkey过长,会极大影响HFile的存储效率

            byte[] rowkey_bytes = Bytes.toBytes("ROW4");
            /* 一行一行的插入数据,每一次put操作都是一次有效的RPC(
             所以数据量大时不要这样使用, 而应该使用BufferedMutator来实现批量的异步写操作。)
             这里两个列族,commonInfo列族两个“小列”,concelInfo一个“小列”*/
            caller.putOneByOne(connection, tableName, rowkey_bytes, "commonInfo", "city", "Ziyang");
            caller.putOneByOne(connection, tableName, rowkey_bytes, "commonInfo", "password", "000000");
            caller.putOneByOne(connection, tableName, rowkey_bytes, "concelInfo", "age", "100");

            // 删除表
//            caller.myDeleteTable(admin, tableName);

            // 获取指定的数据
            caller.getOneRow(connection, tableName, rowkey_bytes);

            // 批量put数据
            String[] str = new String[]{"ROW5", "commonInfo", "city", "Shanghai", "ROW5"
                    , "concelInfo", "age", "35", "ROW6", "concelInfo", "age", "120", "Illegal_Value"};
            caller.putList(connection, tableName, str);

            // 删除两行数据
            Delete delete1 = new Delete(Bytes.toBytes("ROW5"));
            Delete delete = new Delete(Bytes.toBytes("ROW6"));
            /*也可以定义删除的列族:
            其中addCaddColumn是删除最新版本,addCaddColumns
            是删除所有版本*/
            Table table = connection.getTable(tableName);
            table.delete(delete1);
            table.delete(delete);

            // scan表
            caller.scan(connection, tableName);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                // 最后记得关闭集群
                if (connection != null) {
                    connection.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

注:代码不用上传到集群运行,类似于JDBC,配置文件中已经有了各种配置信息,数据通过网络套接字进行传输(确定编写代码的网络与集群网络能通信,否则可能会报Will not attempt to authenticate using SASL)

注释

标注1

  • HBaseConfiguration 继承了 hadoop.conf.Configuration
  • 该类的HBaseConfiguration()和HBaseConfiguration(Configuration c)
    构造方法已经被弃用,建议使用create方法
    LOG.warn(“instantiating HBaseConfiguration() is deprecated. Please use HBaseConfiguration.create() to construct a plain Configuration”);
  • HBaseConfiguration.create()方法首先调用hadoop的Configuration()构造conf对象;然后将该conf对象传递给addHbaseResources方法,该方法返回最终的“知道”各种配置信息的conf对象。

    这里写图片描述

    从这里我们可知:resource文件夹只需要放入hbase-default.xml和hbase-site.xml这两个配置文件即可,然后将resource文件夹添加到classpath
    fjksv
  • *

标注2

  • Hbase2.0.0中,把HTableDescriptor标记为@Deprecated,并会在3.0.0版本被移除
  • HTableDescriptor包含有关HBase表的详细信息,例如所有列族的描述符、获取列族数量、列族名字等等

标注3

要进一步缩小要获取的范围,请使用以下方法:

  1. 要从特定列族获取所有列,请为每个列族执行addFamily进行检索。
  2. 要获取特定列(qualifier),请对要检索的每个列执行addColumn。
  3. 要仅在特定范围的版本时间戳内检索列,请执行setTimeRange。
  4. 要仅检索具有特定时间戳的列,请执行setTimestamp。
  5. 要限制要返回的每列的版本数,请执行setMaxVersions。
  6. 要添加过滤器,请调用setFilter。

标注4

Result类(可以直接返回各种Map结构和值)更多的使用方法:

  1. 要获取Result中所有单元格的完整映射,包括多个系列和多个版本,使用getMap()。
  2. 要获取每个 family到其列(qualifiers和values)的映射,仅包括每个列的最新版本,请使用getNoVersionMap()。
  3. 要获得一个个别 family的限定符到最新值的映射,使用getFamilyMap(byte [])。
  4. 要获取特定family和qualifiers的最新值,使用getValue(byte [],byte [])。返回的结果是Cell对象数组,每个对象包含row, family, qualifier, timestamp, 和value.
  5. 可以通过方法listCells()访问底层的Cell对象。这将从内部Cell []创建一个列表。

运行截图:

创建表,插入三行数据:
这里写图片描述

查看表(验证):
这里写图片描述


list:
这里写图片描述


通过getValue获取指定信息 & rawCells() 获取指定行所有信息
这里写图片描述

这里写图片描述


删除与禁用表:
这里写图片描述


Scan全表 & Scan特定列
这里写图片描述


putList方法一次put多条数据:
这里写图片描述

报错及解决过程

1.Failed to load class “org.slf4j.impl.StaticLoggerBinder
在类路径上放置一个(只有一个)slf4j-nop.jar,slf4j-simple.jar,slf4j-log4j12.jar,slf4j-jdk14.jar或logback-classic.jar可以解决问题(直接在maven中加入)
2.WARN No appenders could be found for logger (org.apache.hadoop.security.Groups).
没有配置文件log4j.xml或者log4j.properties,亦或者路径不对
3.Will not attempt to authenticate using SASL
网络ping不通的原因
4.警告:java.io.IOException: No FileSystem for scheme: hdfs
试过添加core-site.xml和hdfs-site.xml,无效但是并不影响使用)

参考资源

参考1:HBase官方apidocs(1.2.6)
参考2:《HBase The Definitive Guide》 2nd Edition
参考3:java的变长参数机制
参考4:为什么要使用SLF4J而不是Log4J&SLF4j使用
参考5:易百HBase教程

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是使用Java语言实现Flink实时读取Kafka并将数据写入HBase数据库的示例代码: ``` import org.apache.flink.api.common.functions.MapFunction; import org.apache.flink.api.common.serialization.SimpleStringSchema; import org.apache.flink.api.java.utils.ParameterTool; import org.apache.flink.streaming.api.datastream.DataStream; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaConsumer; import org.apache.flink.streaming.connectors.kafka.FlinkKafkaProducer; import org.apache.flink.streaming.util.serialization.KeyedSerializationSchema; import org.apache.flink.streaming.util.serialization.SimpleStringSchema; import org.apache.hadoop.hbase.client.Put; import org.apache.hadoop.hbase.client.TableDescriptorBuilder; import org.apache.hadoop.hbase.client.TableDescriptor; import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; import org.apache.hadoop.hbase.client.ConnectionFactory; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.client.Table; import org.apache.hadoop.hbase.client.Connection; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.util.Bytes; import org.apache.hadoop.hbase.HBaseConfiguration; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.mapreduce.TableOutputFormat; import org.apache.hadoop.hbase.io.ImmutableBytesWritable; import java.util.Properties; public class KafkaToHbaseExample { public static void main(String[] args) throws Exception { // 设置Kafka消费者配置 String kafkaBroker = "localhost:9092"; String kafkaTopic = "test"; String kafkaGroupId = "flink-kafka-hbase"; Properties kafkaProps = new Properties(); kafkaProps.setProperty("bootstrap.servers", kafkaBroker); kafkaProps.setProperty("group.id", kafkaGroupId); // 设置HBase表格的配置 String hbaseTableName = "test_table"; String hbaseColumnFamily = "cf"; Configuration hbaseConfig = HBaseConfiguration.create(); hbaseConfig.set(TableOutputFormat.OUTPUT_TABLE, hbaseTableName); hbaseConfig.set("hbase.zookeeper.quorum", "localhost"); hbaseConfig.set("hbase.zookeeper.property.clientPort", "2181"); Connection hbaseConnection = ConnectionFactory.createConnection(hbaseConfig); Admin hbaseAdmin = hbaseConnection.getAdmin(); TableDescriptor hbaseTableDescriptor = TableDescriptorBuilder.newBuilder(TableName.valueOf(hbaseTableName)) .setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(hbaseColumnFamily)).build()) .build(); if (!hbaseAdmin.tableExists(TableName.valueOf(hbaseTableName))) { hbaseAdmin.createTable(hbaseTableDescriptor); } hbaseAdmin.close(); hbaseConnection.close(); // 创建Flink执行环境 StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment(); env.setParallelism(1); // 创建Kafka数据流 FlinkKafkaConsumer<String> kafkaConsumer = new FlinkKafkaConsumer<>(kafkaTopic, new SimpleStringSchema(), kafkaProps); DataStream<String> kafkaStream = env.addSource(kafkaConsumer); // 将Kafka数据流转换为HBase数据流 DataStream<Put> hbaseStream = kafkaStream.map(new MapFunction<String, Put>() { @Override public Put map(String value) throws Exception { Put put = new Put(Bytes.toBytes("row key")); put.addColumn(Bytes.toBytes(hbaseColumnFamily), Bytes.toBytes("column"), Bytes.toBytes(value)); return put; } }); // 将HBase数据流写入表格 FlinkKafkaProducer<Put> hbaseSink = new FlinkKafkaProducer<>(kafkaBroker, hbaseTableName, new KeyedSerializationSchema<Put>() { @Override public byte[] serializeKey(Put element) { return null; } @Override public byte[] serializeValue(Put element) { return element.toByteArray(); } @Override public String getTargetTopic(Put element) { return null; } }, kafkaProps, FlinkKafkaProducer.Semantic.EXACTLY_ONCE); hbaseStream.addSink(hbaseSink); // 执行Flink任务 env.execute("Read from Kafka and write to HBase"); } } ``` 在上面的代码中,我们首先设置了Kafka消费者和HBase表格的配置。接下来,我们创建了一个HBase表格,并使用Flink的Kafka源将Kafka数据流读取到Flink中。然后,我们将Kafka数据流转换为HBase数据流,并在每个记录上创建一个Put对象,该对象包含HBase表格的行键和列。 最后,我们将HBase数据流写入Kafka中,以便在后续流程中将其写入HBase表格。请注意,因为我们使用了Flink的Kafka生产者,需要实现KeyedSerializationSchema接口来序列化Put对象。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值