一、 hbase架构简介
HBase 是一个开源的、分布式的、数据多版本的,列式存储的nosql数据库。依托 Hadoop 的分布式文件系统 HDFS 作为底层存储, 能够为数十亿行数百万列的海量数据表提供随机、实时的读写访问。 HBase 集群服务包含:HBase 数据库服务、HDFS 分布式文件系统、Phoenix 查询引擎。压缩格式方面支持 GZIP、BZIP2、LZO、SNAPPY,可自行在应用中指定。 关于 HBase 更多的详细信息,可参阅 HBase 官方文档,关于 Phoenix 查询引擎的详细信息,可参阅 Phoenix 官方网站 。
与 Hadoop 一样,HBase 集群采用的是 master/slave 架构。 如下图所示,HBase 集群分三种节点类型:主节点 (HBase Master 和 HDFS NameNode),从节点 (HBase RegionServer 和 HDFS DataNode) 和客户端节点 (HBase Client)。 用户在HBase 客户端可通过HBase Shell、Java API(本地或MapReduce)、Rest API 或其他工具来访问HBase。 若需要使用除java外的其他语言时,可在客户端节点 (HBase Client)自行启动 Thrift Server 以供支持。
二、 hbase源码编译
1. 下载源码
$ git://git.apache.org/hbase.git
2. hbase编译
$ mvn clean package -DskipTests assembly:single
$ ls hbase-assembly/target/
三、 hbase配置安装
1. 配置JAVA_HOME
$ vim ./hbase/conf/hbase-env.sh
export JAVA_HOME=/usr/lib/java
export HBASE_MANAGES_ZK=true
2. 配置HDFS
$ vim ./hbase/conf/hbase-site.xml
hbase.rootdir
hdfs://master:9000/hbase
hbase.cluster.distributed
true
hbase.master
master:60000
hbase.master.port
60000
hbase.master.info.port
60001
hbase.zookeeper.quorum
master,slave1,slave2
hbase.zookeeper.property.dataDir
/home/spark/workspace/zookeeper/data
其中第一个属性指定本机的hbase的存储目录,必须与Hadoop集群的core-site.xml文件配置保持一致;第二个属性指定hbase的运行模式,true代表全分布模式;第三第四个属性指定hbase master的ip和port;第五个属性指定 Zookeeper 管控机器,一般为奇数个;第六个属性是数据存放的路径。这里我使用的默认的 HBase 自带的 Zookeeper。
3. 配置regionservers
$ vim ./hbase/conf/regionservers
slave1
slave2
4. hbase安装运行
$ pscp -r -h all_iplist hbase /home/hbase/
$ ./bin/start-hbase.sh
$ /home/hbase/bin/hbase shell
list
四、 hbase测试
1. hbase表创建插入查询删除操作
$ ./bin/hbase shell
hbase(main):001:0> create 'test', 'cf'
0 row(s) in 1.2130 seconds
=> Hbase::Table - test
hbase(main):002:0> list 'test'
TABLE
test
1 row(s) in 0.0180 seconds
=> ["test"]
hbase(main):003:0> put 'test', 'row1', 'cf:a', 'value1'
0 row(s) in 0.0850 seconds
hbase(main):004:0> put 'test', 'row2', 'cf:b', 'value2'
0 row(s) in 0.0110 seconds
hbase(main):005:0> put 'test', 'row3', 'cf:c', 'value3'
0 row(s) in 0.0100 seconds
hbase(main):006:0> scan 'test'
ROW COLUMN+CELL
row1 column=cf:a, timestamp=1469163844008, value=value1
row2 column=cf:b, timestamp=1469163862005, value=value2
row3 column=cf:c, timestamp=1469163899601, value=value3
3 row(s) in 0.0230 seconds
hbase(main):007:0> get 'test', 'row1'
COLUMN CELL
cf:a timestamp=1469094709015, value=value1
1 row(s) in 0.0350 seconds
hbase(main):008:0> disable 'test'
0 row(s) in 1.1820 seconds
hbase(main):009:0> drop 'test'
0 row(s) in 0.1370 seconds
2. benchmark性能测试
# 测试随机写,预分区10个 region,使用多线程代替 MapReduce 的方式来并发随机写操作,10个线程,每个线程写10000行。
$ ./bin/hbase pe --nomapred --rows=10000 --presplit=10 randomWrite 10
# 测试顺序写,预分区10个 region,使用多线程代替 MapReduce 的方式来并发顺序写操作,10个线程,每个线程写10000行。
$ ./bin/hbase pe --nomapred --rows=10000 --presplit=10 sequentialWrite 10
# 测试基于 row 的自增操作,使用多线程代替 MapReduce 的方式来并发自增操作,10个线程,每个线程 increment 10000次。
$ ./bin/hbase pe --rows=10000 --nomapred increment 10
# 测试基于row的追加操作,使用多线程代替 MapReduce 的方式来并发追加操作,10个线程,每个线程 append 10000次。
$ ./bin/hbase pe --rows=10000 --nomapred append 10
# 测试随机读,使用多线程代替 MapReduce 的方式来并发随机读操作,10个线程,每个线程读10000行
$ ./bin/hbase pe --nomapred --rows=10000 randomRead 10
# 测试顺序读,使用多线程代替 MapReduce 的方式来并发顺序读操作,10个线程,每个线程读10000行
$ ./bin/hbase pe --nomapred --rows=10000 sequentialRead 10
# 测试范围scan操作,使用多线程代替 MapReduce 的方式来并发范围 scan 操作,10个线程,每个线程 scan 10000次,每次范围返回最大100行。
$ ./bin/hbase pe --rows=10000 --nomapred scanRange100 10
3. 其他测试,待整理
这个测试是通过 MapReduce 服务来批量导入 HDFS 中数据到 HBase
注解
已创建 Hadoop 集群并完成上文创建 HBase 客户端中使用 MapReduce 服务配置。
可通过 DistCp 命令来拷贝不同 HDFS 中的数据,关于 DistCp 更多的详细信息,可参阅 DistCp
使用 MapReduce 导入数据有三种方案:
一、直接书写 MapReduce 使用 HBase 提供的 JAVA API 从 HDFS 导入到 HBase 表。
二、书写 MapReduce 将 HDFS 中数据转化为 HFile 格式,再使用 HBase 的 BulkLoad 工具导入到 HBase 表。
三、使用 HBase ImportTsv 工具将格式化的 HDFS 数据导入到 HBase 表。
若要导入的数据已经是格式化的数据(有固定的分隔符),不需要自己实现 MapReduce 做进一步数据清洗,直接采用方案三;若数据并未格式化仍需规整则采用方案二。
以下方案中均使用 HBase 表 test_import,包含一个column family:content,可通过 HBase Shell 预先建好表
$ cd /usr/local/hbase
$ bin/hbase shell
hbase(main):001:0> create 'test_import', 'content'
0 row(s) in 1.2130 seconds
=> Hbase::Table - test_import
项目若使用mvn构建,pom.xml 中增加如下内容:
1.2.2
org.apache.hbase
hbase-server
${hbase.version}
方案一 MapReduce 代码如下,先创建表,在 Map 中完成数据解析,在 Reduce 中完成入库。Reduce的个数相当于入库线程数。
package com.qingcloud.hbase
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.mapreduce.TableReducer;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class ImportByMR {
private static String table = "test_import";
private static class ImportByMRMapper extends Mapper {
@Override
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] sp = value.toString().split(" ");
if (sp.length < 2) {
return;
}
context.write(new Text(sp[0]), new Text(sp[1]));
}
}
private static class ImportByMRReducer extends TableReducer {
@Override
public void reduce(Text key, Iterable value, Context context) throws IOException, InterruptedException {
byte[] bRowKey = key.toString().getBytes();
ImmutableBytesWritable rowKey = new ImmutableBytesWritable(bRowKey);
for (Text t : value) {
Put p = new Put(bRowKey);
p.setDurability(Durability.SKIP_WAL);
p.addColumn("content".getBytes(), "a".getBytes(), t.toString().getBytes());
context.write(rowKey, p);
}
}
}
private static void createTable(Configuration conf) throws IOException {
TableName tableName = TableName.valueOf(table);
Connection connection = ConnectionFactory.createConnection(conf);
Admin admin = connection.getAdmin();
if (admin.tableExists(tableName)) {
System.out.println("table exists!recreating.......");
admin.disableTable(tableName);
admin.deleteTable(tableName);
}
HTableDescriptor htd = new HTableDescriptor(tableName);
HColumnDescriptor tcd = new HColumnDescriptor("content");
htd.addFamily(tcd);
admin.createTable(htd);
}
public static void main(String[] argv) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = HBaseConfiguration.create();
File file = new File("/usr/local/hbase/conf/hbase-site.xml");
FileInputStream in = new FileInputStream(file);
conf.addResource(in);
createTable(conf);
GenericOptionsParser optionParser = new GenericOptionsParser(conf, argv);
String[] remainingArgs = optionParser.getRemainingArgs();
Job job = Job.getInstance(conf, ImportByMR.class.getSimpleName());
job.setJarByClass(ImportByMR.class);
job.setMapperClass(ImportByMRMapper.class);
TableMapReduceUtil.initTableReducerJob(table, ImportByMRReducer.class, job);
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(Text.class);
job.setOutputKeyClass(ImmutableBytesWritable.class);
job.setOutputValueClass(Mutation.class);
job.setNumReduceTasks(1);
FileInputFormat.addInputPath(job, new Path(remainingArgs[0]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
hbase-tools-1.0.0.jar 是将上述代码打成的jar包,APP_HOME 是 jar 包所放置的目录,/user/inputPath 下是需要导入到HBase中的数据。 数据格式为 rowkey value,两列空格分隔。需自行准备后通过 bin/hdfs dfs -put 到 HDFS 的 /user/inputPath 目录。 依次执行下述命令:
$ cd /usr/local/hadoop
$ bin/hadoop jar $APP_HOME/hbase-tools-1.0.0.jar com.qingcloud.hbase.ImportByMR /user/inputPath
执行成功后可简单通过测试一中的 HBase Shell 来验证数据。
方案二 MapReduce 代码如下,Map 对数据做进一步处理,Reduce 无需指定,会根据 Map 的 outputValue 自动选择实现。
package com.qingcloud.hbase
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class ImportByBulkLoad {
private static String myTable = "test_import";
private static class ImportByBulkLoadMapper extends Mapper {
@Override
public void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String[] sp = value.toString().split(" ");
if (sp.length < 2) {
return;
}
byte[] bRowKey = sp[0].getBytes();
ImmutableBytesWritable rowKey = new ImmutableBytesWritable(bRowKey);
Put p = new Put(bRowKey);
p.setDurability(Durability.SKIP_WAL);
p.addColumn("content".getBytes(), "a".getBytes(), sp[1].getBytes());
context.write(rowKey, p);
}
}
public static void main(String[] argv) throws IOException, ClassNotFoundException, InterruptedException {
Configuration conf = HBaseConfiguration.create();
File file = new File("/usr/local/hbase/conf/hbase-site.xml");
FileInputStream in = new FileInputStream(file);
conf.addResource(in);
GenericOptionsParser optionParser = new GenericOptionsParser(conf, argv);
String[] remainingArgs = optionParser.getRemainingArgs();
Job job = Job.getInstance(conf, ImportByBulkLoad.class.getSimpleName());
job.setJarByClass(ImportByBulkLoad.class);
job.setMapperClass(ImportByBulkLoadMapper.class);
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
job.setMapOutputValueClass(Put.class);
FileInputFormat.addInputPath(job, new Path(remainingArgs[0]));
HFileOutputFormat2.setOutputPath(job, new Path(remainingArgs[1]));
TableName tableName = TableName.valueOf(myTable);
Connection connection = ConnectionFactory.createConnection(conf);
Table table = connection.getTable(tableName);
RegionLocator regionLocator = connection.getRegionLocator(tableName);
HFileOutputFormat2.configureIncrementalLoad(job, table, regionLocator);
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
hbase-tools-1.0.0.jar 是将上述代码打成的 jar 包,APP_HOME 是 jar 包所放置的目录,/user/inputPath 下是需要导入到HBase中的数据。数据格式为 rowkey value,两列空格分隔。 需自行准备后通过 bin/hdfs dfs -put 到 HDFS 的 /user/inputPath 目录。 /user/outputPath 是 MapReduce 生成的 HFile 格式的结果。test_import 是 HBase 表名。依次执行下述命令:
$ cd /usr/local/hadoop
$ bin/hdfs dfs -rmr /user/outputPath
$ export HADOOP_CLASSPATH=`/usr/local/hbase/bin/hbase classpath`
$ bin/hadoop jar $APP_HOME/hbase-tools-1.0.0.jar com.qingcloud.hbase.ImportByBulkLoad /user/inputPath /user/outputPath
$ bin/hadoop jar /usr/local/hbase/lib/hbase-server-.jar completebulkload /user/outputPath test_import
执行成功后可简单通过测试一中的 HBase Shell 来验证数据。
方案三无需书写代码,/user/inputPath 下是需要导入到 HBase 中的数据。数据格式为 rowkey value,两列空格分隔。需自行准备后通过 bin/hdfs dfs -put 到 HDFS 的 /user/inputPath 目录。 /user/outputPath 是 HFile 格式的暂存结果。test_import是HBase表名。依次执行下述命令:
$ cd /usr/local/hadoop
$ bin/hdfs dfs -rmr /user/outputPath
$ export HADOOP_CLASSPATH=`/usr/local/hbase/bin/hbase classpath`
$ bin/hadoop jar /usr/local/hbase/lib/hbase-server-.jar importtsv -Dimporttsv.columns=HBASE_ROW_KEY,content:a -Dimporttsv.bulk.output=/user/outputPath test_import /user/inputPath
或
$ cd /usr/local/hadoop
$ bin/hdfs dfs -rmr /user/outputPath
$ cd /usr/local/hbase
$ bin/hbase org.apache.hadoop.hbase.mapreduce.ImportTsv -Dimporttsv.columns=HBASE_ROW_KEY,content:a -Dimporttsv.bulk.output=/user/outputPath test_import /user/inputPath
执行成功后可简单通过测试一中的 HBase Shell 来验证数据。