HBase是一个在HDFS上开发的面向列的分布式数据库。如果需要实时地随机访问超大规模数据集,就可以使用HBase。本篇介绍HBase的基础知识,包括安装配置、部署运行、表的创建和Java api的使用。
一、安装
1.2 安装HBase
将下载的压缩包解压到安装目录。
tar -zxvf 压缩包 安装目录
1.3 配置HBase
HBase有两个运行模式:单机模式和分布式模式。而分布式模式有分为伪分布式模式(所有守护进程都运行在单个节点上)和完全分布式模式(进程运行在物理服务器集群中)。
分布式模式依赖Hadoop分布式文件系统,即HDFS。所以在配置之前,一定要确保有一个合适的,正在工作的HDFS集群。这里我们采用完全分布式模式。
1)首先进入HBase的安装目录,编辑 /conf/hbase-site.xml文件,修改内容如下:
hbase.rootdir
hdfs://probd/hbase
probd is the cluster name of hdfs.
hbase.cluster.distributed
true
这里添加hbase.cluster.distributed属性并设置为true,添加hbase.rootdir并设置为HDFS中NameNode的访问地址,HBase会将数据写到NameNode主机上的/hbase目录下。
2)配置region服务器。编辑/conf/regionservers文件,该文件列出了所有运行HRegionServer守护进程的主机,每个主机独占一行(类似Hadoop中的slaves文件):
probd01
probd02
probd03
probd04
HBase集群启动和关闭时会按照该文件中罗列的主机逐一执行。
3)配置zookeeper。分布式的HBase依赖于zookeeper集群,所有的节点和客户端都必须能够正常访问zookeeper。HBase默认管理一个单点的zookeeper集群,用户通过启动和关闭脚本就可以把zookeeper当做HBase的一部分来启动和关闭进程。用户也可以不依赖于HBase管理zookeeper集群,只需为HBase指出需要使用的集群即可。这里我们使用后一种,即使用已有的zookeeper集群。我们需要在/conf/hbase-env.sh文件中将HBASE_MANAGES_ZK属性设置为false:
export HBASE_MANAGES_ZK=false
然后,修改hbase-site.xml文件,设置zookeeper的连接地址与客户端端口号:
hbase.zookeeper.quorum
probd01:2181,probd02:2181,probd03:2181
hbase.zookeeper.property.dataDir
/probd/zookeeper-3.4.6/data/data
hbase.zookeeper.property.clientPort
2181
zookeeper的地址与端口号请参照自己集群的实际值。
这些配置完成后,需要复制conf目录到集群的其它节点上以完成同步集群的配置。
1.4 配置文件介绍
该小结不属于相关操作,只是介绍一下上面配置过程中所涉及的3个文件。
1)hbase-site.xml
在Hadoop中,如果用户需要增加HDFS的特定配置就要添加到hdfs-site.xml文件中。与此类似,在HBase中,用户需要增加配置信息就需要将配置添加到conf/hbase-site.xml文件中。配置参数可以查看HBase 目录 src/main/resources中的源文件hbase-default.xml,在doc目录中也有配置参数信息的HTML文档(但也并非所有的配置信息都罗列在hbase-default.xml中,配置中有些参数并不常用并且只在源码中存在,因此,唯一的办法就是通过阅读源码来查找这些配置参数的作用)。
进程启动后,服务器会读取hbase-default.xml文件,然后读取hbase-site.xml文件,hbase-site.xml文件的内容会覆盖hbase-default.xml中的内容。
2)hbase-env.sh
HBase的环境变量等信息需要在这个文件中设置,例如,HBase守护进程的JVM启动参数;Java堆大小和垃圾回收策略等。在这个文件中还可以设置HBase配置文件的目录、日志目录、SSH选项、进程pid文件的目录等。
3)regionservers
这个文件罗列了所有region服务器的主机名,它是纯文本文件,文件中的每一行都是主机名。HBase的运维脚本会依次迭代访问每一行来启动所有region服务器进程。
1.5 部署运行HBase
当我们配置好HBase后,接下来就要咋集群上部署HBase。在启动HBase之前,首先要确保HDFS已经启动并处于工作状态。由于我们不依赖HBase管理zookeeper,所以还要确保zookeeper已经运行,否则HBase会把zookeeper作为进程的一部分启动。之后我们进入bin目录下,执行脚本,启动HBase:
start-hbase.sh
HBase的运行日志写入了logs目录的子目录中。接下来,我们将介绍如何建表、添加数据、扫描已插入的数据、禁用表和删除表等操作。
二、HBase的基本操作
首先,启动HBase的交互环境Shell:
$HBASE_HOME/bin/hbase shell
现在来创建一个简单的表并增加几行数据:
hbase(main):002:0> create 'testtable','colfam1'
0 row(s) in 0.2930 seconds
hbase(main):003:0> list 'testtable'
TABLE
testtable
1 row(s) in 0.0520 seconds
hbase(main):004:0> put 'testtable','myrow-1','colfam1:q1','value-1'
0 row(s) in 0.1020 seconds
hbase(main):005:0> put 'testtable','myrow-2','colfam1:q2','value-2'
0 row(s) in 0.0410 seconds
hbase(main):006:0> put 'testtable','myrow-2','colfam1:q3','value-3'
0 row(s) in 0.0380 seconds
通过一条命令,我们创建了一张带有一个列族的表,我们可以通过list命令来检查这张表是否已经存在。然后我们存放了几行数据:通过两个不同的行健myrow-1和myrow-2把新增数据添加到两个不同的行中。有了一个名为colfam1的列族之后,还有添加一个任意限定符才能形成实际的列,如colfam1:q1、colfam1:q2、colfam1:q3。
接下来,我们看新增的数据是否能被检索,这里用到scan命令:
hbase(main):007:0> scan 'testtable'
ROW COLUMN_CELL
myrow-1 column=colfam1:q1,timestamp=1559206303975,value= value-1
myrow-2 column=colfam1:q2,timestamp=1559206304013,value= value-2
myrow-2 column=colfam1:q3,timestamp=1559206304069,value= value-3
2 row(s) in 0.1400 seconds
我们可以看到HBase打印数据时是通过面向单元格的方式分别输出每一列数据。可以看到确实打印了两次myrow-2,和预期的一样,后面还显示了每一列的实际数值。
如果我们想要获取单行数据,可以使用get命令:
hbase(main):008:0> get 'testtable','myrow-1'
ROW COLUMN_CELL
colfam1:q1 timestamp=1559206752410,value= value-1
1 row(s) in 0.0480 seconds
删除数据也是基本操作之一,我们来看一下删除一个具体的单元格,并检查数据是否真的删除了:
hbase(main):009:0> delete 'testtable','myrow-2','colfam1:q2'
0 row(s) in 0.0390 seconds
hbase(main):010:0> scan 'testtable'
ROW COLUMN_CELL
myrow-1 column=colfam1:q1,timestamp=1559207110853,value= value-1
myrow-2 column=colfam1:q3,timestamp=1559207110868,value= value-3
2 row(s) in 0.0620 seconds
我们看到,通过delete命令确实删除了表中的数据。接下来,我们删除这张表:
hbase(main):011:0> disable 'testtable'
0 row(s) in 2.1250 seconds
hbase(main):012:0> drop 'testtable'
0 row(s) in 2878 seconds
删除表之前需要禁用表,然后再删除。
然后,通过输入exit命令关闭Shell并返回命令行窗口:
hbase(main):013:0> exit
$ _
最后,运行stop-hbase.sh脚本关闭HBase系统:
$HBASE_HOME/bin/stop-hbase.sh
stopping hbase..........
一旦启动了这个脚本,将会看到一条描述集群正在停止的信息,该信息会周期性的打印“.”字符,这仅仅表明脚本正在运行,并不是运行进度的反馈或隐藏的有用信息。关闭脚本大概需要几分钟完成。如果集群中机器的数量很多,那么执行的时间可能更长。我们在关闭Hadoop集群之前一定要确认HBase已经被正常关闭了。
三、HBase API的使用
3.1 HBaseConfiguration:这是每个hbase client都会使用到的对象,它代表hbase的配置信息。具有两个构造函数:
/**
* Instantiating HBaseConfiguration() is deprecated. Please use
* HBaseConfiguration#create() to construct a plain Configuration
* @deprecated Please use create() instead.
*/
@Deprecated
public HBaseConfiguration() {
//TODO:replace with private constructor, HBaseConfiguration should not extend Configuration
super();
addHbaseResources(this);
LOG.warn("instantiating HBaseConfiguration() is deprecated. Please use"
+ " HBaseConfiguration#create() to construct a plain Configuration");
}
/**
* Instantiating HBaseConfiguration() is deprecated. Please use
* HBaseConfiguration#create(conf) to construct a plain Configuration
* @deprecated Please user create(conf) instead.
*/
@Deprecated
public HBaseConfiguration(final Configuration c) {
//TODO:replace with private constructor
this();
merge(this, c);
}
可以看到两个构造函数在高版本中已经过时了,取而代之的是两个静态方法create:
/**
* Creates a Configuration with HBase resources
* @return a Configuration with HBase resources
*/
public static Configuration create() {
Configuration conf = new Configuration();
// In case HBaseConfiguration is loaded from a different classloader than
// Configuration, conf needs to be set with appropriate class loader to resolve
// HBase resources.
conf.setClassLoader(HBaseConfiguration.class.getClassLoader());
return addHbaseResources(conf);
}
/**
* @param that Configuration to clone.
* @return a Configuration created with the hbase-*.xml files plus
* the given configuration.
*/
public static Configuration create(final Configuration that) {
Configuration conf = create();
merge(conf, that);
return conf;
}
默认的构造函数会加载hbase-default.xml和hbase-site.xml中的配置信息:
public static Configuration addHbaseResources(Configuration conf) {
conf.addResource("hbase-default.xml");
conf.addResource("hbase-site.xml");
checkDefaultsVersion(conf);
return conf;
}
如果classpath没有这两个文件,就需要你自己设置配置:
Configuration conf = new Configuration();
conf.set(“hbase.zookeeper.quorum”, “zkServer”);
conf.set(“hbase.zookeeper.property.clientPort”, “2181″);
HBaseConfiguration hbaseConf = new HBaseConfiguration(conf);
3.2 创建表
创建表之前首先需要连接上HBase:
Configuration conf = HBaseConfiguration.create();
//创建hbase的连接,这是一个分布式连接
Connection conn = ConnectionFactory.createConnection(conf);
连接上HBase后,可以通过Connection来获取HBaseAdmin实例:
//获取HBaseAdmin实例
HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();
创建表是通过HBaseAdmin对象来操作的,HBaseAdmin负责表的META信息处理。它提供了createTable这个方法:
public void createTable(TableDescriptor desc)
public void createTable(TableDescriptor desc, byte [] startKey,byte [] endKey, int numRegions)
public void createTable(final TableDescriptor desc, byte [][] splitKeys)
下面,我们来创建3个列族的表:
Configuration conf = HBaseConfiguration.create();
//创建hbase的连接,这是一个分布式连接
Connection conn = ConnectionFactory.createConnection(conf);
//这个admin是管理table时使用的,比如说创建表
HBaseAdmin admin = (HBaseAdmin) conn.getAdmin();
//创建表名
TableName tableName = TableName.valueOf("user");
//创建列族
//ColumnFamilyDescriptor:代表的是列族的schema
ColumnFamilyDescriptor family1 = ColumnFamilyDescriptorBuilder.of("info1");
ColumnFamilyDescriptor family2 = ColumnFamilyDescriptorBuilder.of("info2");
ColumnFamilyDescriptor family3 = ColumnFamilyDescriptorBuilder.of("info3");
//TableDescriptor:代表的是表的schema
TableDescriptor table = TableDescriptorBuilder.newBuilder(tableName)
.addColumnFamily(family1)
.addColumnFamily(family2)
.addColumnFamily(family3)
.build();
//创建表
admin.createTable(table);
3.3 插入和修改数据
table = conn.getTable(TableName.valueOf("user"));
//构造参数是row_key,必传
Put put = new Put(Bytes.toBytes("zhangsan_123"));
//这里的参数依次为:列族名,列名,值
put.addColumn(Bytes.toBytes("info2"),Bytes.toBytes("name"),Bytes.toBytes("lisi"));
put.addColumn(Bytes.toBytes("info2"),Bytes.toBytes("age"),Bytes.toBytes(22 ));
put.addColumn(Bytes.toBytes("info2"),Bytes.toBytes("sex"),Bytes.toBytes("男"));
put.addColumn(Bytes.toBytes("info2"),Bytes.toBytes("address"),Bytes.toBytes("天堂" ));
table.put(put);
//table.put(List); //通过一个List集合,可以添加一个集合
3.4 查询数据
查询单条数据
String rowKey = "zhangsan_123";
Get get = new Get(Bytes.toBytes(rowKey));
Result result = table.get(get);
byte[] address = result.getValue(Bytes.toBytes("info2"), Bytes.toBytes("address"));
byte[] name = result.getValue(Bytes.toBytes("info2"), Bytes.toBytes("name"));
byte[] sex = result.getValue(Bytes.toBytes("info2"), Bytes.toBytes("sex"));
byte[] age = result.getValue(Bytes.toBytes("info2"), Bytes.toBytes("age"));
System.out.print(Bytes.toString(name) + ",");
System.out.print(Bytes.toString(sex) + ",");
System.out.print(Bytes.toString(address) + ",");
System.out.print(Bytes.toInt(age) + ",");
System.out.println();
全表扫描
完整的全表扫描要慎用,不过全表扫描中可以指定很多过滤器,我们可以很好的使用它。
Scan scan = new Scan();
ResultScanner resultScanner = table.getScanner(scan);
printResult(resultScanner);
区间扫描
区间扫描我们用处也很多,因为row key是按照字典序来排列的,我们可以根据这个特性,查找某一个用户指定时间段的数据,比如查询用户A昨天到今天的数据在查询的过程中,我们还可以指定返回的结果,比如指定返回一个列族,以及返回指定的列,这样可以增加查询速度
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes("zhangsan_1232")); //设置开始行
scan.withStopRow(Bytes.toBytes("zhangsan_12352")); //设置结束行
scan.addFamily(Bytes.toBytes("info2"));//查询指定列族
ResultScanner resultScanner = table.getScanner(scan);
printResult(resultScanner);
列值过滤器
列值过滤器也是我们很常用的操作,它提供了一种甚至值的条件查询。类似sql中的where field = 'xxx',这极大的扩展了hbase的查询方式,因为如果只是根据row key来查询,那么很多产景都不适用hbase。
Scan scan = new Scan();
/*
* 第一个参数: 列族
* 第二个参数: 列名
* 第三个参数: 是一个枚举类型
* CompareOp.EQUAL 等于
* CompareOp.LESS 小于
* CompareOp.LESS_OR_EQUAL 小于或等于
* CompareOp.NOT_EQUAL 不等于
* CompareOp.GREATER_OR_EQUAL 大于或等于
* CompareOp.GREATER 大于
*/
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(Bytes.toBytes("info2"), Bytes.toBytes("name"), CompareFilter.CompareOp.GREATER_OR_EQUAL, Bytes.toBytes("zhangsan8"));
//这个方法很重要,需要注意,当此过滤器过滤时,如果遇到该列值为NULL的情况,如果设置的参数为true,则会过滤掉这一行,如果设置的参数为false,那么则会把这一行的结果返回,默认为false
singleColumnValueFilter.setFilterIfMissing(true);
scan.setFilter(singleColumnValueFilter);
ResultScanner resultScanner = table.getScanner(scan);
printResult(resultScanner);
前缀过滤器
前缀过滤器和表里面的内容没有关系,它只是用来匹配指定的列的,比如有这样两个列 name1 和name2 ,通过这个过滤器,就会查询这两个列的所有数据,当然,其实这个方式和scan.addColumn差不多,并且它会匹配到多个列族。
ColumnPrefixFilter columnPrefixFilter = new ColumnPrefixFilter(Bytes.toBytes("name"));
Scan scan = new Scan();
scan.setFilter(columnPrefixFilter);
ResultScanner resultScanner = table.getScanner(scan);
printResult(resultScanner);
row_key正则
row_key正则查找也是hbase查找中经常用到的功能。
//查找以指定内容开头的
Filter rowKeyFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator("^zhangsan_1239"));
Scan scan = new Scan();
scan.setFilter(rowKeyFilter);
ResultScanner resultScanner = table.getScanner(scan);
printResult(resultScanner);
组合过滤器
我们可能有这样的情况,我们想扫描指定row key范围的,又想指定address为深圳的,或者是更多的一些条件,这个时候我们就需要多个过滤器组合使用。
/*
* 我们需要注意Operator这个参数,这是一个枚举类型,里面有两个类型
* Operator.MUST_PASS_ALL 需要通过全部的条件,也就是并且,and &&
* Operator.MUST_PASS_ONE 任何一个条件满足都可以,也就是或者,or ||
*/
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
//row key正则表达式的过滤器
Filter rowKeyFilter = new RowFilter(CompareFilter.CompareOp.EQUAL, new RegexStringComparator("^zhangsan_1239"));
//列值过滤器
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(Bytes.toBytes("info1"), Bytes.toBytes("name"), CompareFilter.CompareOp.EQUAL, Bytes.toBytes("zhangsan9"));
//把两个filter添加进filterList中
filterList.addFilter(rowKeyFilter);
filterList.addFilter(singleColumnValueFilter);
Scan scan = new Scan();
scan.setFilter(filterList);
ResultScanner resultScanner = table.getScanner(scan);
printResult(resultScanner);
最后附上printResult方法:
private void printResult(ResultScanner resultScanner) {
for (Result result : resultScanner) {
byte[] address = result.getValue(Bytes.toBytes("info1"), Bytes.toBytes("address"));
byte[] name = result.getValue(Bytes.toBytes("info1"), Bytes.toBytes("name"));
byte[] sex = result.getValue(Bytes.toBytes("info1"), Bytes.toBytes("sex"));
byte[] age = result.getValue(Bytes.toBytes("info1"), Bytes.toBytes("age"));
byte[] rowKey = result.getRow();
System.out.print(Bytes.toString(rowKey) + ",");
System.out.print(Bytes.toString(name) + ",");
System.out.print(Bytes.toString(sex) + ",");
System.out.print(Bytes.toString(address) + ",");
System.out.print((age == null ? null : Bytes.toInt(age)) + ",");
System.out.println();
}
}
3.5 删除数据
删除数据使用的是Delete对象,我们可以删除一行,或者删除一个列族,或者删除一个列族中的指定列:
//删除一行
Delete deleteRow = new Delete(Bytes.toBytes("zhangsan_1235"));
Delete deleteCol = new Delete(Bytes.toBytes("zhangsan_1235"));
//删除该行的指定列
deleteCol.addFamily(Bytes.toBytes("info1"));列族
//删除指定的一个单元
deleteCol.addColumn(Bytes.toBytes("info1"),Bytes.toBytes("name"));
table.delete(deleteCol);
table.delete(deleteRow);
//table.delete(List); //通过添加一个list集合,可以删除多个
3.6 删除表
//删除表之前需要禁用表
admin.disableTable(TableName.valueOf(tablename));
admin.deleteTable(TableName.valueOf(tablename));