HBase学习记录
一、HBase简介
1.HBase定义
HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。
2.NoSQL数据库
NoSQL数据库,全程Not only SQL,指非关系型数据库,它没有关系型数据库的ACID特性。
3.Hbase逻辑结构
从逻辑上来看,HBase里的数据类似于一张表的格式
4.Hbase物理结构
从物理上来看,每一条数据的保存方式如下图所示:
5.Hbase的数据模型
1)Name Space
命名空间,类似于关系型数据库中的database,每个命名空间中有多个表。HBase有两个自带的命名空间,分别是hbase和default,hbase中存放的是HBase内置的表,default是用户默认使用的命名空间。
2)Table
类似于关系型数据库中的表。但是在HBase中,定义表时,只需要声明列族即可,不需要声明具体的列。
向HBase中写入数据时,字段可以动态、按需指定,所以可以轻松应对字段变更的情景。
3)Row
HBase表中每一行数据都是由一个RowKey和多个Column组成,数据是按照RowKey的字典序存储的,并且查询数据时只能根据RowKey进行检索。
4)Column
HBase中的每个列是由Column Family(列族)和Column Qualifier(列限定符)进行限定的,例如info: name。建表时,只需要指明列族,列限定符无需预先定义。
5)TimeStamp
在HBase中,对每一条数据的操作都会生成一个对应的时间戳,作为对这条数据的操作时间。
6)Cell
由{rowkey,column Family,column Qualifier,time Stamp}唯一确定的单元。
6.HBase架构(不完整版)
一些属性的解释:
store:一个列簇对应一个store,store默认存储为storeFile
region:HBase会把一张表的数据,横向切分成多个区域,一个区域就是一个region,那么自然一个Region里可能有多个store。通常情况下,一个表中的多个Region最好是分别放在不同的Region server中,这样在处理数据时,可以做到最好的负载
图中的架构角色:
1)Region Server
Region Server负责管理Region,而Region中存放的是具体的数据,所以它有以下的作用:
对于数据的:get、put、delete
对于Region的:splitRegion、compactRegion
2)Master
Master负责管理所有的Region Server,它也直接与表的元数据相关,所以它的作用有:
对于表的:create、delete、alter(全都跟表的元数据有关)
对于Region Server的:给每个Region Server分配Region,同时监控每个Region Server的状态,进行负载均衡和故障转移。
3)Zookeeper
毫无疑问,zookeeper负责master的高可用、RegionServer的监控(Region Server启动后要去zookeeper注册状态)、集群的配置维护等。
4)HDFS
HDFS为HBase提供最终底层的数据存储服务,同时为HBase提供高可用的支持。
二、HBase安装部署
1.Zookeeper安装部署
略
2.Hadoop安装部署
略(启动时要启动HDFS和Yarn)
3.HBase安装部署
1)首先,解压对应的压缩包,然后添加环境变量:sudo vim /etc/profile.d/my_env.sh
添加内容如下:
添加
#HBASE_HOME
export HBASE_HOME=/opt/module/hbase
export PATH=$PATH:$HBASE_HOME/bin
2)修改HBase的配置文件
i.先修改hbase-env.sh,修改内容如下:
export HBASE_MANAGES_ZK=false
表示不使用HBase里自带的zookeeper,使用自己安装的
ii.修改hbase-site.xml,修改内容如下:
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop102:8020/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
</configuration>
第一个配置:HBase数据存储到的HDFS的路径
第二个配置:开启HBase配置集群
第三个配置:使用的zookeeper地址(不写端口号)
3)分发hbase整个文件夹到集群另外的节点上
4.HBase启动停止
1)单点启动
[atguigu@hadoop102 hbase]$ bin/hbase-daemon.sh start master
[atguigu@hadoop102 hbase]$ bin/hbase-daemon.sh start regionserver
注意:如果集群之间的节点时间不同步,会导致regionserver无法启动,会抛出异常
2)群启:bin/start-hbase.sh
3)停止:bin/stop-hbase.sh
5.HBase高可用
要确保在关闭HBase集群的情况下进行配置。
1)在conf目录下创建backup-masters
文件
2)在backup-masters文件中添加要作为备用master的主机名
3)分发这个文件
结果:在配置前,启动HBase时只会启动一个Master,并无备用的Master;配置后,同样会启动一个Master,但是还会有一个备用的Master,在当前Master挂掉后,备用的Master被启动;当挂掉的Master恢复后,作为备用的Master。
三、HBase Shell操作
所有HBase的Shell操作首先要进入HBase客户端:bin/hbase shell
查看所有帮助命令:help
查看某个命令使用:help '命令'
1.namespace的操作
1)查看所有的namespace:list_namespace
2)创建namespace:create_namespace '名称',{'属性名称'=>'属性值',...}
语句中的属性名称是指namespace的属性名称
3)查看某个namespace:describe_namespace '名称'
4)修改namespace的信息
添加或者修改:alter_namespace '名称',{METHOD=>'set','属性名称'=>'属性值",...}
删除:alter_namespace '名称',{METHOD=>'unset',NAME=>'属性名称'}
5)删除namespace:drop_namespace '名称'
注意:要删除的namespace必须是空的,其下没有表,否则会报错
2.表的操作
1)查看当前数据库里的所有表:list
2)创建表:create '表名','列族'
3)向表中插入数据:put '表名','rowkey','列族:列名','值'
4)查看表结构:describe '表名'
5)扫描表中数据
扫描查看全表数据:scan '表名'
扫描查看范围内数据:scan '表名',{STARTROW=>'rowkey1',STOPROW=>'rowkey2'}
注意:在有范围时,是左闭右开的,即从rowkey1开始,到rowkey2且不包含rowkey2。且HBase中rowkey是按字典序排序的,所以假如为了查找到rowkey=1003的数据,那么可以取比1003大的中最小的ASCII码的值,比如!
,那么就可以写成scan '表名',{STARTROW=>'1001',STOPROW=>'1003!'}
;同理,比一个值小的最大的ASCII码可以取|
6)更新指定字段的数据(直接put即可):put '表名','rowkey','列名:列族','值'
7)查看指定数据
查看指定行:get '表名','rowkey'
查看指定列族-列:get '表名','rowkey','列族:列'
8)统计表数据行数:count '表名'
9)删除数据
删除一整条数据(一个rowkey的所有数据代表是一条数据):deleteall '表名','rowkey'
删除某一rowkey的某一列(不加时间戳的话,默认删除最新版本的数据):delete '表名','rowkey','列族:列',ts
10)清空表数据:truncate '表名'
注意:先要把表置为disable,才能去清空
11)删除表
首先,把表置为disable状态:disable '表名'
删除表:drop '表名'
12)变更表信息
将info列族的数据改成存放3个版本(默认是显示1个版本):alter '表名',{NAME=>'info',VERSION=>3}
四、HBase进阶
1.RegionServer架构
1)StoreFile
StoreFile是实际保存数据的文件,StoreFile以Hfile的形式存储在HDFS上。每个Store会有一个或者多个StoreFile,数据在每个StoreFile中是有序的。
2)MemStore
MemStore是一个写缓存,如果一条一条写,效率会很低,所以先把数据存放到一个写缓存中,排好序后,等到数据达到一定的阈值再将数据写入到HFile中,每次都会生成一个新的HFile
3)WAL
WAL是预写日志。因为MemStore里的数据是存放在内存中的,所以数据安全性不高,如果宕机了,数据可能会丢失,所以采用了预写日志的方式。将数据先写在一个叫做Write-Ahead logfile的文件,然后再写入到MemStore,所以在系统出现故障的时候,数据可以通过这个日志文件重建。
4)BlockCache
读缓存,每次查询出来的数据会缓存在BlockCache中,方便下次查询。当BlockCache满了后,采用LRU策略,将一定的数据替换出来。
2.写流程
整个写流程的过程很简单,很容易理解。
首先,要知道往哪个Region里面写,那么就要知道哪个Region Server负责了这个Region,这属于HBase的元数据,而zookeeper负责了HBase的管理,所以要访问zookeeper,关键的流程就是这样,所以大概有以下几步:
1)Client先访问zookeeper,获取hbase:meta表位于哪个Region Server
2)访问hbase:meta所在的Region Server,获取hbase:meta这个表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server里的哪个Region中。同时将该table的Region信息以及meta表的位置信息缓存到客户端的meta cache,方便下次访问。
3)与目标的Region Server进行通讯。
4)将数据顺序写入(追加)到WAL。
5)将数据写入对应的MemStore,数据会在MemStore进行排序。
6)收到数据后,会向客户端发送ack收到
7)等到MemStore的刷写时机到了后,将数据刷写到HFile中。
3.MemStore Flush
MemStore刷写时机:
1)当某个MemStore的大小达到了hbase.hregion.memstore.flush.size(默认值为128M),其所在的Region里的所有MemStore都会刷写。
当MemStore的大小达到了hbase.hregion.memstore.flush.size(默认值128M)* hbase.hregion.memstore.block.multiplier(默认值4)即512M时,会阻止继续往该MemStore写数据。
2)当Region Server中MemStore的总大小达到了堆内存(java_heapsize)* hbase.regionserver.global.memstore.size(默认值0.4)* hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)时,region会按照其所有memstore的大小顺序(由大到小)依次进行刷写。直到region中所有memstore的总大小减小到上述值以下。
当Region Server中Memstore的总大小达到java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4)时,会组织继续往所有的Memstore写数据。
3)到达自动刷写的时间,也会触发memstore flush。自动刷新的时间间隔由hbase.regionserver.optionalcacheflushinterval(默认1小时)进行配置。
4)当WAL文件的数量超过hbase.regionserver.max.logs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.max.logs以下(该属性名已经废弃,现无需手动设置,最大值为32)。
4.读流程
读流程1:
读流程2:
读流程在前半部分类似于写流程,也很容易理解。
1)Client先访问zookeeper,获取hbase:meta表位于哪个Region Server
2)访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region以及meta表的位置信息缓存到客户端的meta cache,方便下次访问。
3)与目标Region Server进行通讯。
4)分别在MemStore和HFile中查询目标数据,并将查询到的所有数据进行合并。
5)将查询到的新的数据块(Block,HFile数据存储单元,默认为64KB)缓存到Block Cache。
6)将合并后的最终结果返回给客户端。
注意:
说明1:为什么要从MemStore和HFile中查询数据?
因为可能HFile中还没有数据,数据全在MemStore中缓存着,还没有到flush的时间。
说明2:为什么要进行数据的合并?
因为一个rowkey可能能查询到多个版本的数据,要拿到所有版本的数据,然后合并知道才知道要返回什么数据。
5.StoreFile Compaction
因为MemStore每次刷写都会生成一个新的HFile,且同一个字段的不同版本和不同类型可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction。
Compaction分为两种,分别是Minor Compaction和Major Compaction。
Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据(因为合并的是一部分数据,所以只能从知道的这一部分中清理数据,别的HFile中的数据没办法清理)
Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉所有过期和删除的数据。
6.Region Split
默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server。
Region拆分策略:
1)策略一
当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize(默认为10G),该Region就会进行拆分(0.94版本之前)。
缺点:刚开始只有一个Region,切分时间太晚了。
2)策略二
当一个Region中的某个Store下所有StoreFile总大小超过Min(initialSize*R^3 ,hbase.hregion.max.filesize"),该Region就会进行拆分。其中initialSize默认值为2*hbase.hregion.memstore.flush.size(默认为128M),R为当前Region Server中属于该table的Region个数(0.94版本之后)
举例如下:
第一次split:Min(21281^3,10G)=256MB
第二次split:Min(21282^3,10G)=2048MB
同理,第三次split:6912MB
第四次split:16384MB>10G,因此取较小的10GB
后面每次split的size都是10GB了
缺点:流程比较麻烦,需要分情况讨论,还要做比较
3)策略三
HBase2.0引入了新的split策略:如果当前Region Server上该表只有一个Region,按照2* hbase.hregion.memstore.flush.size拆分,否则按照hbase.hregion.max.filesize拆分。
优点:简便,第一次拆分时间也不是很晚。
五、HBase API
1.依赖
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.0.5</version>
</dependency>
2.DDL操作
public class HBase_DDL {
static Connection connection;
static {
//创建配置文件
Configuration conf= HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");
try {
//获取与HBase的连接
connection = ConnectionFactory.createConnection(conf);
} catch (IOException e) {
e.printStackTrace();
}
}
public static boolean existsTable(String nameSpace,String tableName) throws IOException {
//判断表是否存在属于DDL,是admin的操作
//获取admin对象
Admin admin = connection.getAdmin();
boolean exists = admin.tableExists(TableName.valueOf(nameSpace, tableName));
//关闭admin连接
admin.close();
return exists;
}
public static void createTable(String nameSpace,String tableName,String ... cfs) throws IOException {
//创建表,属于DDL操作,也是admin
Admin admin = connection.getAdmin();
if(cfs==null|| cfs.length<1){
System.err.println("至少指定一个列族");
return ;
}
if(existsTable(nameSpace, tableName)){
System.out.printf("表已经存在");
return ;
}
//创建表描述器
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(nameSpace, tableName));
//循环添加列族信息
for(String cf: cfs){
//创建列族描述器
ColumnFamilyDescriptor columnFamilyDescriptor = ColumnFamilyDescriptorBuilder.newBuilder(cf.getBytes())
.build();
//表的描述器中添加列族描述器
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
}
//执行建表
admin.createTable(tableDescriptorBuilder.build());
//关闭admin资源
admin.close();
}
public static void dropTable(String nameSpace,String tableName) throws IOException {
//删除表也是DDL操作,也属于admin
Admin admin = connection.getAdmin();
if(!existsTable(nameSpace, tableName)){
System.out.printf("表不存在,无法删除");
return ;
}
//删除表要先将表设置为不可用状态
admin.disableTable(TableName.valueOf(nameSpace, tableName));
admin.deleteTable(TableName.valueOf(nameSpace, tableName));
//关闭资源
admin.close();
}
public static void createNameSpace(String nameSpace) throws IOException {
//创建命名空间也是admin的事情
Admin admin = connection.getAdmin();
//创建命名空间描述器
NamespaceDescriptor namespaceDescriptor = NamespaceDescriptor.create(nameSpace).build();
//创建命名空间
try {
admin.createNamespace(namespaceDescriptor);
}catch (NamespaceExistException e){
System.out.println("命名空间已经存在!");
}finally {
//关闭资源
admin.close();
}
}
}
3.DML操作
public class HBase_DML {
static Connection connection;
static {
//创建配置文件
Configuration conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");
try {
//获取连接
connection = ConnectionFactory.createConnection(conf);
} catch (IOException e) {
e.printStackTrace();
}
}
/*
插入数据Put
*/
public static void putData(String nameSpace,String tableName,String rowkey,String cf,String cl,String value) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
//创建要插入的数据的信息,对应的rowkey等等
Put put = new Put(Bytes.toBytes(rowkey));
put.addColumn(Bytes.toBytes(cf),Bytes.toBytes(cl),Bytes.toBytes(value));
//执行插入命令
table.put(put);
//释放资源
table.close();
}
/*
删除数据
*/
public static void deleteDate(String nameSpace,String tableName,String rowkey,String cf,String cl) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
//配置要删除的rowkey
//只配置这一个,会删除对应rowkey的所有数据,一整条数据
Delete delete = new Delete(Bytes.toBytes(rowkey));
//删除某个rowkey的指定列族的数据
delete.addFamily(Bytes.toBytes(cf));
//配置删除某个rowkey的某个列族的某个列的数据,可以加时间,不加默认删除最新版本的数据
delete.addColumn(Bytes.toBytes(cf),Bytes.toBytes(cl));
//删除某个rowkey的某个列族某个列的所有数据
delete.addColumns(Bytes.toBytes(cf),Bytes.toBytes(cl));
//执行删除命令
table.delete(delete);
//释放资源
table.close();
}
/*
数据查询
*/
public static void getData(String nameSpace,String tableName,String rowkey,String cf,String cl) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
//设置要查找的rowkey
//如果只设置这一个,那么会会返回该rowkey的所有数据
Get get = new Get(Bytes.toBytes(rowkey));
//可以设置要查询的列族
get.addFamily(Bytes.toBytes(cf));
//可以设置要查询的列
get.addColumn(Bytes.toBytes(cf),Bytes.toBytes(cl));
//执行查询操作
Result result = table.get(get);
for (Cell cell : result.rawCells()) {
//这里每一个cell就代表文件中的一行,精确到rowkey-列族-列对应的那个value
System.out.println("ROW: "+Bytes.toString(CellUtil.cloneRow(cell))
+"CF: "+Bytes.toString(CellUtil.cloneFamily(cell))
+"CL: "+Bytes.toString(CellUtil.cloneQualifier(cell))
+"VALUE: "+Bytes.toString(CellUtil.cloneValue(cell)));
}
//释放资源
table.close();
}
/*
扫描数据
*/
public static void scanData(String nameSpace,String tableName,String rowkey,String cf,String cl) throws IOException {
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
//如果只配置这一个,代表扫描全表数据
Scan scan = new Scan();
//可以根据范围查询,前闭后开
scan.withStartRow(Bytes.toBytes("1001"))
.withStopRow(Bytes.toBytes("1003"));
//执行扫描命令,返回的是一个可迭代的结果
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
//每一个result是一个rowkey对应的所有数据,逻辑表里的一行,物理表中的多行
for (Cell cell : result.rawCells()) {
//这里每一个cell就代表文件中的一行,精确到rowkey-列族-列对应的那个value
System.out.println("ROW: "+Bytes.toString(CellUtil.cloneRow(cell))
+"CF: "+Bytes.toString(CellUtil.cloneFamily(cell))
+"CL: "+Bytes.toString(CellUtil.cloneQualifier(cell))
+"VALUE: "+Bytes.toString(CellUtil.cloneValue(cell)));
}
//一个result处理完了,就代表一个rowkey对应的所有数据处理完了
System.out.println("--------------------------------");
}
//释放资源
table.close();
}
}
六、HBase 优化
1.预分区
每一个Region都维护着startRow和endRowKey,如果加入的数据符合某个region维护的rowkey范围,则该数据交给这个region维护。所以依照这个原则,我们可以将region的分区提前规划好,以提高HBase的性能。
1)手动设置预分区
create 'staff1','info',SPLITS => ['1000','2000','3000','4000']
在建表时,设置了五个分区:负无穷—1000、1000—2000、2000—3000、3000—4000、4000—正无穷
2)生成16进制序列预分区
create 'staff2','info',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
3)按照文件中设置的规则预分区
在HBase根目录下创建文件splits.txt,在文件中写入如下内容:
aaaa
bbbb
cccc
dddd
执行:create 'staff3','info',SPLITS_FILE => 'splits.txt'
,同样也会生成五个分区
4)使用JavaAPI创建预分区
byte[][] splitKeys = new byte[4][];
//['1000','2000','3000','4000']
splitKeys[0]= Bytes.toBytes("1000");
splitKeys[1]= Bytes.toBytes("2000");
splitKeys[2]= Bytes.toBytes("3000");
splitKeys[3]= Bytes.toBytes("4000");
admin.createTable(tableDescriptorBuilder.build(),splitKeys);
七、整合Phoenix
1.Phoenix简介
Phoenix是HBase的开源SQL皮肤,可以使用标准JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据。
2.Phoenix安装
1)解压安装包:tar -zxvf apache-phoenix-5.0.0-HBase-2.0-bin.tar.gz -C /opt/module/
2)复制server包并拷贝到各个节点的hbase/lib:
[atguigu@hadoop102 module]$ cd /opt/module/phoenix/
[atguigu@hadoop102 phoenix]$ cp /opt/module/phoenix/phoenix-5.0.0-HBase-2.0-server.jar /opt/module/hbase/lib/
[atguigu@hadoop102 phoenix]$ xsync /opt/module/hbase/lib/phoenix-5.0.0-HBase-2.0-server.jar
3)配置环境变量:
#phoenix
export PHOENIX_HOME=/opt/module/phoenix
export PHOENIX_CLASSPATH=$PHOENIX_HOME
export PATH=$PATH:$PHOENIX_HOME/bin
5)重启HBase
[atguigu@hadoop102 ~]$ stop-hbase.sh
[atguigu@hadoop102 ~]$ start-hbase.sh
6)连接Phoneix:/opt/module/phoenix/bin/sqlline.py hadoop102,hadoop103,hadoop104:2181
3.Phoenix Shell操作
3.1 schema的操作
Phoenix中schema就相当于HBase里的namespace,但是默认情况下在phoenix中不能直接创建schema。需要在HBase的conf目录下的hbase-site.xml和phoenix中bin目录下的hbase-site.xml中添加以下内容:
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
重新启动HBase,连接phoenix客户端。
创建schema:create schema bigdata;
注意:在phoenix中,schema名、表名、字段名等会自动转换为大写,若要小写,使用双引号,如"student"
3.2 表的操作
1)显示所有表:!tables 或 !table
2)创建表(必须要指定主键):
指定单个字段为主键:
CREATE TABLE IF NOT EXISTS student(
id VARCHAR primary key,
name VARCHAR,
addr VARCHAR);
指定多个字段为联合主键:
CREATE TABLE IF NOT EXISTS us_population(
State CHAR(2) NOT NULL,
City VARCHAR NOT NULL,
Population BIGINT,
CONSTRAINT my_pk PRIMARY KEY(state,city));
3)插入(修改)数据:upsert into student values('1001','zhangsan','beijing');
4)查询记录:select * from student where id='1001'
5)删除记录:delete from student where id='1001'
6)删除表:drop table student;
7)退出命令行:!quit
3.3 表的映射
1)表的关系
在Phoenix中创建的表,在HBase也会同步的创建。
但是默认情况下,在HBase中创建的表,通过Phoenix是查不到的,如果要在Phoenix中操作在HBase中创建的表,则需要在Phoenix中进行表的映射,有两种映射方式:视图映射和表映射。
举例如下:
在HBase中创建如下的表:
Rowkey | info1 | info2 |
---|---|---|
id | name | address |
create 'test','info1','info2'
2)视图映射
Phoenix中创建的视图是只读的,只能用来做查询,无法通过视图对源数据进行修改等操作。
创建视图如下:
create view "test"(id VARCHAR primary key,"info1"."name" VARCHAR,"info2"."address" VARCHAR);
删除视图:
drop view "test";
3)表映射
创建的表映射可以进行修改、删除,会影响HBase里的表。
创建一个表映射(跟视图类似,把view换为table即可):
create table "test"(id VARCHAR parimary key,"info1"."name" VARCHAR,"info2"."address" VARCHAR)
column_encoded_bytes=NONE;
4)表映射中数值类型的问题
Hbase中存储数值类型的值(如int,long等)会按照正常数字的补码进行存储. 而phoenix对数字的存储做了特殊的处理. phoenix 为了解决遇到正负数同时存在时,导致负数排到了正数的后面(负数高位为1,正数高位为0,字典序0 < 1)的问题。 phoenix在存储数字时会对高位进行转换.原来为1,转换为0, 原来为0,转换为1.
因此,如果hbase表中的数据的写是由phoenix写入的,不会出现问题,因为对数字的编解码都是phoenix来负责。如果hbase表中的数据不是由phoenix写入的,数字的编码由hbase负责. 而phoenix读数据时要对数字进行解码。 因为编解码方式不一致。导致数字出错.
举例如下:
在hbase中创建表,并插入数值类型的数据
hbase(main):001:0> create 'person','info'
hbase(main):001:0> put 'person','1001', 'info:salary',Bytes.toBytes(123456)
注意: 如果要插入数字类型,需要通过Bytes.toBytes(123456)来实现。
在phoenix中创建映射表并查询数据
create table "person"(id varchar primary key,"info"."salary" integer )
column_encoded_bytes=0;
select * from "person"
会发现数字显示有问题
解决办法:
在phoenix中创建表时使用无符号的数值类型. unsigned_long
create table "person"(id varchar primary key,"info"."salary" unsigned_long )
column_encoded_bytes=0;
4.Phoenix JDBC操作
1)Thin Client 瘦客户端
启动query server:queryserver.py start
需要导入以下依赖:
<dependencies>
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-queryserver-client</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
</dependencies>
使用方法:
public class PhoenixThinClientTest {
public static void main(String[] args) throws SQLException {
//获取Thin客户端连接的url
String url= ThinClientUtil.getConnectionUrl("hadoop102",8765);
//获取连接
Connection connection = DriverManager.getConnection(url);
PreparedStatement preparedStatement = connection.prepareStatement("select * from student");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1)+"\t"+resultSet.getString(2));
}
//关闭连接
connection.close();
}
}
2)Thick Client 胖客户端
需要加入以下依赖:
<dependencies>
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>5.0.0-HBase-2.0</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b06</version>
</dependency>
</dependencies>
使用方法:
public class PhoenixThickClientTest {
public static void main(String[] args) throws SQLException {
//胖客户端的url
String url="jdbc:phoenix:hadoop102,hadoop103,hadoop104:2181";
Properties properties=new Properties();
//设置开启映射
properties.put("phoenix.schema.isNamespaceMappingEnabled","true");
Connection connection = DriverManager.getConnection(url);
PreparedStatement preparedStatement = connection.prepareStatement("select * from test");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1)+":"+resultSet.getString(2));
}
}
}
要注意的是:这里需要开启映射
5.Phoenix二级索引
使用Phoenix二级索引首先要修改配置文件,添加如下配置到HBase的HRegionserver节点的hbase-site.xml:
<!-- phoenix regionserver 配置参数-->
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
<property>
<name>hbase.region.server.rpc.scheduler.factory.class</name>
<value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>
<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
<property>
<name>hbase.rpc.controllerfactory.class</name>
<value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>
<description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>
</property>
5.1 为什么需要二级索引
对于HBase而言,如果想精确地定位到某行记录,唯一的办法是通过rowkey来查询。如果不通过rowkey来查找数据,就必须逐行地比较每一列的值,即全表扫瞄。
对于较大的表,全表扫描的代价是不可接受的。但是,很多情况下,需要从多个角度查询数据。例如,在定位某个人的时候,可以通过姓名、身份证号、学籍号等不同的角度来查询,要想把这么多角度的数据都放到rowkey中几乎不可能(业务的灵活性不允许,对rowkey长度的要求也不允许)。所以需要secondary index(二级索引)来完成这件事。secondary index的原理很简单,但是如果自己维护的话则会麻烦一些。现在,Phoenix已经提供了对HBase secondary index的支持。
5.2 全局二级索引
创建全局二级索引,会在HBase中建立一张新表。也就是说索引数据和数据表是存放在不同的表中的,因此全局索引适合读多写少的业务场景。
写数据的时候会消耗大量的开销,因为索引表也要更新,而索引表是分布在不同的数据节点上的,跨节点的数据传输带来了较大的性能消耗。
在读数据的时候Phoenix会选择索引表来降低查询消耗的时间。
1)创建单个字段的全局索引
CREATE INDEX myindex ON test(name);
结果如下图:
在对test表中name字段创建一个索引后,生成了一个索引表myindex,它把test表的原来的rowkey+name合在一块作为新表里的rowkey,value此时为空。
使用案例:
上图使用name创建了一个索引。
第一条查询语句要查询的字段是所有的字段,部分字段索引表中没有,索引还是需要进行全表扫描。
第二条查询语句查询的是name,在索引表中存在,但是又因为不是唯一的,索引是范围索引。
2)创建携带其他字段的全局索引
CREATE INDEX myindex ON test(name) INCLUDE(addr);
如下图:
上面使用name创建了一个索引,同时把addr这个字段的数据包含了进去,这样用where过滤name时,查询addr也会走索引。
5.3 本地二级索引
本地索引适用于写操作频繁的场景。
索引数据和数据表的数据是存放在同一张表中的,而且是同一个Region,避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。
如上图:
将name列建立了一个本地索引,这样会在test表中增加新增的索引数据。
八、与Hive的集成
1.HBase与Hive的对比
1)Hive
Hive的本质其实就相当于将HDFS中已经存储的文件在Mysql中做了一个双射关系,以方便使用HQL去管理查询。
Hive适用于离线的数据分析和清洗,延迟较高。
Hive存储的数据依旧在DataNode上,编写的HQL语句终将是转换为MapReduce代码执行。
2)HBase
是一种面向列族存储的非关系型数据库。
适用于单表非关系型数据的存储,不适合做关联查询,类似JOIN等操作。
数据持久化存储的体现形式是HFile,存放于DataNode中,被ResionServer以region的形式进行管理。
面对大量的企业数据,HBase可以直线单表大量数据的存储,同时提供了高效的数据访问速度。
2.HBase与Hive集成使用
使用前要现在Hive的conf里的hive-site.xml中添加zookeeper的属性,如下:
<property>
<name>hive.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
<property>
<name>hive.zookeeper.client.port</name>
<value>2181</value>
</property>
3.Hive中建立HBase表
场景:HBase不存在某张表,在Hive中建立一张表,同时HBase也会生成对应的一张表。
前提条件:HBase中不存在表hbase_emp_table
在Hive进行建表:
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.HBaseStorageHandler'
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中都生成了对应的表。
注意:
1)保存格式要设置:STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
2)对应列的映射也要设置:WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,info:ename,info:job,info:mgr,info:hiredate,info:sal,info:comm,info:deptno")
3)对应表的映射:TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");
4.向表中装载数据
在建表之后,不能直接使用load命令向Hive中对应的HBase那张表装载数据,因为HBase有自己的存储格式,无法识别直接上传的文件。
装载数据方式:
1)在Hive中创建临时中间表,用于load文件中的数据
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';
2)向Hive中间表load数据
hive> load data local inpath '/home/admin/softwares/data/emp.txt' into table emp;
3)通过insert命令将中间表的数据导入到Hive关联的HBase的那张表中
hive> insert into table hive_hbase_emp_table select * from emp;
5.Hive映射HBase的表
前提条件:HBase已存在某张表,Hive中进行对这张表的映射。
注意:如果HBase已存在某张表,Hive中进行映射只能创建外部表。
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.HBaseStorageHandler'
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");
2)关联之后就可以使用Hive函数进行一些分析操作了
hive (default)> select * from relevance_hbase_emp;