(一) 简介
Apache HBase – Apache HBase™ Home
Hbase是一个构建在Hdfs基础之上的非关系型(NoSql,Not Only Sql)数据库,也是一个分布式的、面向列的开源数据库
Nosql数据库和关系型数据库的明显区别:Nosql数据库往往不会提供sql语句接口(你不能写sql语句操作它,往往是以api/指令的形式)。
1、Hadoop
- 从 1970 年开始,大多数的公司数据存储和维护使用的是关系型数据库
- 大数据技术出现后,很多拥有海量数据的公司开始选择像Hadoop的方式来存储海量数据
- Hadoop使用分布式文件系统HDFS来存储海量数据,并使用 MapReduce 来处理。Hadoop擅长于存储各种格式的庞大的数据,任意的格式甚至非结构化的处理
2、Hadoop的局限
- Hadoop主要是实现批量数据的处理,并且通过顺序方式访问数据
- 要查找数据必须搜索整个数据集, 如果要进行随机读取数据,效率较低
3、HBase 与 NoSQL
- NoSQL是一个通用术语,泛指不是使用SQL作为主要语言的非关系型数据库
- HBase是BigTable的开源java版本。是建立在HDFS之上,提供高可靠性、高性能、列族存储、可伸缩、实时读写NoSQL的数据库系统
- HBase仅能通过主键(row key)和主键的range来检索数据
- 主要用来存储结构化和半结构化的松散数据
- Hbase查询数据功能很简单,不支持join等复杂操作,不支持复杂的事务(只支持行级的事务),从技术上来说,HBase更像是一个「数据存储」而不是「数据库」,因为HBase缺少RDBMS中的许多特性,例如带类型的列。
- Hbase中支持的数据类型:byte[]
- 与Hadoop一样,Hbase目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加存储和处理能力,例如,把集群从10个节点扩展到20个节点,存储能力和处理能力都会加倍
- HBase中的表一般有这样的特点
- 大:一个表可以有上十亿行,上百万列
- 面向列:面向列(族)的存储和权限控制,列(族)独立检索
- 稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏
4.发展历程
年份 | 重大事件 |
2006年11月 | Google发布BigTable论文. |
2007年10月 | 发布第一个可用的HBase版本,基于Hadoop 0.15.0 |
2008年1月 | HBase称为Hadoop的一个子项目 |
2010年5月 | HBase称为Apache的顶级项目 |
2010年5月 | HBase称为Apache的顶级项目 |
5、 HBase特点
- 强一致性读/写
HBASE不是“最终一致的”数据存储,只支持单行事务
- 自动分块(region)
HBase表通过Region分布在集群上,随着数据的增长,区域被自动拆分和重新分布
- 自动RegionServer故障转移
- Hadoop/HDFS集成
HBase支持HDFS开箱即用作为其分布式文件系统
- MapReduce
HBase通过MapReduce支持大规模并行处理,将HBase用作源和接收器
- Java Client API
HBase支持易于使用的 Java API 进行编程访问
- Thrift/REST API
- 块缓存和布隆过滤器
HBase支持块Cache和Bloom过滤器进行大容量查询优化
- 运行管理
HBase为业务洞察和JMX度量提供内置网页。
6. RDBMS与HBase的对比
(1)关系型数据库
a、结构
- 数据库以表的形式存在
- 支持FAT、NTFS、EXT、文件系统
- 使用主键(PK)
- 通过外部中间件可以支持分库分表,但底层还是单机引擎
- 使用行、列、单元格
b、功能
- 支持向上扩展(买更好的服务器)
- 使用SQL查询
- 面向行,即每一行都是一个连续单元
- 数据总量依赖于服务器配置
- 具有ACID支持
- 适合结构化数据
- 传统关系型数据库一般都是中心化的
- 支持事务
- 支持Join
(2)HBase
a、结构
- 以表形式存在
- 支持HDFS文件系统
- 使用行键(row key)
- 原生支持分布式存储、计算引擎
- 使用行、列、列蔟和单元格
b、功能
- 支持向外扩展
- 使用API和MapReduce、Spark、Flink来访问HBase表数据
- 面向列蔟,即每一个列蔟都是一个连续的单元
- 数据总量不依赖具体某台机器,而取决于机器数量
- HBase不支持ACID(Atomicity、Consistency、Isolation、Durability),即原子性、一致性、隔离性、持久性
- 适合结构化数据和非结构化数据
- 一般都是分布式的
- HBase不支持事务,支持的是单行数据的事务操作
- 不支持Join
7. HDFS对比HBase
(1)HDFS
HDFS是一个非常适合存储大型文件的分布式文件系统
HDFS它不是一个通用的文件系统,也无法在文件中快速查询某个数据
(2)HBase
HBase构建在HDFS之上,并为大型表提供快速记录查找(和更新)
HBase内部将大量数据放在HDFS中名为「StoreFiles」的索引中,以便进行高速查找
Hbase比较适合做快速查询等需求,而不适合做大规模的OLAP应用
8.HIVE对比HBASE
(1)Hive
- 数据仓库工具
Hive的本质其实就相当于将HDFS中已经存储的文件在Mysql中做了一个双射关系,以方便使用HQL去管理查询
- 用于数据分析、清洗
Hive适用于离线的数据分析和清洗,延迟较高
- 基于HDFS、MapReduce
Hive存储的数据依旧在DataNode上,编写的HQL语句终将是转换为MapReduce代码执行
(2)HBase
- NoSQL数据库
是一种面向列存储的非关系型数据库。
- 用于存储结构化和非结构化的数据
适用于单表非关系型数据的存储,不适合做关联查询,类似JOIN等操作。
- 基于HDFS
数据持久化存储的体现形式是Hfile,存放于DataNode中,被ResionServer以region的形式进行管理
- 延迟较低,接入在线业务使用
面对大量的企业数据,HBase可以实现单表大量数据的存储,同时提供了高效的数据访问速度
(3)总结Hive与HBase
- Hive和Hbase是两种基于Hadoop的不同技术
- Hive是一种类SQL的引擎,并且运行MapReduce任务
- Hbase是一种在Hadoop之上的NoSQL 的Key/value数据库
- 这两种工具是可以同时使用的。就像用Google来搜索,用FaceBook进行社交一样,Hive可以用来进行统计查询,HBase可以用来进行实时查询,数据也可以从Hive写到HBase,或者从HBase写回Hive
9、HBase应用场景
(1)对象存储
不少的头条类、新闻类的的新闻、网页、图片存储在HBase之中,一些病毒公司的病毒库也是存储在HBase之中
(2)时序数据
HBase之上有OpenTSDB模块,可以满足时序类场景的需求
(3)推荐画像
用户画像,是一个比较大的稀疏矩阵,蚂蚁金服的风控就是构建在HBase之上
(4)时空数据
主要是轨迹、气象网格之类,滴滴打车的轨迹数据主要存在HBase之中,另外在技术所有大一点的数据量的车联网企业,数据都是存在HBase之中
(5)CubeDB OLAP
Kylin一个cube分析工具,底层的数据就是存储在HBase之中,不少客户自己基于离线计算构建cube存储在hbase之中,满足在线报表查询的需求
(6)消息/订单
在电信领域、银行领域,不少的订单查询底层的存储,另外不少通信、消息同步的应用构建在HBase之上
(7)Feeds流
典型的应用就是xx朋友圈类似的应用,用户可以随时发布新内容,评论、点赞。
(8)NewSQL
之上有Phoenix的插件,可以满足二级索引、SQL的需求,对接传统数据需要SQL非事务的需求
(9)其他
- 存储爬虫数据
- 海量数据备份
- 短网址
- …
(二) 集群搭建
1、安装
(1)上传解压HBase安装包
https://repo.huaweicloud.com/apache/hbase/2.2.4/
Apache HBase ™ Reference Guide
tar -zxvf hbase-2.2.4-bin.tar.gz -C /home/offcn/apps/
(2)修改HBase配置文件
a、hbase-env.sh
cd /home/offcn/apps/hbase-2.2.4/conf
vim hbase-env.sh
# 第28行
export JAVA_HOME=/home/offcn/apps/jdk1.8.0_144
export HBASE_LOG_DIR=$HOME/logs/hbase-2.2.4
export HBASE_MANAGES_ZK=false
b、hbase-site.xml
vim hbase-site.xml
------------------------------
<configuration>
<!-- HBase数据在HDFS中的存放的路径 -->
<property>
<name>hbase.rootdir</name>
<value>hdfs://bd-offcn-01:8020/hbase</value>
</property>
<!-- Hbase的运行模式。false是单机模式,true是分布式模式。若为false,Hbase和Zookeeper会运行在同一个JVM里面 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- ZooKeeper的地址 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>bd-offcn-01,bd-offcn-02,bd-offcn-03</value>
</property>
<!-- ZooKeeper数据的存储位置 -->
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/offcn/data/hbase-2.2.4</value>
</property>
<!-- 在分布式情况下, 设置为false -->
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>
</configuration>
(3)配置环境变量
# 配置Hbase环境变量
sudo vim /etc/profile
#hbase-2.2.4
export HBASE_HOME=/home/offcn/apps/hbase-2.2.4
export HBASE_LOG_DIR=$HOME/logs/hbase-2.2.4
export PATH=$PATH:$HBASE_HOME/bin
#加载环境变量
source /etc/profile
(4)复制jar包到lib
cp /home/offcn/apps/hbase-2.2.4/lib/client-facing-thirdparty/htrace-core4-4.2.0-incubating.jar /home/offcn/apps/hbase-2.2.4/lib/
(5)修改regionservers文件
vim regionservers
清空原来的,添加如下
bd-offcn-01
bd-offcn-02
bd-offcn-03
(6)分发安装包与配置文件
cd /home/offcn/apps
scp -r hbase-2.2.4/ bd-offcn-02:$PWD
scp -r hbase-2.2.4/ bd-offcn-03:$PWD
sudo scp /etc/profile bd-offcn-02:/etc
sudo scp /etc/profile bd-offcn-03:/etc
在bd-offcn-02和bd-offcn-03加载环境变量
source /etc/profile
(7)启动Hbase
cd /home/offcn/bin
# 启动ZK
./zk.sh start
# 启动hadoop
start-dfs.sh
# 启动hbase
start-hbase.sh
--单独启动关闭进程
bin/hbase-daemon.sh start master
bin/hbase-daemon.sh start regionserver
(8)验证Hbase是否启动成功
# 启动hbase shell客户端
hbase shell
# 输入status
2、WebUI
http://bd-offcn-01:16010/
3、安装目录说明
目录名 | 说明 |
bin | 所有hbase相关的命令都在该目录存放 |
conf | 所有的hbase配置文件 |
hbase-webapps | hbase的web ui程序位置 |
lib | hbase依赖的java库 |
lib | hbase依赖的java库 |
(三) HBase数据模型
1、简介
在HBASE中,数据存储在具有行和列的表中。这是看起来关系数据库(RDBMS)一样,但将HBASE表看成是多个维度的Map结构更容易理解。
ROWKEY | C1列蔟 | C2列蔟 | ||||
rowkey | 列1 | 列2 | 列3 | 列4 | 列4 | 列6 |
rowkey | 0001 |
C1 | 列1 => 值1 列2 => 值2 列3 => 值3 |
C2 | 列4 => 值4 列5 => 值5 列6 => 值6 |
2、术语
(1)表(Table)
- HBase中数据都是以表形式来组织的
- HBase中的表由多个行组成
在HBase WebUI(http://bd-offcn-01:16010中可以查看到目前HBase中的表)
(2)行(row)
- HBASE中的行由一个rowkey(行键)和一个或多个列组成,列的值与rowkey、列相关联
- 行在存储时按行键按字典顺序排序
- 行键的设计非常重要,尽量让相关的行存储在一起,让其连续
后续,我们会讲解rowkey的设计策略。
(3)列(Column)
- HBASE中的列由列蔟(Column Family)和列限定符(Column Qualifier)组成
- 表示如下——列蔟名:列限定符名。例如:C1:USER_ID、C1:SEX
(4)列蔟(Column Family)
- 出于性能原因,列蔟将一组列及其值组织在一起
- 每个列蔟都有一组存储属性
- 表中的每一行都有相同的列蔟,但在列蔟中不存储任何内容
- 所有的列蔟的数据全部都存储在一块(文件系统HDFS)
(5)列标识符(Column Qualifier)
列标识符,可以理解为列名。
- 列蔟中包含一个个的列限定符,这样可以为存储的数据提供索引
- 列蔟在创建表的时候是固定的,但列限定符是不作限制的
- 不同的行可能会存在不同的列标识符
(6)单元格(Cell)
- 单元格是行、列系列和列限定符的组合
- 包含一个值和一个时间戳(表示该值的版本)
- 单元格中的内容是以二进制存储的
ROW | COLUMN+CELL |
1250995 | column=C1:ADDRESS, timestamp=1588591604729, value=\xC9\xBD\xCE\xF7\xCA |
1250995 | column=C1:LATEST_DATE, timestamp=1588591604729, value=2019-03-28 |
1250995 | column=C1:NAME, timestamp=1588591604729, value=\xB7\xBD\xBA\xC6\xD0\xF9 |
1250995 | column=C1:NUM_CURRENT, timestamp=1588591604729, value=398.5 |
1250995 | column=C1:NUM_PREVIOUS, timestamp=1588591604729, value=379.5 |
1250995 | column=C1:NUM_USEAGE, timestamp=1588591604729, value=19 |
1250995 | column=C1:PAY_DATE, timestamp=1588591604729, value=2019-02-26 |
1250995 | column=C1:RECORD_DATE, timestamp=1588591604729, value=2019-02-11 |
1250995 | column=C1:SEX, timestamp=1588591604729, value=\xC5\xAE |
1250995 | column=C1:TOTAL_MONEY, timestamp=1588591604729, value=114 |
1250995 | column=C1:TOTAL_MONEY, timestamp=1588591604729, value=114 |
3、概念模型
Row Key | ColumnFamily contents | ColumnFamily anchor | ColumnFamily people |
"com.offcn" | anchor:cnnsi.com = "CNN" | ||
"com.offcn" | anchor:my.look.ca = "CNN.com" | ||
"com.offcn" | contents:html = "<html>…" | ||
"com.offcn" | contents:html = "<html>…" | ||
"com.offcn" | contents:html = "<html>…" | ||
"com.example " | contents:html = "<html>…" | people:author = "John Doe" |
- 上述表格有两行、三个列蔟(contens、anchor、people)
- “com.offcn”这一行anchor列蔟两个列(anchor:cssnsi.com、anchor:my.look.ca)、contents列蔟有个1个列(html)
- HBase中如果某一行的列被更新的,那么最新的数据会排在最前面,换句话说同一个rowkey的数据是按照倒序排序的
(四)常用shell操作
我们可以以shell的方式来维护和管理HBase。例如:执行建表语句、执行增删改查操作等等,注意设置:xshell backspace
1、需求
有以下订单数据,我们想要将这样的一些数据保存到HBase中。
订单ID | 订单状态 | 支付金额 | 支付方式ID | 用户ID | 操作时间 | 商品分类 |
001 | 已付款 | 200.5 | 1 | 001 | 2020-5-2 18:08:53 | 手机; |
接下来,我们将使用HBase shell来进行以下操作:
- 创建表
- 添加数据
- 更新数据
- 删除数据
- 查询数据
2、创建表
在HBase中,所有的数据也都是保存在表中的。要将订单数据保存到HBase中,首先需要将表创建出来。
(1)启动HBase Shell
HBase的shell其实JRuby的IRB(交互式的Ruby),但在其中添加了一些HBase的命令。
启动HBase shell:
hbase shell
(2)创建表
语法:
create '表名','列蔟名'...
创建订单表,表名为ORDER_INFO,该表有一个列蔟为C1
create 'ORDER_INFO','C1'
注意:
- create要写成小写
- 一个表可以包含若干个列蔟
- 命令解析:调用hbase提供的ruby脚本的create方法,传递两个字符串参数
- 通过下面链接可以看到每个命令都是一个ruby脚本
hbase/hbase-shell/src/main/ruby/shell/commands at branch-2.1 · apache/hbase · GitHub
(3)查看表
list
hbase(main):005:0> list
TABLE
ORDER_INFO
1 row(s)
Took 0.0378 seconds
=> ["ORDER_INFO"]
(4)删除表
要删除某个表,必须要先禁用表
a、禁用表
语法:disable "表名"
b、删除表
语法:drop "表名"
c、删除ORDER_INFO表
disable "ORDER_INFO"
drop "ORDER_INFO"
3、添加数据
(1)需求
接下来,我们需要往订单表中添加以下数据。
订单ID | 订单状态 | 支付金额 | 支付方式ID | 用户ID | 操作时间 | 商品分类 |
ID | STATUS | PAY_MONEY | PAYWAY | USER_ID | OPERATION_DATE | CATEGORY |
000001 | 已提交 | 4070 | 1 | 4944191 | 2020-05-25 12:09:16 | 手机; |
(2)PUT操作
HBase中的put命令,可以用来将数据保存到表中。但put一次只能保存一个列的值。以下是put的语法结构:
put '表名','ROWKEY','列蔟名:列名','值'
要添加以上的数据,需要使用7次put操作。如下:
put 'ORDER_INFO','000001','C1:ID','000001'
put 'ORDER_INFO','000001','C1:STATUS','已提交'
put 'ORDER_INFO','000001','C1:PAY_MONEY',4070
put 'ORDER_INFO','000001','C1:PAYWAY',1
put 'ORDER_INFO','000001','C1:USER_ID',4944191
put 'ORDER_INFO','000001','C1:OPERATION_DATE','2020-04-25 12:09:16'
put 'ORDER_INFO','000001','C1:CATEGORY','手机;'
4、查看添加的数据
(1)需求
要求将rowkey为:000001对应的数据查询出来。
(2)get命令
在HBase中,可以使用get命令来获取单独的一行数据。语法:
get '表名','rowkey'
(3)查询指定订单ID的数据
get 'ORDER_INFO','000001'
COLUMN | CELL |
C1:CATEGORY | timestamp=1588415690678, value=\xE6\x89\x8B\xE6\x9C\xBA; |
C1:OPERATION_DATE | timestamp=1588415689773, value=2020-04-25 12:09:16 |
C1:PAYWAY | timestamp=1588415689681, value=1 |
C1:PAY_MONEY | timestamp=1588415689643, value=4070 |
C1:STATUS | timestamp=1588415689591, value=\xE5\xB7\xB2\xE6\x8F\x90\xE4\xBA\xA4 |
C1:USER_ID | timestamp=1588415689721, value=4944191 |
(4)显示中文
在HBase shell中,如果在数据中出现了一些中文,默认HBase shell中显示出来的是十六进制编码。要想将这些编码显示为中文,我们需要在get命令后添加一个属性:{FORMATTER => 'toString'}
a.查看订单的数据
get 'ORDER_INFO','000001', {FORMATTER => 'toString'}
注:
- { key => value},这个是Ruby语法,表示定义一个HASH结构
- get是一个HBase Ruby方法,’ORDER_INFO’、’000001’、{FORMATTER => 'toString'}是put方法的三个参数
- FORMATTER要使用大写
- 在Ruby中用{}表示一个字典,类似于hashtable,FORMATTER表示key、’toString’表示值
5、更新操作
(1)需求
将订单ID为000001的状态,更改为「已付款」
(2)使用put来更新数据
同样,在HBase中,也是使用put命令来进行数据的更新,语法与之前的添加数据一模一样。
(3)更新指定的列
put 'ORDER_INFO', '000001', 'C1:STATUS', '已付款'
注意:
- HBase中会自动维护数据的版本,但是默认只存储一个版本,可以更改存储的版本数量
alter 'ORDER_INFO',{NAME=>'C1',VERSIONS=>3}
- 每当执行一次put后,都会重新生成新的时间戳,但是默认只能看到最新版本的数据
put 'ORDER_INFO', '000001', 'C1:STATUS', '已付款2'
put 'ORDER_INFO', '000001', 'C1:STATUS', '已付款3'
put 'ORDER_INFO', '000001', 'C1:STATUS', '已付款4'
get 'ORDER_INFO','000001',{FORMATTER => 'toString'}
查看若指定版本数量的数据
get 'ORDER_INFO','000001',{COLUMN => 'C1:STATUS',VERSIONS=>3,FORMATTER => 'toString'}
get 'ORDER_INFO','000001',{COLUMN => 'C1:STATUS',VERSIONS=>2,FORMATTER => 'toString'}
get 'ORDER_INFO','000001',{COLUMN => 'C1:STATUS',VERSIONS=>4,FORMATTER => 'toString'}
6、删除操作
(1)删除状态列数据
a、需求
将订单ID为000001的状态列删除。
b、delete命令
在HBase中,可以使用delete命令来将一个单元格的数据删除。语法格式如下:
delete '表名', 'rowkey', '列蔟:列'。
注意:此处HBase默认会保存多个时间戳的版本数据,所以这里的delete删除的是最新版本的列数据。
c、删除指定的列
delete 'ORDER_INFO','000001','C1:STATUS'
删除指定时间戳(版本)的数据
delete 'ORDER_INFO','000001','C1:STATUS' ,1622556637324
(2)删除整行数据
a、需求
将订单ID为000001的信息全部删除(删除所有的列)
b、deleteall命令
deleteall命令可以将指定rowkey对应的所有列全部删除。语法:
deleteall '表名','rowkey'
c、删除指定的订单
deleteall 'ORDER_INFO','000001'
(3)清空表
a、需求
将ORDER_INFO的数据全部删除
b、truncate命令
truncate命令用来清空某个表中的所有数据。语法:
truncate "表名"
c、清空ORDER_INFO的所有数据
truncate 'ORDER_INFO'
7、导入测试数据集
(1)需求
在ORDER_INFO.txt 中,有一份这样的HBase数据集,我们需要将这些指令放到HBase中执行,将数据导入到HBase中。
可以看到这些都是一堆的put语句。那么如何才能将这些语句全部执行呢?
(2)执行command文件
a、上传command文件
将该数据集文件上传到指定的目录中
b、执行
使用以下命令执行:
hbase shell /home/offcn/tmp/ORDER_INFO.txt
即可。
执行完之后查看结果
scan 'ORDER_INFO',{FORMATTER => 'toString'}
8、计数操作
(1)需求
查看HBase中的ORDER_INFO表,一共有多少条记录。
(2)count命令
count命令专门用来统计一个表中有多少条数据。语法:
count ‘表名’
注意:这个操作是比较耗时的。在数据量大的这个命令可能会运行很久。
(3)获取订单数据
count 'ORDER_INFO'
9、大量数据的计数统计
当HBase中数据量大时,可以使用HBase中提供的MapReduce程序来进行计数统计。语法如下:
$HBASE_HOME/bin/hbase org.apache.hadoop.hbase.mapreduce.RowCounter '表名'
- 启动YARN集群
启动yarn集群
start-yarn.sh
(2)执行MR JOB
$HBASE_HOME/bin/hbase org.apache.hadoop.hbase.mapreduce.RowCounter 'ORDER_INFO'
通过观察YARN的WEB UI,我们发现HBase启动了一个名字为rowcounter_ORDER_INFO的作业。
10、扫描操作
(1)需求一:查询订单所有数据
a、需求
查看ORDER_INFO表中所有的数据
b、scan命令
在HBase,我们可以使用scan命令来扫描HBase中的表。语法:
scan '表名'
c.扫描ORDER_INFO表
scan 'ORDER_INFO',{FORMATTER => 'toString'}
注意:要避免scan一张大表!
(2)需求二:查询订单数据(只显示3条)
scan 'ORDER_INFO', {LIMIT => 3, FORMATTER => 'toString'}
(3)需求三:查询订单状态、支付方式
a、需求
只查询订单状态以及支付方式,并且只展示3条数据
b、命令
scan 'ORDER_INFO', {LIMIT => 3, COLUMNS => ['C1:STATUS', 'C1:PAYWAY'], FORMATTER => 'toString'}
注意:
- [‘C1:STATUS’, …]在Ruby中[]表示一个数组
(4)需求四:查询指定订单ID的数据并以中文展示
根据ROWKEY来查询对应的数据,ROWKEY为02602f66-adc7-40d4-8485-76b5632b5b53,只查询订单状态、支付方式,并以中文展示。
要查询指定ROWKEY的数据,需要使用ROWPREFIXFILTER,用法为:
scan '表名', {ROWPREFIXFILTER => 'rowkey'}
实现指令:
scan 'ORDER_INFO', {ROWPREFIXFILTER => '02602f66-adc7-40d4-8485-76b5632b5b53', COLUMNS => ['C1:STATUS', 'C1:PAYWAY'], FORMATTER => 'toString'}
11、过滤器
(1)简介
在HBase中,如果要对海量的数据来进行查询,此时基本的操作是比较无力的。此时,需要借助HBase中的高级语法——Filter来进行查询。Filter可以根据列簇、列、版本等条件来对数据进行过滤查询。因为在HBase中,主键、列、版本都是有序存储的,所以借助Filter,可以高效地完成查询。当执行Filter时,HBase会将Filter分发给各个HBase服务器节点来进行查询。
HBase中的过滤器也是基于Java开发的,只不过在Shell中,我们是使用基于JRuby的语法来实现的交互式查询。以下是HBase 2.2的JAVA API文档。
(2)HBase中的过滤器
在HBase的shell中,通过show_filters指令,可以查看到HBase中内置的一些过滤器。
rowkey 过滤器 | RowFilter | 实现行键字符串的比较和过滤 |
PrefixFilter | rowkey前缀过滤器 | |
KeyOnlyFilter | 只对单元格的键进行过滤和显示,不显示值 | |
FirstKeyOnlyFilter | 只扫描显示相同键的第一个单元格,其键值对会显示出来 | |
InclusiveStopFilter | 替代 ENDROW 返回终止条件行 | |
列过滤器 | FamilyFilter | 列簇过滤器 |
QualifierFilter | 列标识过滤器,只显示对应列名的数据 | |
ColumnPrefixFilter | 对列名称的前缀进行过滤 | |
MultipleColumnPrefixFilter | 可以指定多个前缀对列名称过滤 | |
ColumnRangeFilter | 过滤列名称的范围 | |
值过滤器 | ValueFilter | 值过滤器,找到符合值条件的键值对 |
SingleColumnValueFilter | 在指定的列蔟和列中进行比较的值过滤器 | |
SingleColumnValueExcludeFilter | 排除匹配成功的值 | |
其他过滤器 | ColumnPaginationFilter | 对一行的所有列分页,只返回 [offset,offset+limit] 范围内的列 |
PageFilter | 对显示结果按行进行分页显示 | |
TimestampsFilter | 时间戳过滤,支持等值,可以设置多个时间戳 | |
ColumnCountGetFilter | 限制每个逻辑行返回键值对的个数,在 get 方法中使用 | |
DependentColumnFilter | 允许用户指定一个参考列或引用列来过滤其他列的过滤器 |
Java API官方地址:Apache HBase 3.0.0-alpha-2-SNAPSHOT API
(3)过滤器的用法
过滤器一般结合scan命令来使用。打开HBase的JAVA API文档。找到RowFilter的构造器说明,我们来看以下,HBase的过滤器该如何使用。
scan '表名', { Filter => "过滤器(比较运算符, '比较器表达式')” }
a、比较运算符
比较运算符 | 描述 |
= | 等于 |
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
!= | 不等于 |
b、比较器
比较器 | 描述 |
BinaryComparator | 匹配完整字节数组 |
BinaryPrefixComparator | 匹配字节数组前缀 |
BitComparator | 匹配比特位 |
NullComparator | 匹配空值 |
RegexStringComparator | 匹配正则表达式 |
SubstringComparator | 匹配子字符串 |
c、比较器表达式
基本语法:比较器类型:比较器的值
比较器 | 表达式语言缩写 |
BinaryComparator | binary:值 |
BinaryPrefixComparator | binaryprefix:值 |
BitComparator | bit:值 |
NullComparator | null |
RegexStringComparator | regexstring:正则表达式 |
SubstringComparator | substring:值 |
(4)需求一:使用RowFilter查询指定订单ID的数据
a、需求
只查询订单的ID为:02602f66-adc7-40d4-8485-76b5632b5b53、订单状态以及支付方式
分析
- 因为要订单ID就是ORDER_INFO表的rowkey,所以,我们应该使用rowkey过滤器来过滤
- 通过HBase的JAVA API,找到RowFilter构造器
通过上图,可以分析得到,RowFilter过滤器接受两个参数,
- op——比较运算符
- rowComparator——比较器
所以构建该Filter的时候,只需要传入两个参数即可
b、命令
scan 'ORDER_INFO', {FILTER => "RowFilter(=,'binary:02602f66-adc7-40d4-8485-76b5632b5b53')"}
(5)需求二:查询状态为已付款的订单
a、需求
查询状态为「已付款」的订单
分析
- 因为此处要指定列来进行查询,所以,我们不再使用rowkey过滤器,而是要使用列过滤器
- 我们要针对指定列和指定值进行过滤,比较适合使用SingleColumnValueFilter过滤器,查看JAVA API
需要传入四个参数:
- 列簇
- 列标识(列名)
- 比较运算符
- 比较器
注意:
- 列名STATUS的大小写一定要对!此处使用的是大写!
- 列名写错了查不出来数据,但HBase不会报错,因为HBase是无模式的
b、命令
scan 'ORDER_INFO', {FILTER => "SingleColumnValueFilter('C1', 'STATUS', =, 'binary:已付款')", FORMATTER => 'toString'}
(6)需求三:查询支付方式为1,且金额大于3000的订单
分析
- 此处需要使用多个过滤器共同来实现查询,多个过滤器,可以使用AND或者OR来组合多个过滤器完成查询
- 使用SingleColumnValueFilter实现对应列的查询
a、命令
1.查询支付方式为1
SingleColumnValueFilter('C1', 'PAYWAY', = , 'binary:1')
2.查询金额大于3000的订单
SingleColumnValueFilter('C1', 'PAY_MONEY', > , 'binary:3000')
3.组合查询
scan 'ORDER_INFO', {FILTER => "SingleColumnValueFilter('C1', 'PAYWAY', = , 'binary:1') AND SingleColumnValueFilter('C1', 'PAY_MONEY', > , 'binary:3000')", FORMATTER => 'toString'}
注意:
- HBase shell中比较默认都是字符串比较,所以如果是比较数值类型的,会出现不准确的情况
- 例如:在字符串比较中4000是比100000大的
12、INCR
(1)需求
某新闻APP应用为了统计每个新闻的每隔一段时间的访问次数,他们将这些数据保存在HBase中。
该表格数据如下所示:
新闻ID | 访问次数 | 时间段 | ROWKEY |
0000000001 | 12 | 00:00-01:00 | 0000000001_00:00-01:00 |
0000000002 | 12 | 01:00-02:00 | 0000000002_01:00-02:00 |
要求:原子性增加新闻的访问次数值。
(2)incr操作简介
incr可以实现对某个单元格的值进行原子性计数。语法如下:
incr '表名','rowkey','列蔟:列名',累加值(默认累加1)
- 如果某一列要实现计数功能,必须要使用incr来创建对应的列
- 使用put创建的列是不能实现累加的
(3)导入测试数据
该脚本创建了一个表,名为NEWS_VISIT_CNT,列蔟为C1。并使用incr创建了若干个计数器,每个rowkey为:新闻的编号_时间段。CNT为count的缩写,表示访问的次数。
hbase shell /home/offcn/tmp/NEWS_VISIT_CNT.txt
scan 'NEWS_VISIT_CNT', {LIMIT => 5, FORMATTER => 'toString'}
(4)需求:对0000000020新闻01:00 - 02:00访问计数
1.获取0000000020这条新闻在01:00-02:00当前的访问次数
get_counter 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT'
2.使用incr进行累加
incr 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT'
incr 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT',2
3.再次查案新闻当前的访问次数
get_counter 'NEWS_VISIT_CNT','0000000020_01:00-02:00','C1:CNT'
13、更多的操作
以下连接可以查看到所有HBase中支持的SHELL脚本。
https://learnhbase.net/2013/03/02/hbase-shell-commands/
例如:显示服务器状态
2.4.1 :062 > status
1 active master, 0 backup masters, 3 servers, 0 dead, 1.0000 average load
Took 0.0034 seconds
显示HBase当前用户,例如:
2.4.1 :066 > whoami
root (auth:SIMPLE)
groups: root
Took 0.0080 seconds
显示当前所有的表
2.4.1 :067 > list
TABLE
ORDER_INFO
1 row(s)
Took 0.0266 seconds
=> ["ORDER_INFO"]
(4) count
统计指定表的记录数,例如:
2.4.1 :070 > count 'ORDER_INFO'
66 row(s)
Took 0.0404 seconds
=> 66
展示表结构信息
2.4.1 :074 > describe 'ORDER_INFO'
Table ORDER_INFO is ENABLED
ORDER_INFO
COLUMN FAMILIES DESCRIPTION
{NAME => 'C1', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CELLS => 'FALSE', CACHE_DATA_ON_WRITE =
> 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE =
> 'false', IN_MEMORY => 'false', CACHE_BLOOMS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLO
CKSIZE => '65536'}
1 row(s)
Took 0.0265 seconds
检查表是否存在,适用于表量特别多的情况
2.4.1 :075 > exists 'ORDER_INFO'
Table ORDER_INFO does exist
Took 0.0050 seconds
=> true
检查表是否启用或禁用
2.4.1 :077 > is_enabled 'ORDER_INFO'
true
Took 0.0058 seconds
=> true
2.4.1 :078 > is_disabled 'ORDER_INFO'
false
Took 0.0085 seconds
=> 1
该命令可以改变表和列蔟的模式,例如:
# 创建一个USER_INFO表,两个列蔟C1、C2
create 'USER_INFO', 'C1', 'C2'
# 新增列蔟C3
alter 'USER_INFO', 'C3'
# 删除列蔟C3
alter 'USER_INFO', 'delete' => 'C3'
注意:
- 'delete' => 'C3',还是一个Map结构,只不过只有一个key,可以省略两边的{}
(9)disable/enable
禁用一张表/启用一张表
删除一张表,记得在删除表之前必须先禁用
清空表的数据,禁用表-删除表-创建表
14、名称空间操作
//1.创建一个命名空间:"haha"
hbase(main):001:0> create_namespace 'haha'
0 row(s) in 1.0220 seconds
//2.查看命名空间:"haha"
hbase(main):003:0> describe_namespace 'haha'
DESCRIPTION
{NAME => 'haha'}
1 row(s) in 0.0160 seconds
//3.列举出所有的命名空间
hbase(main):004:0> list_namespace
NAMESPACE
default //用户建表未指定命名空间时的默认命名空间
haha
hbase //系统内建表,包括namespace和meta表
3 row(s) in 0.0190 seconds
//4.在某命名空间中创建表
hbase(main):005:0> create 'haha:wowo','info'
0 row(s) in 1.2680 seconds
=> Hbase::Table - haha:wowo
//5.列出某命名空间下的所有表
hbase(main):008:0> list_namespace_tables 'haha'
TABLE
wowo
1 row(s) in 0.0150 seconds
// 6. 数据操作
put 'haha:wowo','1001','info:name','zhangsan'
get 'haha:wowo','1001','info:name'
scan 'haha:wowo'
(五)Hbase Java编程
1、需求与数据集
某某自来水公司,需要存储大量的缴费明细数据。以下截取了缴费明细的一部分内容。
用户id | 姓名 | 用户地址 | 性别 | 缴费时间 | 表示数(本次) | 表示数(上次) | 用量(立方) | 合计金额 | 查表日期 | 最迟缴费日期 |
4944191 | 登卫红 | 贵州省铜仁市德江县7单元267室 | 男 | 2020-05-10 | 308.1 | 283.1 | 25 | 150 | 2020-04-25 | 2020-06-09 |
因为缴费明细的数据记录非常庞大,该公司的信息部门决定使用HBase来存储这些数据。并且,他们希望能够通过Java程序来访问这些数据。
2、准备工作
(1)创建IDEA Maven项目
groupId | com.offcn |
artifactId | example-hbase |
(2)导入pom依赖
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
(3)复制HBase和Hadoop配置文件
将以下三个配置文件复制到resource目录中
hbase-site.xml
从Linux中下载:sz /home/offcn/apps/hbase-2.2.4/conf/hbase-site.xml
core-site.xml
从Linux中下载:sz /home/offcn/apps/hadoop-3.2.1/etc/hadoop/core-site.xml
log4j.properties
注意:请确认配置文件中的服务器节点hostname/ip地址配置正确
(4)创建包结构和类
- 在java目录下创建com.offcn.hbase.admin.api_test 包结构
- 创建TableAmdinTest类
(5)创建Hbase连接以及admin管理对象
要操作Hbase也需要建立Hbase的连接。此处我们仍然使用TestNG来编写测试。使用@BeforeTest初始化HBase连接,创建admin对象、@AfterTest关闭连接。
package com.offcn.hbase.admin.api_test;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class TableAmdinTest {
private Configuration conf ;
private Connection conn ;
private Admin admin ;
@Before
public void beforeOp()throws Exception {
// 1 获取配置对象
conf = HBaseConfiguration.create();
// 指定zookeeper地址
conf.set("hbase.zookeeper.quorum","bd-offcn-01,bd-offcn-02,bd-offcn-03");
// 2 获取与hbase的链接
conn = ConnectionFactory.createConnection(conf);
// 3 获取admin对象,通过改对象做些操作表的操作,例如创建表,删除表 判断表是否存在
admin = conn.getAdmin();
}
@Test
public void test()throws Exception {
System.out.println(admin);
}
@After
public void afterOp()throws Exception {
// 4 关闭资源
admin.close();
conn.close();
}
}
3、需求一:使用Java代码创建表
创建一个名为WATER_BILL的表,包含一个列蔟C1。
实现步骤:
- 判断表是否存在,存在,则退出
- 使用TableDescriptorBuilder.newBuilder构建表描述构建器
- 使用ColumnFamilyDescriptorBuilder.newBuilder构建列蔟描述构建器
- 构建列蔟描述,构建表描述
- 创建表
参考代码:
@Test
public void createTable()throws Exception {
TableName water_bill = TableName.valueOf("WATER_BILL");
String columnFamily = "C1";
//1. 判断表是否存在 存在,则退出
if(admin.tableExists(water_bill)){
return;
}
//2. 使用TableDescriptorBuilder.newBuilder构建表描述构建器
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(water_bill);
//3. 使用ColumnFamilyDescriptorBuilder.newBuilder构建列蔟描述构建器
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(columnFamily));
//4. 构建列蔟描述,构建表描述
ColumnFamilyDescriptor build = columnFamilyDescriptorBuilder.build();
// 让表与列族产生关系
tableDescriptorBuilder.setColumnFamily(build);
TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
//5. 创建表
admin.createTable(tableDescriptor);
}