美文网首页hbase
HBASE之JAVA API学习笔记

HBASE之JAVA API学习笔记

作者: 我是老薛 | 来源:发表于2018-12-09 09:43 被阅读0次

    本文是对如何使用Hbase提供的Java Api编写Hbase客户端程序的一个学习总结,共包括如下章节的内容:

    • 概述
    • 更新和查询数据
    • 创建和更新表结构
    • 编译和运行
    • 远程访问Hbase

    参考资料:

    1、如果需要了解Hbase的基本知识,可参见《HBASE学习笔记》一文。

    一、概述

    Hbase提供了一套Java API,可以让编写java程序来访问Hbase。从HBase 1.0开始,Hbase提供了新的Api替代以前的老的Api,新的Api更加干净和直观。
    新的Api使用接口,而不是特定的类来操作API。新的Api,使用ConnectionFactory的工厂方法来获取一个Connection对象,然后调用getAdmin()和getTable()方法来获取Admin和Table实例,接着使用实例完成具体操作,最后关闭实例和连接。
    本文用的hbase版本是hbase2.0.2,将使用新的API进行介绍。新旧API一个非常直观的区别是,带H开头的是旧的API,如Htable,HBaseAdmin,HTableDescriptor,HColumnDescriptor等。
    java客户端是通过zookeeper找到hbase服务的,因此,需要在代码中配置zookeeper的信息。

    二、更新和查询数据

    我们通过具体例子来说明,假设hbase中已经有一张表test,表中有一个列族data。关键的API是Table接口。

    (一)插入数据

    本例子代码是往表中插入数据,完整的代码如下:

    package com.hbaseexample;
    import java.io.IOException;
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.hbase.HBaseConfiguration;
    import org.apache.hadoop.hbase.TableName;
    import org.apache.hadoop.hbase.client.Connection;
    import org.apache.hadoop.hbase.client.ConnectionFactory;
    import org.apache.hadoop.hbase.client.Put;
    import org.apache.hadoop.hbase.client.Table;
    import org.apache.hadoop.hbase.util.Bytes;
    public class Example {
        private Connection connection = null;
        private Table table = null;
        public static void main(String[] args) {
            Example example = new Example();
            try {
                example.conn();
                example.addData();
                System.out.println("finish");
            } catch (IOException e) {e.printStackTrace();
            } finally {example.close();}
        }
        public void conn() throws IOException {
            Configuration config = HBaseConfiguration.create();
            config.set("hbase.zookeeper.quorum", "127.0.0.1");//zk地址
            config.set("hbase.zookeeper.property.clientPort", "2181");//zk端口
            connection = ConnectionFactory.createConnection(config);
            table = connection.getTable(TableName.valueOf("test"));
        }
        public void close() {
            if (table != null)
                try {table.close();} catch (IOException e) {}
            if (connection != null)
                try {connection.close();    } catch (IOException e) {}
        }
        public void addData() throws IOException {
            Put p = new Put(Bytes.toBytes("rk_100001")); //行键 
            //addColumn第1个参数是列族,第2个参数是列名,第3个参数是插入的值
            p.addColumn(Bytes.toBytes("data"), Bytes.toBytes("uname"), Bytes.toBytes("tom"));
            p.addColumn(Bytes.toBytes("data"), Bytes.toBytes("age"), Bytes.toBytes("25"));
            table.put(p);
        }
    }
    

    上面代码是一个完整的带main方法的java类,首先获得一个Connection对象,然后获取到Table对象,再创建一个Put对象保存要插入的数据,最后利用Table对象提交数据。

    下面例子代码都是在这个代码基础上运行。

    (二)按键值查询数据

    如果我们要查询指定的数据,可以利用Get对象来完成,代码如下(这里只给出了片段代码,只需把这代码加入到上面插入数据的完整例子代码中就可以使用):

        public void getData() throws IOException{
            Get g = new Get(Bytes.toBytes("rk_100001"));
            Result r = table.get(g);
            byte[] value = r.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
            String valueStr = Bytes.toString(value);
            System.out.println("uname:"+valueStr);
            value = r.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
            System.out.println("age:"+Bytes.toString(value));
        }
    

    上面代码根据表的键值创建Get对象,然后利用Table对象获取到结果集,最后通过结果集获取各单元值。

    (三)扫描表

    在某项场景下,我们不知道键值的时候,可以利用Scan对象扫描整个表,遍历所需的数据。例子如:

    public void scanData() throws IOException{
        Scan s = new Scan();
        ResultScanner scanner = table.getScanner(s);
        try {
             for (Result rr : scanner) {
                 byte[] row = rr.getRow();
                 byte[] uname = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
                 byte[] age = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
                 System.out.println("Found:" + Bytes.toString(row)+","+Bytes.toString(uname)+","+Bytes.toString(age));
             }
        } finally {
            scanner.close();
        }
    }
    

    上面代码对整个表进行扫描。我们也可以给Scan对象增加条件,只获取指定的列族或指定列的数据。

    如果指定获取列族的数据,可以如下面代码:

    public void scanData() throws IOException{
        Scan s = new Scan();
        s.addFamily(Bytes.toBytes("data"));
        ResultScanner scanner = table.getScanner(s);
        try {
             for (Result rr : scanner) {
                 byte[] row = rr.getRow();
                 byte[] uname = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
                 byte[] age = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
                 System.out.println("Found:" + Bytes.toString(row)+","+Bytes.toString(uname)+","+Bytes.toString(age));
             }
        } finally {
            scanner.close();
        }
    }
    

    上面代码,调用Scan对象的addFamily方法限定只获取指定列族的数据,可以调用多次,增加多个列族。

    我们也可以指定到具体的列,如下面代码:

    public void scanData() throws IOException{
        Scan s = new Scan();
        s.addColumn(Bytes.toBytes("data"), Bytes.toBytes("uname"));
        s.addColumn(Bytes.toBytes("data"), Bytes.toBytes("age"));
        ResultScanner scanner = table.getScanner(s);
        try {
             for (Result rr : scanner) {
                 byte[] row = rr.getRow();
                 byte[] uname = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("uname"));
                 byte[] age = rr.getValue(Bytes.toBytes("data"), Bytes.toBytes("age"));
                 System.out.println("Found:" + Bytes.toString(row)+","+Bytes.toString(uname)+","+Bytes.toString(age));
             }
        } finally {
            scanner.close();
        }
    }
    

    (四)获取表的列族信息

    有时我们可能需要通过代码获取hbase表定义的列族信息,例子代码如下:

    public void getCfInfo() throws IOException{
        ColumnFamilyDescriptor[] columnFamilyDescriptors = table.getDescriptor().getColumnFamilies();
        for (ColumnFamilyDescriptor columnFamilyDescriptor : columnFamilyDescriptors) {
            String familyName = columnFamilyDescriptor.getNameAsString();
            System.out.println(familyName);
        }
    }
    

    (五)删除表中数据

    我们可以按照键值来删除数据,如下面代码。

    public void deleteData() throws IOException{
            String[] rowKeys={"row1","row3"};
            List<Delete> deleteList = new ArrayList<>(rowKeys.length);
            Delete delete;
            for (String rowKey : rowKeys) {
                delete = new Delete(Bytes.toBytes(rowKey));
                deleteList.add(delete);
            }
            table.delete(deleteList);
        }
    

    三、创建和更新表结构

    我们可以编写java代码创建表、删除表、修改表的结构,类似关系数据库的DDL语句。关键的Api是Admin接口。

    (一)查看所有的表

    类似hbase命令行中list命令,显示当前用户下所有的hbase表,例子代码如下。

    package com.hbaseexample;
    import java.io.IOException;
    import org.apache.hadoop.conf.Configuration;
    import org.apache.hadoop.hbase.HBaseConfiguration;
    import org.apache.hadoop.hbase.TableName;
    import org.apache.hadoop.hbase.client.Admin;
    import org.apache.hadoop.hbase.client.Connection;
    import org.apache.hadoop.hbase.client.ConnectionFactory;
    public class AdminExample {
        private Connection connection = null;
        private Admin admin = null;
        public static void main(String[] args) {
            AdminExample example = new AdminExample();
            try {
                example.conn();
                example.listTable();
                System.out.println("finish");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                example.close();
            }
        }
        public void conn() throws IOException {
            Configuration config = HBaseConfiguration.create();
            config.set("hbase.zookeeper.quorum", "127.0.0.1");//zk地址
            config.set("hbase.zookeeper.property.clientPort", "2181");//zk端口
            connection = ConnectionFactory.createConnection(config);
            admin = connection.getAdmin();
        }
        public void close() {
            if (admin != null)
                try {admin.close(); } catch (IOException e) {}
            if (connection != null)
                try {connection.close();} catch (IOException e) {}
        }
        public void listTable() throws IOException{
            TableName[] names = admin.listTableNames();
            for(TableName name:names){
                System.out.println(name.getNameAsString());
            }
        }
    }
    

    上面代码是一个完整的带main方法的java类,首先获得一个Connection对象,然后获取到Admin对象,然后调用Admin对象的方法。下面的例子都是在上面代码基础上运行。

    (二)创建/删除表

    下面代码演示了检查一个表是否存在,如果存在,则先使之无效,再删除,然后创建一个新表。代码如下(不是完整代码,可放到上面完整例子代码中运行):

    public void doDDL() throws IOException {
        TableName tableName = TableName.valueOf("test");
        boolean re = admin.tableExists(tableName );
        if(re){
            admin.disableTable(tableName);
            admin.deleteTable(tableName);
        }
        TableDescriptorBuilder tableDesc = TableDescriptorBuilder.newBuilder(tableName);
        tableDesc.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf1")).build());
        tableDesc.setColumnFamily(ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf2")).build());
        admin.createTable(tableDesc.build());
    }
    

    (三)删除表的列族

    例子代码如下

    public void deleteCF() throws IOException{
        TableName tableName = TableName.valueOf("test");
        admin.deleteColumnFamily(tableName, Bytes.toBytes("cf2"));
    }
    

    (四)添加表的列族

    例子代码如下

    public void addCF() throws IOException{
        TableName tableName = TableName.valueOf("test");
        ColumnFamilyDescriptor cf =ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf2")).build();
        admin.addColumnFamily(tableName, cf );
        cf =ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf3")).build();
        admin.addColumnFamily(tableName, cf );
    }
    

    (五)修改已存在的列族

    例子代码如下

    public void modifyCF() throws IOException{
        TableName tableName = TableName.valueOf("test");
        ColumnFamilyDescriptorBuilder builder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf1"));
        builder.setMaxVersions(3);
        admin.modifyColumnFamily(tableName, builder.build());
    }
    

    创建列族时,默认列族支持的最多存储的版本数是1,上面例子代码将列族cf1支持的最多存储版本数改为3。

    四、编译和运行

    上面的例子只是给出了程序的源代码,那怎么编译和执行呢?因为代码中引入了很多Hbase JAVA API的接口和类,需要引入相应的jar包。编写的java hbase客户端程序所依赖的jar包都位于hbase安装目录的lib目录下。
    想要成功编译上面代码,只需引入如下三个jar包即可(均在lib目录下可找到):

    hadoop-common-2.7.7.jar
    hbase-common-2.0.2.jar
    hbase-client-2.0.2.jar

    但要想运行程序,需要依赖更多的jar包。这有两种方式:
    1、将lib根目录下所有的jar包加入到classpath路径中(如果在ide中运行,则全部引入),这样可以在IDE(如eclipse)中直接运行。

    2、把程序编译后的class打成jar包。然后利用hbase提供的命令行客户端脚本程序hbase来执行,这样就不需要关心要引入哪些jar包了。
    执行hbase脚本前,需要先将打成的jar包加入到HBASE_CLASSPATH环境变量中。然后运行hbase,后面跟着可执行类名。如在linux下,在控制台下执行如下两条语句即可:

    export HBASE_CLASSPATH= 上面打成的jar包
    hbase com.hbaseexample.Example
    

    如在windows下,在dos命令行执行,需要将export命令改成set命令。

    五、远程访问hbase

    如果我们希望编写的hbase客户端程序(位于windows系统下)能操作安装在远程的Linux机器上的hbase服务。这时需要做一些额外的配置。
    当然前提是hbase客户端程序将运行的机器上已经有hbase客户端可运行的环境。

    需要做如下配置:
    1、配置远程hbase服务器支持远程连接。这时需要在hbase-site.xml中添加一个属性配置,属性名 hbase.zookeeper.quorum ,属性值为远程服务器主机的主机名(注意,一定不是IP地址,而是主机名)。如下面配置例子:

      <property>
        <name>hbase.zookeeper.quorum</name>
        <value>master</value>
      </property>
    

    上面的master是远程服务器的主机名,不能配置成IP地址。

    2、修改hbase客户端程所在的windows机器上的配置文件:C:\Windows\System32\drivers\etc\hosts 在其中添加远程Linux机器IP地址和主机名的对应关系。如:
    192.168.2.6 master
    上面的192.168.2.6是远程Linux主机的IP地址,master是远程Linux主机的主机名,要与hbase-site.xml中的配置一致。

    3、修改java代码
    将上面例子中的代码中的如下语句
    config.set("hbase.zookeeper.quorum", "127.0.0.1");
    改成如下的语句
    config.set("hbase.zookeeper.quorum", "192.168.2.6");
    上面的192.168.2.6是Linux服务器的IP地址,也可以用主机名master来替换IP地址,如:
    config.set("hbase.zookeeper.quorum", "master");
    经过上面的设置后,java程序在windows下运行,就可以连接运行在远程linux上的hbase服务了。

    下面简单介绍下linux下查看和修改主机名的方法,我们这里用的是版本是Centos7的linux操作系统。方法如下:

    1、查看主机名的命令 hostname,默认情况下,Linxu的主机名叫localhost.localdomain

    2、修改主机名,可以临时修改(机器重启将无效),或永久生效。
    临时修改只需执行如下命令:
    sudo hostname 设置的主机名
    如果要永久生效,就需要修改Linux下的配置文件** /etc/hostname**,将其中的内容改成要设置的主机名,如master。根据需要,我们还可以修改 /etc/hosts文件,添加IP地址与主机名的对应关系。

    相关文章

      网友评论

        本文标题:HBASE之JAVA API学习笔记

        本文链接:https://www.haomeiwen.com/subject/bjjhhqtx.html