HBase 是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。
一、HBase 逻辑结构
逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。但从 HBase 的底层物理存储结构(K-V)来看,HBase更像是一个 multi-dimensional map。

将多列分为列簇(不同的列簇存放在不同的文件夹中),列簇中可以只包含一个列,列簇中列的数量可以动态增加。
Row key(行键,其值唯一),按字典序排列(按位比较),必须存在。
Region是大表横向的一个切片,上图中划分了三个region,按数据量切分(根据Row key进行分类),不同的region存放在不同的文件夹。
store,是存储的数据,上图中划分了六个store,最终存放在HDFS中。
同一个Region下,不同store一定对应不同的列簇。
同一列簇下不同store,一定属于不同Region。
二、HBase 物理存储结构

查询数据时,返回时间戳(TimeStamp)最大的版本。
三、HBase 数据模型
(1)Name Space
命名空间,类似于关系型数据库的 database概念,每个命名空间下有多个表。Hbase 有两个自带的命名空间,分别是 hbase和 default;hbase 中存放的是HBase 内置的表,default表是用户默认使用的命名空间。
(2)Region
类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往 HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。
(3)Row
HBase 表中的每行数据都由一个RowKey和多个 Column(列)组成,数据是按照 RowKey
的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。
(4)Column
HBase 中的每个列都由 Column Family(列族)和Column Qualifier(列限定符)进行限定,例如 info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。
(5)Time Stamp
用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会自动为其加上该字段,其值为写入 HBase 的时间。
(6)Cell
由 {rowkey, column Family:column Qualifier,time Stamp} 唯一确定的单元。cell 中的数据是没有类型的,全部是字节码形式存储。
四、HBase 基本知识
4.1 HBase 安装
(1)安装部署zookeeper
(2)安装部署Hadoop
(3)获取并解压HBase安装包
(4)分别修改配置文件hbase-env.sh、hbase-site.xml、regionservers
1)在regionservers文件中写入集群地址
2)在hbase-env.sh文件中配置 JAVA_HOME地址,并注释掉以下内容
![]()
将以下变量设置为false,表示不使用HBase自身带有的zookeeper
![]()
3)修改hbase-site.xml文件内容


4)软连接hadoop 配置文件到HBase
ln -s /opt/module/hadoop-2.7.2/etc/hadoop/core-site.xml /opt/module/hbase/conf/core-site.xml
ln -s /opt/module/hadoop-2.7.2/etc/hadoop/hdfs-site.xml /opt/module/hbase/conf/hdfs-site.xml
5)远程发送HBase文件到集群其他节点
集群的启动及关闭
单节点启动
bin/hbase-daemon.sh start master
bin/hbase-daemon.sh start regionserver
集群启动/关闭
bin/start-hbase.sh
bin/stop-hbase.sh
4.2 HBase 基本架构

Master 用于管理表结构。可以启用多个Master 保证集群的高可用。
RregionServer 用于管理数据的增删改查,一个RregionServer管理多个Rregion。
Rregion管理多个列簇(其物理结构为Store,不同列簇存储是分离的)
数据刚开始存储在Mem Store(内存)中,之后会刷写在磁盘文件中。
zookeeper帮助Master分担了客户端请求操作数据的工作。
HLog,预写入日志,记录操作,防止内存数据在落盘前丢失。
4.3 HBase 写数据流程
HBase的读比写要慢。

HBase中有两个系统表,meta和namespace,meta表存储了用户表的信息,namespace表存储用户定义的命名空间名称。
zookeeper中存储了HBase的meta所在的节点位置。HBase老版本0.9x中,zookeeper存储的是-root-表所在节点位置,-root-表内存储了meta表位置信息,这是为了防止meta表进行分割,产生多个meta表。
源码中第7步写入操作日志是异步执行,第7步提交任务后,接着进行第8步写入内存操作,第8步执行完会判断第7步是否执行成功,如果失败则进行回滚(将内存中的数据进行删除)。
第9步之后,hbase等到MemStore的刷写时机后,将数据刷写到HDFS上。
Flush流程
刷写时机(以内存大小为依据):
1、regionserver.global.memstore.size(默认值为regionserver堆内存的0.4)
当 region server中memstore 的总大小超过该值时,进行flush操作,此时会阻塞客户端的读写操作。
2、hbase.regionserver.global.memstore.size.lower.limit(默认值上述值的0.95)
即计算:java.heapsize * regionserver.global.memstore.size * hbase.regionserver.global.memstore.size.lower.limit
当 region server中memstore 的总大小超过该值时,会按照其所有 memstore 的大小顺序(由大到小)依次进行刷写,且不会阻塞客户端的读写操作。如果客户端写入操作速度大于刷写速度,使memstore的总大小超过第一个计算值时,会阻塞客户端的读写操作。
3、hbase.hregion.memstore.flush.size(默认值为128M)当单个region的memstore大小超过该值时进行刷写。
4、以WAL日志文件大小为依据(新版本已废除)

当单个region的数据大小超过该值进行刷写操作,此时只刷写该region内的数据。
刷写时机(以在内存中停留的时间为依据):
1、hbase.regionserver.optionalcacheflushinterval(默认值为1个小时)
当内存中文件最后一次编辑的时间超过该值时,进行刷写操作(即最后一条数据存在超过一小时)。
flush刷写数据时,对内存中未落盘的重复数据会进行删除,如果刷写时有delete标记的数据,同内存的该数据会被删除,但该数据的delete标记数据会保存。
4.4 HBase 读数据流程

第七步读取数据时,分别在BlockCache(读缓存),MemStore和StoreFile(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(timestamp)或者不同的类型(PutDelete)。
然后,将从文件中查询到的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。
最后,将合并后的最终结果返回给客户端。
4.5 Compact 流程
由于 memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的 HFile 中,因此査询时需要遍历所有的 HFile。为了减少 HFile 的个数,以及清理掉过期和删除的数据,会进行 StoreFile Compaction。
Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction。Minor Compaction会将临近的若干个较小的 HFile 合并成一个较大的 HFile,但不会清理过期和删除的数据。Major Compaction 会将一个 Store 下的所有的 HFile 合并成一个大 HFile,并且会清理掉过期和删除数据。

当HFile文件数超过三个时,触发Minor Compaction后,也会执行Major Compaction。
hbase.hregion.majorcompaction设置Major Compaction执行周期,默认为7天,Major Compaction非常耗资源,建议生产关闭(设置为0),在应用空闲时间手动触发。
hbase.hstore.compactionThreshold参数默认为3,当HFile文件数超过该值时触发Minor Compaction。
4.6 Split 流程
默认情况下,每个 Table 起初只有一个 Region,随着数据的不断写入,Region 会自动进行拆分。刚拆分时,两个子 Region 都位于当前的 Region Server,但处于负载均衡的考虑HMaster 有可能会将某个 Region 转移给其他的 Region Server。
Region Split 时机:
1、当1个region中的某个Store 下所有 StoreFile的总大小超过hbase.hregion.max.filesize
该 Region 就会进行拆分(0.94 版本之前)。
2、当1 个 region 中的某个 Store 下所有 StoreFile 的总大小超过 Min(R^2 * hbase.hregion.memstore.flush.size, hbase.hregion.max.filesize),该Region 就会进行拆分,其中R为当前 Region Server 中属于该 Table 的个数(0.94 版本之后)。
根据第二条规则切分,如果row_key一直追加,则文件数据分布不均匀,前期切分的文件较小且不再增加,后期切分的数据文件较大,即数据倾斜问题。可以通过预分区来解决
官方建议最好使用一个列簇,为什么?
因为如果列簇设计不合理,会导致列簇数据分布倾斜,一些列簇数据较多,而一些列簇数据较少。在进行Region切分时,本来较少数据的列簇,数据会变更少,这时如果触发全局flush,这些少数据的列簇就会产生很多小文件。
五、HBase 命令行操作
5.1 基本操作
进入 HBase 客户端命令行 bin/hbase shell
查看帮助命令 help
查看当前数据库中有哪些表 list(只能查用户表,看不到系统表)
5.2 表的操作
查看命名空间 list_namespace
创建命名空间 create_namespace <命名空间名称>
删除命名空间(删除时,命名空间内不能有表存在) drop_namespace <命名空间名称>
创建表 (至少要有一个列簇)
create <表名>, <列簇名>,...,<列簇名>(create 'stu', 'info1', 'info2')
查看表结构 describe <表名>
变更表信息(必须对某一列簇进行修改),将info1 列簇中的数据存放三个版本:
alter <表名>, {NAME=>'info1', VERSIONS=>3}
删除表(删除表之前,必须先将表下线)
1)下线表 disable <表名>
2)删除表 drop <表名>
5.3 数据的操作
向表中添加数据/修改数据
put <命名空间名:表名>, <Row_key>, <列簇名:列名>, <值>
put 'stu', '1001', 'info1:name', 'zhangsan'
修改的数据没有真正的删除,只显示时间戳最大的数据
查看数据
scan <命名空间名:表名>, <过滤条件>
get <命名空间名:表名>, <Row_key>, <过滤条件>
查看表中旧数据 scan <命名空间名:表名>, {RAW => true, VERSIONS => 10}
删除数据
delete <命名空间名:表名>, <Row_key>, <列簇名:列名>, <时间戳>(至少三个参数)
删除整行数据(按照列簇依次删除) deleteall <命名空间名:表名>, <Row_key>
删除整张表的数据 truncate <命名空间名:表名>
六、HBase API操作
引入hbase-client的maven依赖
创建Admin对象来对表进行管理
// 1.获取配置文件信息
// 旧版本
// HBaseConfiguration configuration =new HBaseConfiguration();
Configuration configuration = HBaseConfiguration.create();
configuration.set("hbase.zookeeper.quorum","hadoop102, hadoop103, hadoop104");
// 2.获取管理员对象
// 旧版本
// HBaseAdmin admin =new HBaseAdmin(configuration);
Connection connection = ConnectionFactory.createConnection(configuration);
Admin admin = connection.getAdmin();
// 关闭资源
admin.close();
connection.close();
判断表是否存在
boolean exists = admin.tableExists(TableName.value0f(tableName));
创建表
public static void createTable(String tableName, String... cfs)throws I0Exception{
// 1.判断是否存在列族信息
if(cfs.length <= 0){
System. out.println("请设置列族信息!");
return;
}
// 2.判断表是否存在
if(isTableExist(tableName)){
System.out.println(tableName +"表已存在!");
return;
}
// 3.创建表描述器
HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.value0f(tableName));
// 4.循环添加列簇信息
for(String cf : cfs){
// 5.创建列簇描述器
HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(cf);
// 可以设置最大版本数
// hColumnDescriptor.setMaxVersions( num );
//添加具体的列簇信息
hTableDescriptor.addFamily(hColumnDescriptor);
}
// 7.创建表
admin.createTable(hTableDescriptor);
}
删除表
public static void dropTable(String tableName) throws IOException{
// 1.判断表是否存在
if(!isTableExist(tableName)){
System.out.println(tableName + "表不存在!!!");
return;
}
// 2.使表下线
admin.disableTable(TableName.valueOf(tableName));
// 3.删除表
admin.deleteTable(TableName.value0f(tableName));
}
创建命名空间
public static void createNameSpace(String ns){
// 1.创建命名空间描述器
NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(ns).build();
// 2.创建命令空间
try{
admin.createNamespace(namespaceDegcriptor);
}catch(NamespaceExistException e){
System.out.println(ns +"命名空间已存在!")
}
catch(IException e){
e.printStackTrace();
}
}
向表中插入数据
public static void putData(String tableName, String rowKey, String cf, String cn, String value) throws IOException{
// 1.获取表对象
Table table = connection.getTable(TableName.valueof(tableName));
// 2.创建Put对象
Put put = new Put(Bytes.toBytes(rowKey));
// 3.给Put对象赋值
put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn), Bytes.toBytes(value));
// 4.插入数据
table.put (put);
// 5.关闭表连接
table.close();
}
获取单行数据
public static void getData(String tableName, String rowkey, String cf, String cn) throws IOException{
// 1. 获取表对象
Table table = connection.getTable(TableName.valueof(tableName));
// 2.创建Get对象
Get get = new Get(Bytes.toBytes(rowKey))
// 2.1 通过 get.setMaxVersions( num ) 方法,可以返回指定的版本数(不超过表元数据版本设置的值)
// 2.2 通过 get.addFamily(Bytes.toBytes(cf))方法,可以获取指定的列簇的所有值
// 2.3 通过 get.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cn))方法,可以获取指定的列簇中列的值
// 3.获取数据
Result result = table.get(get);
//4.解析result,获取一行中多列数据
for(Cell cell : result.rawCells()){
// 5. 打印数据
System.out.println("CF:"+ Bytes.toString(CellUtil.cloneFamily(cell)) +
",CN:"+ Bytes.toString(CellUtil.cloneQualifier(cell)) +
",Value:"+ Bytes.toString(CellUtil.cloneValue(cell)));
}
}
// 6.关闭表连接
table.close();
}
获取多行数据(Scan)
public static void scanTable(String tableName)throws IOException{
// 1.获取表对象
Table table = connection.getTable(TableName.valueof(tableName));
// 2.构建Scan对象(空参对象表示扫描全表)
Scan scan = new Scan();
// 2.1设置扫描的起始和结束的RowKey,[startRowKey, endRowKey),左闭右开
// Scan scan = new Scan(Bytes.toBytes("1001"), Bytes.toBytes("1003"))
// 2.2 构建过滤器,常用的过滤器有CompareFilter(ValueFilter、QualifierFilter、FamilyFilter、RowFilter)、SingleColumnValueFilter
// RowFilter rowFilter = new RowFilter(编写具体的过滤规则);
// scan.setFilter(rowFilter);
// 3.扫描表
ResultScanner resultScanner = table.getScanner(scan);
// 4.解析resultScanner(其本质是result集合的封装,因为数据量可能太多所以进行封装)
for(Result result : resultScanner){
// 5. 解析result,并打印数据
for(Result cell : result){
System.out.println("RK:" + Bytes.toString(CellUtil.cloneRow(cell)) +
",CF:"+ Bytes.toString(CellUtil.cloneFamily(cell)) +
",CN:"+ Bytes.toString(CellUtil.cloneQualifier(cell)) +
",Value:"+ Bytes.toString(CellUtil.cloneValue(cell)));
}
}
// 6.关闭表连接
table.close();
}
删除数据
以RowKey为依据删除,每个列簇标记为DeleteFamily,每个版本都删除。
以RowKey + 列簇为依据删除,命令行不可以执行,使用API可以执行,删除的列簇标记为DeleteFamily,默认删除所有版本,如果传入时间戳参数,则删除小于等于该时间戳的版本。
以RowKey + 列簇 + 列名,delete.addColumns() 删除指定列的所有版本,如果传入时间戳参数,则会删除该列小于等于该时间戳的所有版本,删除的列标记为DeleteColumn。delete.addColumn() 删除指定列的最新版本,如果传入时间戳参数,则会删除该列的具体版本。删除的该列的版本标记为Delete。
本质来说delete操作就是put操作,对该数据新增一个版本,其值为上述描述的各类删除标记。
public static void deleteData(String tableName, String rowKey, String cf, String cn) throws IOException {
// 1.获取表对象
Table table = connection.getTable(TableName.valueof(tableName));
// 2.构建删除对象(只传rowKey,相当于命令行的deleteAll,删除整行数据)
Delete delete = new Delete(Bytes.toBytes(rowKey));
// 2.1 delete.addColumn() 删除指定列的最新版本,如果传入时间戳参数,则会删除该列的具体版本
// delete.addColumn() 如果在数据刷写前删除最新版本,查询时可以查出次新版本;如果数据刷写后删除最新版本,则该列数据为空,是真正删除,因此该方法慎用。
// 2.2 delete.addColumns() 删除指定列的所有版本,如果传入时间戳参数,则会删除该列小于等于该时间戳的所有版本
// 2.3删除指定的列簇
//delete.addFamily()
// 3.执行删除操作
table.delete(delete);
// 4.关闭连接
table.close();
}
七、与MR交互
通过 HBase 的相关 JavaAPI,可以实现伴随 HBase 操作的 MapReduce 过程,比如使用MapReduce 将数据从本地文件系统导入到 HBase 的表中,比如从 HBase 中读取一些原始数据后使用 MapReduce 做数据分析。
MapReduce的切片数是HBase表的Region数。
环境配置及测试
查看 Hbase 的 MapReduce 任务的执行 bin/hbase mapreduce
环境变量的导入:
(1)执行环境变量的导入(临时生效,在命令行执行下述操作)
export HBASE_HOME = /opt/module/hbase-1.3.1
export HADOOP_HOME = /opt/module/hadoop-2.7.2
export HADOOP CLASSPATH = '${SHBASE HOME}/bin/hbase mapreduce'
(2)永久生效:在/etc/profile 配置
export HBASE_HOME = /opt/module/hbase-1.3.1
export HADOOP_HOME = /opt/module/hadoop-2.7.2
并在 hadoop-env.sh 中配置:
export HADOOP_CLASSPATH = $HADOOP_CLASSPATH:/opt/module/hbase/lib/*
运行官方的MapReduce任务:
案例一:统计 student 表中有多少行数据(在Hbase目录下运行以下命令)
/opt/module/hadoop-2.7.2/bin/yarn jar lib/hbase-server-1.3.1.jar rowcounter student
案例二:使用 MapReduce 将本地数据导入到 HBase
1)在本地创建一个 tsv 格式的文件:fruit.tsv,并上传到HDFS

2)创建 HBase 表 create 'fruit' 'info'
3)执行MapReduce到HBase 的fuit 表中
/opt/module/hadoop-2.7.2/bin/yarn jar lib/hbase-server-1.3.1.jar importtsv -Dimporttsv.columns = HBASE_ROW_ KEY, info:name, info:color fruit hdfs://hadoop102:9000/fruit.tsv
# importtsv 是jar包(lib/hbase-server-1.3.1.jar)的主类名
# Dimporttsv.columns = HBASE_ROW_ KEY, info:name, info:color 设置文件列与表列的对应关系
# fruit 是导入的表名
# hdfs://hadoop102:9000/fruit.tsv 是输入数据路径
学习案例一:MR读取文件,将数据写入HBase表
将 fruit.tsv文件,通过MR迁入到 fruit1 表中
1)编写 Map类代码(主要做读取数据的工作)
public class FruitMapper extends Mapper<LongWritable, Text, LongWritable, Text>{
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException,InterruptedException{
context.write(key, value);
}
}
2)编写 Reducer 代码(继承TableReducer类,VALUEOU的泛型不用自己定义)
public class FruitReducer extends TableReducer <LongWritable, Text, NullWritable>{
@Override
protected void reduce(LongWritable key, Iterable<Text> values, Context context)throws IOException, InterruptedException{
//1.遍历values (1001 Apple Red)
for(Text value :values){
// 2.获取每一行数据
String[] fields = value.toString().split("\t");
// 3.构建Put对象
Put put = new Put(Bytes.toBytes(fields[0]));
// 4.给Put对象赋值(如果列簇和列名想要动态获取有两种方式,1.读取配置文件 2.通过context可以获取全局的配置信息,从而读取Main方法设置的值)
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("name"), Bytes.toBytes(fields[1]));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("color"), Bytes.toBytes(fields[2]));
// 5.写出数据
context.write(NullWritable.get(), put);
}
}
}
3)编写 Driver 代码
public class FruitDriver implements Tool{
// 定义一个Configuration
privete Configuration configuration = null;
@Override
public int run(String[] args)throws Exception{
// 1.获取Job对象
Job job = Job.getInstance(configuration);
// 2.设置驱动类路径
job.setJarByClass(FruitDriver.class);
// 3.设置Mapper&Mapper输出的KV类型
job.setMapperClass(FruitMapper.class);
job.setMapOutputKeyClass(LongWritable.class);
job.setMapOutputValueClass(Text.class);
// 4.设置Reducer类
TableMapReduceUtil.initTableReducerJob(args[1], FruitReducer.class, job);
// 5.设置输入参数
FileInputFormat.setInputPaths(job, new Path(args[0]));
// 6.提交任务
boolean result = job.waitForCompletion(true);
return result ? 0 : 1;
}
@Override
public void setConf(Configuration conf){
configuration = conf
}
@Override
public Configuration getConf(){
return configuration;
}
public static void main(String[] args){
try{
Configuration configuration = new Configuration();
int run = ToolRunner.run(configuration, new FruitDriver(),args);
System.exit(run);
}catch(Exception e){
e.printStackTrace();
}
}
}
学习案例二:MR读取HBase表数据,处理后将数据写入另一个HBase表
将 fruit1表中部分数据(name列),通过MR迁入到 fruit2 表中
1)编写 Map类代码(继承TableMapper类,定义输出key,value的泛型,输入的K为Row_Key,输入的值为Row_Key对应的Result)
public class Fruit2Mapper extends TableMapper<ImmutableBytesWritable, Put>{
@Override
protected void map(ImmutableBytesWritable key, Result value, Context context)throws IOException, InterruptedException {
// 1.构建Put对象
Put put = new Put(key. get());
// 2.获取数据
for(Cell cell : value.rawCells()){
// 3.判断当前的cell是否为“name”列
if("name".equals(Bytes.toString(CellUtil.cloneQualifier(cel1)))){
//4.给Put对象赋值
put.add(cell);
}
}
// 5.写出数据
context.write(key, put);
}
}
2)编写reducer程序
public class Fruit2Reducer extends TableReducer<ImmutableBytesWritable, Put,NullWritable>{
@Override
protected void reduce(ImmutableBytesWritable key, Iterable<Put> values, Context context)throws IOException, InterruptedException{
// 遍历写出
for (Put put : values){
context.write(NullWritable.get(), put);
}
}
}
3)编写Driver程序
public class Fruit2Driver implements Tool{
// 定义一个Configuration
privete Configuration configuration = null;
@Override
public int run(String[] args)throws Exception{
// 1.获取Job对象
Job job = Job.getInstance(configuration);
// 2.设置驱动类路径
job.setJarByClass(FruitDriver.class);
// 3.设置Mapper&Mapper输出的KV类型
TableMapReduceUtil.initTableMapperJob(args[0]
new Scan(),
Fruit2Mapper. class,
ImmutableBytesWritable.class,
Put.class,
job)
// 4.设置Reducer类
TableMapReduceUtil.initTableReducerJob(args[1], Fruit2Reducer.class, job);
// 5.设置输入参数
FileInputFormat.setInputPaths(job, new Path(args[0]));
// 6.提交任务
boolean result = job.waitForCompletion(true);
return result ? 0 : 1;
}
@Override
public void setConf(Configuration conf){
configuration = conf
}
@Override
public Configuration getConf(){
return configuration;
}
public static void main(String[] args){
try{
Configuration configuration = new Configuration();
int run = ToolRunner.run(configuration, new Fruit2Driver(),args);
System.exit(run);
}catch(Exception e){
e.printStackTrace();
}
}
}
八、与Hive集成
环境准备
因为后续可能会在操作 Hive 的同时对 HBase 也会产生影响,所以 Hive 需要持有操作HBase的Jar,那么接下来拷贝 Hive 所依赖的 Jar 包(或者使用软连接的形式)。
export HBASE_HOME=/opt/module/hbase
export HIVE_HOME=/opt/module/hive
ln -s $HBASE_HOME/lib/hbase-common-1.3.1.jar $HIVE_HOME/lib/hbase-common-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-server-1.3.1.jar $HIVE_HOME/lib/hbase-server-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-client-1.3.1.jar $HIVE_HOME/lib/hbase-client-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-protocol-1.3.1.jar $HIVE_HOME/lib/hbase-protoco1-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-it-1.3.1.jar $HIVE_HOME/lib/hbase-it-1.3.1.jar
ln -s $HBASE_HOME/lib/htrace-core-3.1.0-incubating.jar $HIVE_HOME/lib/htrace-core-3.1.0-incubating.jar
ln -s $HBASE_HOME/lib/hbase-hadoop2-compat-1.3.1.jar $HIVE_HOME/lib/hbase-hadoop2-compat-1.3.1.jar
ln -s $HBASE_HOME/lib/hbase-hadoop-compat-1.3.1.jar $HIVE_HOME/lib/hbase-hadoop-compat-1.3.1.jar
同时在hive-site.xml 中修改zookeeper的属性,如下:

学习案例一:新建 Hive 表与 HBase 表使相关联
目标:建立Hive 表,关联HBase 表,插入数据到Hive 表的同时能够影响 HBase 表。
分步实现:
(1)在 Hive 中创建表同时关联 HBase
CREATE TABLE hive_hbase_emp_table(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY 'org.apache,hadoop.hive.hbase.HBasStorageHandler'
WITH SERDEPROPERTIES("Hbase.columns.mapping" = ":key, info:ename, info:job, info:mgr info:hiredate, info:sal, info:comm, info:deptno")
TBLPROPERTIES("Hbase.table.name" = "hbase_emp_table");
完成之后,可以分别进入 Hive 和HBase 查看,都生成了对应的表。
(2)在 Hive 中创建临时中间表,用于 load 文件中的数据。
提示:不能将数据直接load 进 Hive 所关联 HBase 的那张表中。
CREATE TABLE emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
row format delimited fields terminated by '\t';
(3)向Hive中间表中load数据
load data local inpath 'path_data/emp.txt' into table emp;
(4)通过 insert 命令将中间表的数据导入到Hive 关联HBase的那张表中
insert into table hive_hbase_emp_table
select * from emp;
(5)查看 Hive 以及关联的 HBase表中都已同步插入数据
两张表关联,数据文件存放在HBase对应的HDFS中,而Hive对应的HDFS目录下没有数据文件。
学习案例二:创建 Hive 外部表与 HBase 已存在的表相关联
目标:在 HBase 中已经存储了某一张表 hbase_emp_table,然后在Hive 中创建一个外部表来关联 HBase 中的 hbase_emp_table 这张表,使可以借助 Hive 来分析 HBase 这张表中的数据。
(1)在 Hive 中创建外部表
CREATE EXTERNAL TABLE relevance_hbase_emp(
empno int,
ename string,
job string,
mgr int,
hiredate string,
sal double,
comm double,
deptno int)
STORED BY 'org.apache,hadoop.hive.hbase.HBasStorageHandler'
WITH SERDEPROPERTIES("Hbase.columns.mapping" = ":key, info:ename, info:job, info:mgr info:hiredate, info:sal, info:comm, info:deptno")
TBLPROPERTIES("Hbase.table.name" = "hbase_emp_table");
创建之后该外部表会与HBase中的hbase_emp_table表相关联,可以通过hive来对该表进行分析,如果再对HBase中的hbase_emp_table表中插入数据,该外部表也可以查询到新插入的数据。
九、HBase 优化
9.1 高可用
在 HBase 中HMaster 负贡监控 HRegionServer 的生命周期,均衡 RegionServer 的负载,如果 HMaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase 支持对 IMaster 的高可用配置。
在 hbase/conf 目录下创建 backup-masters 文件
在 backup-masters 文件中配置高可用 HMaster 的备用节点
如果HMaster宕机,则配置的备份节点会自动成为活跃的HMaster,选举机制与Kafka高可用一致,多个备份节点争抢向zookeeper注册。
9.2 预分区
每一个 region 维护着 StartRow 与 EndRow,如果加入的数据符合某个 Region 维护的RowKey 范围,则该数据交给这个 Region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高 HBase性能。
(1)手动设定预分区(设定5个分区)
create 'table_name', 'info1', 'info2', SPLITS => ['1000', '2000', '3000', '4000']

添加数据时,按字典序比较Row_Key,来决定分区(比如数据Row_Key为158742,会插入第二个分区)
(2)生成16进制序列预分区
create 'table_name', 'info1', 'info2', {NUMREGIONS => 15, SPLITLGO => 'HexStringSplit'}

(3)按照文件中设置的规则预分区
创建 split.txt 文件,内容如下:

然后执行:
create 'table_name', 'info1', SPLITS_FILE => 'split.txt'
(4)使用JavaAPI创建预分区
//自定义算法,产生一系列hash 散列值存储在二维数组中。
byte[][] splitkeys = 某个散列值函数
//创建 Hbaseadmin 实例
HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());
//创建 HrableDescriptor 实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
//通过 HrableDescriptor 实例和散列值二维数组创建带有预分区的 Hbase 表
hAdmin.createTable(tableDesc, splitkeys);
// 设置起始和结束key,按照numRegion分区数,将[starKey,endKey]这一区间平分
// hAdmin.createTable(tableDesc,starKey,endKey, numRegion);
9.3 RowKey设计
RowKey的设计要考虑,散列性(即数据要均匀的分布在各个预分区中)、唯一性、长度原则(生产环境中一般是70-100位)
在考虑散列性的同时也要考虑局部数据的集中性,即常在一起用的数据放在一个分区中。
常用的RowKey设计方案:
(1)生成随机数、hash、散列值

(2)字符串反转(如时间戳反转)

(3)字符串拼接

RowKey情景设计:使用HBase 存储每个人的通话记录(数据为:拨出手机号,接听手机号,拨号时间,通话时长)
假设要分300个region
设计分割键299个分别为(000|,001|,...,298|),299个分割键可以切分出300个region,每个region的RowKey以000_,001_,...,299_开头。
考虑到散列性和部分集中性,希望同一个人的通话记录尽量在一个分区,可以将拨出手机号与300取模运算,通过余数来指定分区。即RowKey为000_手机号
由于不同的人通话次数是不一样的,一些人的通话次数可能远超他人,因此可能会出现数据倾斜问题。
可以在原有基础上加上时间(具体到月),即将(手机号+时间)%300取余来确定分区。即RowKey为000_手机号_时间
9.4 内存优化
HBase 操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,一般会分配整个可用内存的 70%给 HBase 的 Java堆。但是不建议分配非常大的堆内存,因为 GC 过程持续太久会导致 RegionServer 处于长期不可用状态,一般16~48G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
十、HBase 实战之谷粒微博
表设计:

微博内容表以用户ID+时间戳作为RowKey,每发一条微博,添加一行来存储。列簇只有一个info,列名为固定的content,值为所发的微博内容。
用户关系表以用户ID作为RowKey,以两个列簇attends表示用户关注的人,fans表示关注该用户的人。列名为对应的关注/被关注的用户ID,值为空或者存关注的时间。
微博收件箱表,用于展示初始化页面(内容为关注人最近发的微博内容),RowKey是用户ID,列簇只有一个info,列名是该用户对应的attends列簇下的列名,值为关注用户所发的最近微博内容,值存储多个版本(为节省存储空间,值存储为内容的RowKey)。



5790

被折叠的 条评论
为什么被折叠?



