Hbase
HBase简介
1.1 HBase 定义
HBase 是一种分布式、可扩展、支持海量数据存储的 NoSQL 数据库。
HBase – Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩、实时读写的分布式NoSQL数据库
利用Hadoop HDFS作为其文件存储系统,利用Hadoop MapReduce来处理HBase中的海量数据,利用Zookeeper作为其分布式协同服务
主要用来存储非结构化和半结构化的松散数据(列存 NoSQL 数据库)
逻辑上,HBase的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。但从HBase的底层物理存储结构(K-V)来看,HBase更像是一个multi-dimensional map
1.2 HBase 背景
HBase 的原型是 Google 的 BigTable 论文,受到了该论文思想的启发,目前作为 Hadoop的子项目来开发维护,用于支持结构化的数据存储。
2006 年 Google 发表 BigTable 白皮书
2006 年开始开发 HBase
2008 年北京成功开奥运会,程序员默默地将 HBase 弄成了 Hadoop 的子项目
2010 年 HBase 成为 Apache 顶级项目
现在很多公司二次开发出了很多发行版本
HBase 是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用 HBASE 技术可在廉价 PC Server 上搭建起大规模结构化存储集群。HBase 的目标是存储并处理大型的数据,更具体来说是仅需使用普通的硬件配置,就能够处理由成千上万的行和列所组成的大型数据。
HBase 是 Google Bigtable 的开源实现,但是也有很多不同之处。比如:
Google Bigtable 利用 GFS 作为其文件存储系统,HBase 利用 Hadoop HDFS 作为其文件存储系统;
Google 运行 MapReduce 来处理 Bigtable 中的海量数据,HBase 同样利用 Hadoop MapReduce 来处理 HBase 中的海量数据;
Google Bigtable 利用 Chubby 作为协同服务,HBase 利用 Zookeeper 作为对应。
1.3 HBase 数据模型
逻辑上,HBase 的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。但从 HBase 的底层物理存储结构(K-V)来看,HBase 更像是一个 multi-dimensional map。
1.3.1 HBase 逻辑结构
1.3.2 HBase 物理存储结构
1.3.3 数据模型
- NameSpace
命名空间,类似于关系型数据库的 DatabBase 概念,每个命名空间下有多个表。HBase 有两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表,default 表是用户默认使用的命名空间。
- Region
类似于关系型数据库的表概念。不同的是,HBase 定义表时只需要声明列族即可,不需要声明具体的列。这意味着,往 HBase 写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase 能够轻松应对字段变更的场景。
- Row
HBase 表中的每行数据都由一个 RowKey 和多个 Column(列)组成,数据是按照 RowKey 的字典顺序存储的,并且查询数据时只能根据 RowKey 进行检索,所以 RowKey 的设计十分重要。
- Column
HBase 中的每个列都由 Column Family(列族)和 Column Qualifier(列限定符)进行限
定,例如 info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。
- Time Stamp
用于标识数据的不同版本(version),每条数据写入时,如果不指定时间戳,系统会
自动为其加上该字段,其值为写入 HBase 的时间。
- Cell
由 {rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。cell 中的数
据是没有类型的,全部是字节码形式存贮。
1.4 HBase 基本架构
(不完整版)
- RegionServer
Region Server 为 Region 的管理者,其实现类为 HRegionServer,主要作用如下:
① 对于数据的操作:get, put, delete;
② 对于 Region 的操作:splitRegion、compactRegion。
- Master
Master 是所有 RegionServer 的管理者,其实现类为 HMaster,主要作用如下:
① 对于表的操作:create, delete, alter
② 对于 RegionServer的操作:分配 regions 到每个 RegionServer,监控每个 RegionServer 的状态,负载均衡和故障转移。
- Zookeeper
HBase 通过 Zookeeper 来做 Master 的高可用、RegionServer 的监控、元数据的入口以及
集群配置的维护等工作。
- HDFS
HDFS 为 HBase 提供最终的底层数据存储服务,同时为 HBase 提供高可用的支持。
安装
基于hadoop和zookeeper下
1.验证Hadoop平台是否正常
2.验证Zookeeper是否正常
3.解压并修改配置信息
4.同步节点
5.启动并验证
详细见安装目录下
HBase Shell 操作
基本操作
1.进入HBase客户端
hbase shell
2.查看帮助命令
help
3.查看当前数据库中有哪些表
list
表的操作
1.创建表
create 'student','info'
2.插入数据到表
put 'student','1001','info:sex','male'
put 'student','1001','info:age','18'
put 'student','1002','info:name','Janna'
put 'student','1002','info:sex','female'
put 'student','1002','info:age','20'
3.扫描查看表数据
scan 'student'
scan 'student',{STARTROW => '1001', STOPROW => '1001'}
scan 'student',{STARTROW => '1001'}
4.查看表结构
describe 'student'
5.更新指定字段的数据
put 'student','1001','info:name','Nick'
put 'student','1001','info:age','100'
6.查看 “指定行” 或 “指定列族:列” 的数据
get 'student','1001'
get 'student','1001','info:name'
7.统计表数据行数
count 'student'
8.变更表信息
将 info 列族中的数据存放 3 个版本
alter 'student',{NAME=>'info',VERSIONS=>3}
get 'student','1001',{COLUMN=>'info:name',VERSIONS=>3}
9.删除数据
① 删除某 rowkey 的全部数据
deleteall 'student','1001'
② 删除某 rowkey 的某一列数据
delete 'student','1002','info:sex'
10.清空表数据
truncate 'student'
提示:清空表的操作顺序为先 disable,然后再 truncate。
11.删除表
① 首先需要先让该表为 disable 状态
disable 'student'
② 然后才能 drop 这个表
drop 'student'
命名空间的基本操作
1.查看命名空间
list_namespace
2.创建命名空间
create_namespace 'bigdata'
3.在新的命名空间中创建表
create 'bigdata:student','info'
4.删除命名空间
只能删除空的命名空间,如果不为空,需要先删除该命名空间下的所有表
drop_namespace 'bigdata'
HBASE SHELL
Group name: namespace
对数据库的操作
Commands: alter_namespace, create_namespace, describe_namespace, drop_namespace, list_namespace, list_namespace_tables
创建
create_namespace
create_namespace 'bigdata28'
查看
list_namespace
NAMESPACE
bigdata28 -- 创建的
default --默认命名空间
hbase --用于存储命名空间以及表的元数据信息
查看详细信息
describe_namespace
describe_namespace 'bigdata28'
删除命名空间
drop_namespace
drop_namespace 'bigdata28'
查看命名空间下的表
list_namespace_tables
list_namespace_tables 'default'
Group name: ddl
对表进行操作
Commands: alter, alter_async, alter_status, clone_table_schema, create, descr
ibe, disable, disable_all, drop, drop_all, enable, enable_all, exists, get_table, is_disabled, is_enabled, list, list_regions, locate_region, show_filters
创建表
create
# t 表示表名称
# f 表示列族
create 't1', 'f1', 'f2', 'f3'
create 'bigdata28:veh_pass','veh_info','pass_info'
查看表
list_namespace_tables 'bigdata28'
list
查看表的描述信息
describe "bigdata28:veh_pass"
# 每个列族都有对应一个描述信息
# VERSIONS 表示当前列族中存储的数据对应的最多的版本数
{NAME => 'pass_info', 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', BLOCKSIZE => '65536'}
{NAME => 'veh_info', 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', BLOCKSIZE => '65536'}
修改表信息
alter
alter 'bigdata28:veh_pass', {NAME => 'pass_info', IN_MEMORY => 'true'}
# 表示将该列族的数据放入内存中,以加快对该列族数据的访问速度。
# 表示将当前pass_info列族中的数据版本设置为最高可保留3个版本的数据
alter 'bigdata28:veh_pass', {NAME => 'pass_info', VERSIONS => 3}
删除表
drop
drop 't1'
# 对于表删除时,需要对其进行关闭之后再进行删除
ERROR: Table t1 is enabled. Disable it first.
关闭或开启表
disable 't1'
enable 't1'
查看表的Region信息
list_regions
list_regions "bigdata28:veh_pass"
Hbase中的数据是存储在HDFS中,那么如何进行存储的?
Hbase的数据存储目录在 /hbase/data/ 路径中
命名空间、表都是以目录的形式存在的
在表目录下,会存在有一个Region目录,Region是按照RowKey进行划分的(当数据量较少时,仅有一个) 在Region下对应有列族的目录,列族目录下存储具体的数据 ,当数据添加到HBASE后先放在内存当中,之后再保存到对于HDFS路径中
Group name: dml
对数据进行操作
Commands: append, count, delete, deleteall, get, get_counter, get_splits, inc
r, put, scan, truncate, truncate_preserve
添加数据
put
# ns1 表示命名空间
# t1 表示表名称
# r1 表示RowKey信息
# c1 表示列族及列信息
# value 表示对应具体列下的单个数据
put 'ns1:t1', 'r1', 'c1', 'value'
put 'bigdata28:veh_pass','pass1','veh_info:hphm','ASJ666'
put 'bigdata28:veh_pass','pass1','veh_info:cllx','K33'
put 'bigdata28:veh_pass','pass1','veh_info:clys','red'
put 'bigdata28:veh_pass','pass12','veh_info:hphm','ASJ888'
put 'bigdata28:veh_pass','pass12','veh_info:cllx','K13'
put 'bigdata28:veh_pass','pass12','veh_info:clys','w'
# 添加数据时指定时间戳
put 't1', 'r1', 'c1', 'value', ts1
put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_5', 1709279201100
# 展示数据时,按照时间戳从大到小依次展示
put命令可以按照 Rowkey 列族 列 添加单个数据
查看数据
get
get 'ns1:t1', 'r1'
get 'bigdata28:veh_pass','pass1'
# 指定列进行查看数据
get 'bigdata28:veh_pass','pass9', {COLUMN => ['veh_info:cllx', 'veh_info:hphm']}
# Column family cllx does not exist 表示给定的列族不存在,列需要使用列族对其进行修饰
get 可以根据rowKey信息获取到一行数据
查看所有数据
scan
scan 'bigdata28:veh_pass'
# 扫描某个列下所有数据
scan 'bigdata28:veh_pass',{COLUMNS => 'veh_info:hphm'}
# 获取多列数据
scan 'bigdata28:veh_pass',{COLUMNS => ['veh_info:hphm','veh_info:cllx']}
# 限制展示行数(RowKey)
scan 'bigdata28:veh_pass',{LIMIT => 3}
scan 'bigdata28:veh_pass',{COLUMNS => 'veh_info:hphm',LIMIT => 3}
# 指定范围过滤数据
# STARTROW指定开始的RowKey
# STARTROW是包含的
scan 'bigdata28:veh_pass',{STARTROW => 'pass12'}
# STOPROW可以指定结束Rowkey,并且不包含
scan 'bigdata28:veh_pass',{STARTROW => 'pass12',STOPROW => 'pass8'}
# 案例:
# 需求:获取pass1开头的所有数据
scan 'bigdata28:veh_pass',{STARTROW => 'pass1',STOPROW => 'pass8'}
# 当添加了pass7,当前的扫描的数据不符合规范
scan 'bigdata28:veh_pass',{STARTROW => 'pass1',STOPROW => 'pass1~'}
# 在设定STARTROW及STOPROW范围时,可以参考ASC码表中的最大值和最小值
# 扫描所有数据
scan 'bigdata28:veh_pass', {RAW => true, VERSIONS => 4}
scan 可以扫描当前表中的所有数据
删除
delete 'ns1:t1', 'r1', 'c1', ts1
delete 'bigdata28:veh_pass','pass9','veh_info:hphm', 1709279201176
# 删除某个RowKey下的 一列
delete 'bigdata28:veh_pass','pass9','veh_info:hphm'
# 当删除数据时,实际上是对数据进行标记 type=Delete
# 在之后的某一时刻会对标记数据进行批量整理
deleteall
deleteall 'bigdata28:veh_pass','pass9'
# 删除列族信息 可以使用表的Alter语法
alter 'bigdata28:veh_pass',{ METHOD => 'delete', NAME => 'pass_info' }
count
# 统计表的行数
count 'bigdata28:veh_pass'
truncate
# 清空表
# 立即生效
truncate 'bigdata28:veh_pass'
HBASE名词
Rowkey
唯一标识一行数据
可以通过RowKey获取一行数据
按照字典顺序排序的:字典序排序规则是按位置比较,比较大小是按照ASC码值进行比较
Row key只能存储64k的字节数据 一般情况下RowKey在 10-100byte 之间最为合适
put 'bigdata28:veh_pass','pass1','veh_info:hphm','ASJ777'
put 'bigdata28:veh_pass','pass1','veh_info:cllx','K31'
put 'bigdata28:veh_pass','pass1','veh_info:clys','w'
get 'bigdata28:veh_pass','pass1'
put 'bigdata28:veh_pass','pass8','veh_info:hphm','ASJ999'
put 'bigdata28:veh_pass','pass8','veh_info:cllx','K32'
put 'bigdata28:veh_pass','pass8','veh_info:clys','r'
put 'bigdata28:veh_pass','pass10','veh_info:hphm','ASJ000'
put 'bigdata28:veh_pass','pass10','veh_info:cllx','K30'
put 'bigdata28:veh_pass','pass10','veh_info:clys','w'
Column Family(列族)和qualifier(列)
HBase表中的每个列都归属于某个列族(簇),列族必须作为表模式(schema)定义的一部分预先给出。如 create ‘test’, ‘course’。
列名以列族作为前缀,每个“列族”都可以有多个列成员(column);如course:math, course:english, 新的列族成员(列)可以随后按需、动态加入。权限控制、存储以及调优都是在列族层面进行的;
HBase把同一列族里面的数据存储在同一目录下,由几个文件保存。(在HDFS的列族目录下,可以查看到)
Timestamp时间戳
在HBase每个cell存储单元对同一份数据有多个版本,根据唯一的时间戳来区分每个版本之间的差异,不同版本的数据按照时间倒序排序,最新的数据版本排在最前面。
alter 'bigdata28:veh_pass', {NAME => 'veh_info', VERSIONS => 3}
# 将veh_pass中的veh_info列族版本设置为3,其列族下的列,可以同时存储三个版本的时间戳数据
put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_1'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_1'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_1'
put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_2'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_2'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_2'
put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_3'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_3'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_3'
get 'bigdata28:veh_pass','pass9'
# get命令只能获取最新版本的数据
scan 'bigdata28:veh_pass', {RAW => true, VERSIONS => 3}
扫描全表时,可以获取多个版本的数据
put 'bigdata28:veh_pass','pass9','veh_info:hphm','ASJ000_4'
put 'bigdata28:veh_pass','pass9','veh_info:cllx','K30_4'
put 'bigdata28:veh_pass','pass9','veh_info:clys','w_4'
scan 'bigdata28:veh_pass', {RAW => true, VERSIONS => 4}
# 对于HBASE中的数据操作,并不是一个实时的。对于不符合要求的数据,会通过定期检查,再将数据去除
Cell单元格
由行和列的坐标交叉决定。
单元格是有版本的。
单元格的内容是未解析的字节数组。
由 {row key, column( = +), version} 唯一确定的单元。
cell中的数据是没有类型的,全部是字节码形式存贮。(对于JavaAPI调用时,需要指定数据类型)
put 'bigdata28:veh_pass','pass9','veh_info:hpzl','大型汽车'
# 对于命令行,如果出现有中文,那么展示时,无法对其进行直接查看,英文可以由命令行转换
put 'bigdata28:veh_pass','pass9','pass_info:hpzl','大型汽车'
# 列是归属于列族的,不同列族之间的列名可以相同
# 一般情况下相同类型的列会放到一个列族下,所以列名一般不重复
HBase架构分析
HMaster
1.为Region server分配region (当表中添加数据时,产生的Region具体是由HMaster将其分配给从节点HRegionServer)
2.负责Region server的负载均衡 (当表在使用过程中,一开始仅有一个Region,当数据量不断增大时,会对Region进行分裂成多个,分裂的Region是由HMaster进行指定到具体的从节点)
3.发现失效的Region server并重新分配其上的region(如果当Region server 宕机,此时需要将该RegionServer上的数据分配给其他节点)
4.管理用户对table的增删改操作(对表中元数据的操作,需要请求Master,但是数据的读写,并不是由HMaster管理)
RegionServer
1.Region server维护region,处理对这些region的IO请求(负责对HMaster分配的Region进行数据的检查更新操作,同时客户端的数据访问,会直接请求HRegionServer)
2.Region server负责切分在运行过程中变得过大的region(当表中的数据不断添加,数据是存储在Region中,当Region数据量过大时,会降低查询效率,于是需要将Region中的数据划分成多个Region进行分开存储)
Region
1.HBase自动把表水平划分成多个区域(region),每个region会保存一个表里面某段连续的数据(对于表中的RowKey是按照字典序排序,同时按照RowKey进行划分Region);每个表一开始只有一个region(但是建表时,也可以指定Region划分的规则生成多个Region),随着数据不断插入表,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region(裂变)
2.当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Regionserver 上。(通过该方式可以达到降低用户的单节点请求压力,当大量的用户请求被分配到少了的RegionServer上此时称为热点问题)
架构原理
读写流程
当数据被客户端提交到Hbase集群时,Hbase如何发现数据要存储的位置 => 数据存储位置是当前Hbase的元数据信息 =>Hbase元数据信息存储在什么位置?=> 通过查看namespace 发现有hbase命名空间,在该命名空间中对应有meta(表的元数据表,包括当前表的Region中保存的RowKey信息以及位置信息)namespace表示当前命名空间的元数据 => 当提交写入数据请求时,需要通过meta表知道表的Region存储位置 => 那么meta表客户端如何知道存储在什么位置?meta表也是一张Hbase表 => Zookeeper中/hbase路径下存在有meta-region-server, namespace节点,该节点中保存的数据为具体哪个节点中的RegionServer中 => 于是可以先去Meta表中的RegionServer中发现表的元数据 => 再去目标表中的RegionServer中添加数据
写操作
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)将数据顺序写入(追加)到 WAL
5)将数据写入对应的 MemStore,数据会在 MemStore 进行排序
6)向客户端发送 ack响应信息
7)等达到 MemStore 的刷写时机后,将数据刷写到 HFile。
读操作
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中如果直接找到对应的数据,那么可以直接返回,如果找不到,再去BlockCache(读缓存,存放了HFile中的元数据信息)中进行查询,如果能查询到对应HFile,那么可以直接返回,如果查询到多个HFile那么对应会进行合并操作,将合并后的结果数据进行返回
5)将从文件中查询到的数据块元数据信息(Block,HFile 数据存储单元,默认大小为 64KB)缓存到Block Cache。
6)将合并后的最终结果返回给客户端。
API
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.shujia</groupId>
<artifactId>bigdate28</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>hbase</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.hbase/hbase-client -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.1.9</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
hbase连接
package com.shujia;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;
public class Code01HBaseConnection {
public static void main(String[] args) throws IOException {
/* 创建连接需要使用ConnectionFactory
Java中的设计模式 -> 工厂模式 -> 可以使用静态方法获取其连接对象
static Connection createConnection(Configuration conf)
*/
Configuration configuration = new Configuration();
/* 由Hbase的读写原理,知道客户端在连接Hbase做数据操作时,并不需要连接HMaster
而需要连接Zookeeper,于是可以查看Hbase的配置文件,因为Hbase集群部署时
填写了Zookeeper的配置信息,同时对于configuration.set中的Key和Value是
配置文件中的 name 和 Value 值
*/
// configuration.set("hbase.zookeeper.quorum","localhost");
configuration.set("hbase.zookeeper.quorum","master,node1,node2");
Connection connection = ConnectionFactory.createConnection(configuration);
// 获取具体的连接实现类? getClass => 获取类对象
// org.apache.hadoop.hbase.client.ConnectionImplementation
System.out.println("获取连接对象:"+connection.getClass().getName());
/*
当连接对象被获取,是否表明Hbase一定可用?
不能表明,因为对于Hbase来说本身连接并没有添加到连接信息中
*/
connection.close();
}
}
创建了一个新的命名空间和表
package com.shujia;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class Code02HbaseAdmin {
Connection connection = null;
Admin admin = null;
@Before
public void getCon() throws IOException {
Configuration configuration = new Configuration();
configuration.set("hbase.zookeeper.quorum","master:2181");
// configuration.set("hbase.zookeeper.quorum","master:2181,node1:2181,node2:2181");
connection = ConnectionFactory.createConnection(configuration);
// 不添加配置对象,通过读取配置文件hbase-site.xml进行连接
// connection = ConnectionFactory.createConnection();
/*
对于上述的 连接对象获取方式对比
对于添加参数的方式,在代码中对配置信息写死会导致 当在公司测试环境中测试通过后,
将环境变更成开发环境后,需要对代码进行更改,再重新打包,比较耗时,同时工作流程不符合要求
于是对配置文件中的信息进行修改,不需要对代码进行重新编译,更加方便规范
*/
// 对于Admin对象可以对Hbase的元数据进行操作
admin = connection.getAdmin();
}
@Test
public void createNameSpace() throws IOException {
// createNamespace(NamespaceDescriptor descriptor)
NamespaceDescriptor.Builder builder = NamespaceDescriptor.create("api_2");
NamespaceDescriptor namespaceDescriptor = builder.build();
admin.createNamespace(namespaceDescriptor);
}
// 老API方式
@Test
public void createTableOld() throws IOException {
// TableDescriptor desc
/*
Table should have at least one column family.
创建表时必须要指定至少一个列族信息
*/
TableName tableName = TableName.valueOf("api:t1");
HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
// 添加列族信息
// HColumnDescriptor family
hTableDescriptor.addFamily(new HColumnDescriptor("info"));
hTableDescriptor.addFamily(new HColumnDescriptor("data"));
admin.createTable(hTableDescriptor);
}
@Test
public void createTableNew() throws IOException {
/*
在新API中使用TableDescriptorBuilder去构建TableDescriptor构建方式相对较为特殊
需要先通过newBuilder传入TableName对象并通过build方法返回TableDescriptor的子类对象
该写法需要记住,以后在其他框架中也会有类似的构造形式 Builder.build方法构建
*/
TableName tableName = TableName.valueOf("api:student");
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tableName);
// 在当前表描述类中发现有列族信息
// ColumnFamilyDescriptorBuilder 以同样的方式Builder.build方法构建
ColumnFamilyDescriptor scoreColumnFamily = ColumnFamilyDescriptorBuilder.newBuilder("score".getBytes()).build();
ColumnFamilyDescriptor infoColumnFamily = ColumnFamilyDescriptorBuilder.of("info");
// 通过set方式添加一个列族信息
tableDescriptorBuilder.setColumnFamily(scoreColumnFamily);
tableDescriptorBuilder.setColumnFamily(infoColumnFamily);
TableDescriptor tableDescriptor = tableDescriptorBuilder.build();
admin.createTable(tableDescriptor);
}
@After
public void close() throws IOException {
connection.close();
}
}
检查一个表是否存在于HBase中和获取表的描述信息
package com.shujia;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class Code03AdminShow {
Connection connection = null;
Admin admin = null;
@Before
public void getCon() throws IOException {
Configuration configuration = new Configuration();
configuration.set("hbase.zookeeper.quorum","master:2181");
// configuration.set("hbase.zookeeper.quorum","master:2181,node1:2181,node2:2181");
connection = ConnectionFactory.createConnection(configuration);
// 不添加配置对象,通过读取配置文件hbase-site.xml进行连接
// connection = ConnectionFactory.createConnection();
/*
对于上述的 连接对象获取方式对比
对于添加参数的方式,在代码中对配置信息写死会导致 当在公司测试环境中测试通过后,
将环境变更成开发环境后,需要对代码进行更改,再重新打包,比较耗时,同时工作流程不符合要求
于是对配置文件中的信息进行修改,不需要对代码进行重新编译,更加方便规范
*/
// 对于Admin对象可以对Hbase的元数据进行操作
admin = connection.getAdmin();
}
@Test
public void exists() throws IOException {
// Hbase 区分表的大小写
TableName tableName = TableName.valueOf("api:Student");
boolean tableExists = admin.tableExists(tableName);
System.out.println(new String(tableName.getName())+"是否存在:"+tableExists);
}
@Test
public void desc() throws IOException {
TableDescriptor descriptor = admin.getDescriptor(TableName.valueOf("api:student"));
// 获取表中所有列族的信息
ColumnFamilyDescriptor[] columnFamilies = descriptor.getColumnFamilies();
for (ColumnFamilyDescriptor columnFamily : columnFamilies) {
String columnFamilyName = new String(columnFamily.getName());
System.out.println("columnFamilyName:"+columnFamilyName);
}
}
@After
public void close() throws IOException {
connection.close();
}
}
表添加数据
package com.shujia;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Code04Table {
Connection connection = null;
Admin admin = null;
@Before
public void getCon() throws IOException {
connection = ConnectionFactory.createConnection();
admin = connection.getAdmin();
}
@Test
public void putOneData() throws IOException, ParseException {
// 需要获取指定表的对象
TableName tableName = TableName.valueOf("api:student");
Table student = connection.getTable(tableName);
/*
由于Table中的put方法需要获取Put类对象,于是可以通过new 方式获取
对于该数据 1500100001,施笑槐,22,女,文科六班 使用id信息作为RowKey
*/
// Put put = new Put("1500100001".getBytes());
// 当需要指定时间戳时
// 2024-03-02 00:00:00
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = simpleDateFormat.parse("2024-03-02 00:00:00");
long time = date.getTime();
Put put = new Put("1500100001".getBytes(), time);
// public Put addColumn(byte [] family, byte [] qualifier, byte [] value) {
put.addColumn(
"info".getBytes(),
"name".getBytes(),
"施笑槐".getBytes()
);
put.addColumn(
"info".getBytes(),
Bytes.toBytes("age"),
Bytes.toBytes(22)
);
put.addColumn(
"info".getBytes(),
"gender".getBytes(),
"女".getBytes()
);
put.addColumn(
"info".getBytes(),
"clazz".getBytes(),
"文科六班".getBytes()
);
student.put(put);
}
@Test
public void putOneData2() throws IOException, ParseException {
Table table = connection.getTable(TableName.valueOf("api:student"));
// 1500100002,吕金鹏,24,男,文科六班
Put put = new Put(Bytes.toBytes("1500100002"));
/*
add 中需要添加一个Cell对象,而Cell表示行列交叉的一个Value数据
并且由 RowKey + 列族+列 + 时间戳 决定
KeyValue是Cell的自实现类 对应构造方法中存在以下参数:
public KeyValue(byte[] row, byte[] family, byte[] qualifier, byte[] value) {
this(row, family, qualifier, Long.MAX_VALUE, KeyValue.Type.Put, value);
}
*/
KeyValue name = new KeyValue(
Bytes.toBytes("1500100002")
, Bytes.toBytes("info")
, Bytes.toBytes("name")
, Bytes.toBytes("吕金鹏")
);
KeyValue age = new KeyValue(
Bytes.toBytes("1500100002")
, Bytes.toBytes("info")
, Bytes.toBytes("age")
, Bytes.toBytes(24)
);
KeyValue gender = new KeyValue(
Bytes.toBytes("1500100002")
, Bytes.toBytes("info")
, Bytes.toBytes("gender")
, Bytes.toBytes("男")
);
KeyValue clazz = new KeyValue(
Bytes.toBytes("1500100002")
, Bytes.toBytes("info")
, Bytes.toBytes("clazz")
, Bytes.toBytes("文科六班")
);
put.add(name);
put.add(age);
put.add(gender);
put.add(clazz);
table.put(put);
}
@After
public void close() throws IOException {
connection.close();
}
}
读取文件内容填充表数据
package com.shujia;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
public class Code05PutMore {
Connection connection = null;
Admin admin = null;
@Before
public void getCon() throws IOException {
connection = ConnectionFactory.createConnection();
admin = connection.getAdmin();
}
@Test
public void putMoreData() throws IOException {
// 上传数据到Hbase中 需要将数据按条读取并插入
BufferedReader bufferedReader = new BufferedReader(new FileReader("data/students.txt"));
Table table = connection.getTable(TableName.valueOf("api:student"));
String line = null;
while ((line = bufferedReader.readLine()) != null) {
String[] split = line.split(",");
if (split.length == 5){
// 如果切分的列为5 那么可以上传数据
Put put = new Put(Bytes.toBytes(split[0]));
KeyValue name = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("name")
, Bytes.toBytes(split[1])
);
KeyValue age = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("age")
, Bytes.toBytes(Integer.parseInt(split[2]))
);
KeyValue gender = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("gender")
, Bytes.toBytes(split[3])
);
KeyValue clazz = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("clazz")
, Bytes.toBytes(split[4])
);
put.add(name);
put.add(age);
put.add(gender);
put.add(clazz);
System.out.println("上传了一条数据:"+line);
table.put(put);
}
}
table.close();
}
// 对提交过程进行优化
@Test
public void putMoreData2() throws IOException {
// 上传数据到Hbase中 需要将数据按条读取并插入
BufferedReader bufferedReader = new BufferedReader(new FileReader("data/students.txt"));
Table table = connection.getTable(TableName.valueOf("api:student_split"));
String line = null;
ArrayList<Put> putList = new ArrayList<>();
while ((line = bufferedReader.readLine()) != null) {
String[] split = line.split(",");
if (split.length == 5){
// 如果切分的列为5 那么可以上传数据
Put put = new Put(Bytes.toBytes(split[0]));
KeyValue name = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("name")
, Bytes.toBytes(split[1])
);
KeyValue age = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("age")
, Bytes.toBytes(Integer.parseInt(split[2]))
);
KeyValue gender = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("gender")
, Bytes.toBytes(split[3])
);
KeyValue clazz = new KeyValue(
Bytes.toBytes(split[0])
, Bytes.toBytes("info")
, Bytes.toBytes("clazz")
, Bytes.toBytes(split[4])
);
put.add(name);
put.add(age);
put.add(gender);
put.add(clazz);
putList.add(put);
if (putList.size() >=100){
// 当List中数据达到100时,需要提交数据到HBASE中
table.put(putList);
System.out.println("100条数据提交成功...");
// 清空数据
putList.clear();
}
}
}
// 当数据读取完成后,需要再对不足100条数据的ArrayList进行提交
table.put(putList);
table.close();
}
@After
public void close() throws IOException {
connection.close();
}
}
获取get方法表数据
package com.shujia;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class Code06GetData {
Connection connection = null;
Admin admin = null;
@Before
public void getCon() throws IOException {
connection = ConnectionFactory.createConnection();
admin = connection.getAdmin();
}
@Test
public void getData() throws IOException {
Table table = connection.getTable(TableName.valueOf("api:student"));
Get get = new Get(Bytes.toBytes("1500100002"));
// Result 中保存了查询数据的结果 和 JDBC中的模式很像
// Result按照指针的方式获取数据,一开始指针位于数据集之前 需要使用advance将指针向前移动一位
Result result = table.get(get);
System.out.println(result.size()); // 4
// while (result.advance()) {
// Cell cell = result.current();
// System.out.println(cell); // null
// String familyName = Bytes.toString(cell.getFamilyArray());
// String columnName = Bytes.toString(cell.getQualifierArray());
// String value = Bytes.toString(cell.getValueArray());
// System.out.println(familyName + ":" + columnName + "\t" + value);
// }
// while (result.advance()){
// Cell cell = result.current();
// String family = Bytes.toString(CellUtil.cloneFamily(cell));
// String column = Bytes.toString(CellUtil.cloneQualifier(cell));
// // 对于不同列的类型转换不一样,需要分开进行处理
// if (column.equals("age")){
// int value = Bytes.toInt(CellUtil.cloneValue(cell));
// System.out.println(family+":"+column+"\t"+value);
// }else {
// String value = Bytes.toString(CellUtil.cloneValue(cell));
// System.out.println(family+":"+column+"\t"+value);
// }
// }
List<Cell> cells = result.listCells();
for (Cell cell : cells) {
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
// 对于不同列的类型转换不一样,需要分开进行处理
if (column.equals("age")){
int value = Bytes.toInt(CellUtil.cloneValue(cell));
System.out.println(family+":"+column+"\t"+value);
}else {
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(family+":"+column+"\t"+value);
}
}
}
@Test
public void getMoreData() throws IOException {
Table table = connection.getTable(TableName.valueOf("api:student"));
ArrayList<Get> getList = new ArrayList<>();
// Result 中保存了查询数据的结果 和 JDBC中的模式很像
// Result按照指针的方式获取数据,一开始指针位于数据集之前 需要使用advance将指针向前移动一位
getList.add(new Get(Bytes.toBytes("1500100002")));
getList.add(new Get(Bytes.toBytes("1500100003")));
getList.add(new Get(Bytes.toBytes("1500100004")));
for (Result result : table.get(getList)) {
List<Cell> cells = result.listCells();
for (Cell cell : cells) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
// 对于不同列的类型转换不一样,需要分开进行处理
if (column.equals("age")){
int value = Bytes.toInt(CellUtil.cloneValue(cell));
System.out.println(rowKey+"\t"+family+":"+column+"\t"+value);
}else {
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(rowKey+"\t"+family+":"+column+"\t"+value);
}
}
}
}
@After
public void close() throws IOException {
connection.close();
}
}
使用scan方法获取数据
package com.shujia;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.util.Iterator;
public class Code07ScanTable {
Connection connection = null;
Admin admin = null;
@Before
public void getCon() throws IOException {
connection = ConnectionFactory.createConnection();
admin = connection.getAdmin();
}
@Test
public void scanTable() throws IOException {
Table table = connection.getTable(TableName.valueOf("api:student"));
// 创建一个无参的Scan 对表中所有的数据进行遍历操作
ResultScanner scanner = table.getScanner(new Scan());
Result result = null;
while ((result = scanner.next())!=null){
for (Cell cell : result.listCells()) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
// 对于不同列的类型转换不一样,需要分开进行处理
if (column.equals("age")) {
int value = Bytes.toInt(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
} else {
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
}
}
}
}
@Test
public void scanTable2() throws IOException {
Table table = connection.getTable(TableName.valueOf("api:student"));
// 创建一个无参的Scan 对表中所有的数据进行遍历操作
ResultScanner scanner = table.getScanner(new Scan());
for (Result result : scanner) {
for (Cell cell : result.listCells()) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
// 对于不同列的类型转换不一样,需要分开进行处理
if (column.equals("age")) {
int value = Bytes.toInt(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
} else {
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
}
}
}
}
@Test
public void scanTable3() throws IOException {
Table table = connection.getTable(TableName.valueOf("api:student"));
// 创建一个无参的Scan 对表中所有的数据进行遍历操作
ResultScanner scanner = table.getScanner(new Scan());
Iterator<Result> iterator = scanner.iterator();
while (iterator.hasNext()) {
Result result = iterator.next();
for (Cell cell : result.listCells()) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
// 对于不同列的类型转换不一样,需要分开进行处理
if (column.equals("age")) {
int value = Bytes.toInt(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
} else {
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
}
}
}
}
@After
public void close() throws IOException {
connection.close();
}
}
范围获取数据
package com.shujia;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
public class Code08ScanTableRange {
Connection connection = null;
Admin admin = null;
@Before
public void getCon() throws IOException {
connection = ConnectionFactory.createConnection();
admin = connection.getAdmin();
}
@Test
public void scanTable() throws IOException {
Table table = connection.getTable(TableName.valueOf("api:student"));
Scan scan = new Scan(
"15001002".getBytes(),
"15001003~".getBytes()
);
ResultScanner scanner = table.getScanner(scan);
Result result = null;
while ((result = scanner.next())!=null){
for (Cell cell : result.listCells()) {
String rowKey = Bytes.toString(CellUtil.cloneRow(cell));
String family = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
// 对于不同列的类型转换不一样,需要分开进行处理
if (column.equals("age")) {
int value = Bytes.toInt(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
} else {
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(rowKey + "\t" + family + ":" + column + "\t" + value);
}
}
}
}
@After
public void close() throws IOException {
connection.close();
}
}
过滤器使用[HBase 系列(七)——HBase过滤器详解_介绍一下hbase过滤器-CSDN博客]
使用MapReduce计算
数据准备
启动hdfs和hbase
start-dfs.sh
start-hbase.sh
进入hbase shell命令行
hbase shell
创建输入表word
创建输入表word
create 'word','content'
插入数据
put 'word','1001','content:info','when all else is lost the future still remains'
put 'word','1002','content:info','sow nothing reap nothing'
put 'word','1003','content:info','keep on going never give up'
put 'word','1004','content:info','the wealth of the mind is the only wealth'
创建输出表result
create 'result','content'
编码
新建maven工程
添加依赖
pom.xml添加如下依赖:
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.7.1</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.7.1</version>
</dependency>
编写Mapper
package org.example.mr;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableMapper;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import java.io.IOException;
// Mapper输出k,v类型
public class WordCountMapper extends TableMapper<Text, IntWritable> {
@Override
protected void map(ImmutableBytesWritable key, Result value, Context context) throws IOException, InterruptedException {
// getValue参数列族,列
String data = Bytes.toString(value.getValue(Bytes.toBytes("content"), Bytes.toBytes("info")));
String[] words = data.split(" ");
for (String w : words) {
context.write(new Text(w), new IntWritable(1));
}
}
}
编写Reducer
package org.example.mr;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.TableReducer;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import java.io.IOException;
// 数据类型 KEYIN, VALUEIN, KEYOUT
// k3, v3, rowkey
public class WordCountReducer extends TableReducer<Text, IntWritable, ImmutableBytesWritable> {
@Override
protected void reduce(Text k3, Iterable<IntWritable> v3, Context context) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable i : v3) {
sum = sum + i.get();
}
//输出:表中的一条记录 Put对象
//使用单词作为行键
Put put = new Put(Bytes.toBytes(k3.toString()));
// 列族, 列限定符, 值
put.addColumn(Bytes.toBytes("content"), Bytes.toBytes("count"), Bytes.toBytes(String.valueOf(sum)));
//写入HBase
context.write(new ImmutableBytesWritable(Bytes.toBytes(k3.toString())), put);
}
}
编写Main
package org.example.mr;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.mapreduce.TableMapReduceUtil;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import java.io.IOException;
public class WordCountMain {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
// 获取配置,并设置hbase的zk地址
Configuration conf = new Configuration();
conf.set("hbase.zookeeper.quorum","192.168.193.140");
// 创建job,并设置主类
Job job = Job.getInstance(conf);
job.setJarByClass(WordCountMain.class);
// scan查询实例
Scan scan = new Scan();
// 添加查询的列,参数为列族、列
scan.addColumn(Bytes.toBytes("content"),Bytes.toBytes("info"));// 参数:列族,列
// 设置job的读取输入的表,Mapper类,输出k,v类型
TableMapReduceUtil.initTableMapperJob("word", scan, WordCountMapper.class,
Text.class, IntWritable.class, job);
// 设置job的Reducer类,及Reduce输出到的表
TableMapReduceUtil.initTableReducerJob("result", WordCountReducer.class, job);
// 执行job
job.waitForCompletion(true);
}
}
测试
IDEA运行main方法,报错如下:
解决方法为
pom.xml添加如下jackson依赖
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
IDEA再次运行main方法,成功
查看结果
原理及优化
查询快的原因
A:如果快速查询(从磁盘读数据),hbase是根据rowkey查询的,只要能快速的定位rowkey, 就能实现快速的查询,主要是以下因素:
1、hbase是可划分成多个region,你可以简单的理解为关系型数据库的多个分区。
2、键是排好序了的
3、按列存储的
首先,能快速找到行所在的region(分区),假设表有10亿条记录,占空间1TB, 分列成了500个region, 1个region占2个G. 最多读取2G的记录,就能找到对应记录;
其次,是按列存储的,其实是列族,假设分为3个列族,每个列族就是666M, 如果要查询的东西在其中1个列族上,1个列族包含1个或者多个HStoreFile,假设一个HStoreFile是128M, 该列族包含5个HStoreFile在磁盘上. 剩下的在内存中。
再次,是排好序了的,你要的记录有可能在最前面,也有可能在最后面,假设在中间,我们只需遍历2.5个HStoreFile共300M
最后,每个HStoreFile(HFile的封装),是以键值对(key-value)方式存储,只要遍历一个个数据块中的key的位置,并判断符合条件可以了。 一般key是有限的长度,假设跟value是1:19(忽略HFile上其它块),最终只需要15M就可获取的对应的记录,按照磁盘的访问100M/S,只需0.15秒。 加上块缓存机制(LRU原则),会取得更高的效率。
B:实时查询
实时查询,可以认为是从内存中查询,一般响应时间在1秒内。HBase的机制是数据先写入到内存中,当数据量达到一定的量(如128M),再写入磁盘中, 在内存中,是不进行数据的更新或合并操作的,只增加数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了HBase I/O的高性能。
实时查询,即反应根据当前时间的数据,可以认为这些数据始终是在内存的,保证了数据的实时响应。
布隆过滤器
对于Hbase可以由Rowkey进行划分Region,再由Region中的列族划分store,Store中有MemStore对数据进行缓存,对刷写的数据可以保存在HDFS中的HFile文件中,每次MemStore刷写都会生成一个HFile文件,如果要过滤数据 找到对应数据存储在对应哪个HFile中即可。于是对于布隆过滤器的使用,可以将HFile看成是一个集合,当数据添加到HFile中,可以由布隆过滤器维护一个二进制的向量,通过Hash函数对数据进行映射。当有数据要验证时,直接对RowKey数据进行hash计算,找到对应位置是否为1即可。过滤掉大部分的HFile,减少需要扫描的Block。误判对于查询结果影响并不大。
RowKey设计-三原则
Rowkey长度原则
rowkey是一个二进制码流,可以是任意字符串,最大长度 64kb ,实际应用中一般为10-100bytes原因如下:一、数据的持久化文件HFile中是按照KeyValue存储的,如果rowkey过长,比如超过100字节,1000w行数据,光rowkey就要占用100*1000w=10亿个字节,将近1G数据,这样会极大影响HFile的存储效率;二、MemStore将缓存部分数据到内存,如果rowkey字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。
Rowkey散列原则
Rowkey散列原则
如果rowkey按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将rowkey的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个RegionServer,以提高负载均衡的几率。如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。如果Rowkey现定义以时间戳作为 Rowkey+随机值,那么Rowkey是有序的,对应会将连续的Rowkey发送到同一个RegionServer中,在存储时,如果数据量较大,那么大部分的写入请求会连续的写入到一个RegionServer中,没有形成分布式的效果。于是形成写热点问题。同时读取数据时,时间戳最近的数据,其访问频率相对较高,所以所有的读请求也会发送到对应少了的RegionServer中,也会形成读热点问题
rowkey唯一原则
必须在设计上保证其唯一性
经常读取的数据存储到一块,将最近可能会被访问的数据放到一块 => 当查询数据时,是按照Rowkey的区间对数据进行获取,相连续的数据会在一个HFile中存储,将遍历的数据一次性返回即可,而不需要再从多个Region中加载多个HFile文件进行返回。
RowKey热点问题
什么是热点问题?当大量的 client 访问 hbase 集群的一个或少数几个节点,造成少数 region server 的读/写请求过多、负载过大,而其他 region server 负载却很小,就造成了“热点”现象。
解决方案:
①加盐:在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。
优点:对数据进行均匀的分散到多个Region中,有效避免热点问题
缺点:要按照RowKey查询对应的Value数据 rand_id 时,会造成数据不确定存放在哪个位置,不能直接根据一个RowKey获取对应Value值
②哈希:哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据
id:1500001 =hash> 5a91734fe2f68e2cb93cd4c3d46776a7
通过生成的密文作为Rowkey可以有效的将数据进行分散开
优点:使负载分散到整个集群,但是读却是可以预测的
缺点:使用Scan方式扫描数据时,不能以明文的方式查看到对应ID的内容,同时也不能使用连续的方式扫描ID数据
③反转:使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性
20240304154101 直接使用时间戳会导致热点问题,于是使用反转的方式将变化快的数据放在前面,不变的放在后面
=> 10145104034202 当秒数发生变化时,对应连续时间数据数据会分散到多个Region中进行存储
优点:有效解决热点问题,同时数据内容可以有效展示,同时对于get方法获取数据时,也可以直接获取到其Value值
缺点:丢失了数据的连续性
④预分区:可以通过指定SPLITS_FILE的值指定分区文件,如果分区信息比较少,也可以直接用SPLITS分区。create ‘split_table_test’, ‘cf’, {SPLITS_FILE => ‘region_split_info.txt’}
思考:
当数据一开始保存到表中时,表中只会存在一个Region,如果插入的数据量较大,所有的写请求都会存入一个RegionServer中的1个Region中,其写入压力非常大,会影响其他表在同RegionServer中的Region的访问
如果一开始创建表时,就给定初始的Region,那么对应数据按照Region中的Rowkey范围进行划分, 那么就不存在有写入压力了
create ‘api:student_split’, ‘info’, {SPLITS_FILE => ‘/root/region_split_info.txt’}
region_split_info.txt:该文件中保存了每个Region的RowKey存储范围
1500100100!
1500100200!
1500100300!
1500100400!
1500100500!
1500100600!
1500100700!
1500100800!
1500100900!
在Hbase中如何判断是否存在有热点问题?
1.打开Hbase页面16010端口
2.找到对应表的描述页面
3.在页面中对每个Region的请求数量进行分析,当某个Region的请求过多时,说明存在有热点问题。需要对当前表进行RowKey重构
BulkLoading及整合HIVE[Hive数据导入hbase使用BulkLoad方式(spark和mapperReduce两种实现)_import org.apache.hadoop.hbase.mapred.tableoutputf-CSDN博客]
5. HBase 优化
5.1 高可用
在 HBase 中 HMaster 负责监控 HRegionServer 的生命周期,均衡 RegionServer 的负载,如果 HMaster 挂掉了,那么整个 HBase 集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以 HBase 支持对 HMaster 的高可用配置。
- 关闭 HBase 集群
stop-hbase.sh
1
- 在 conf 目录下创建 backup-masters 文件,配置高可用 HMaster 节点
touch backup-masters
1
echo slave1 > backup-masters
1
- 同步配置
xsync backup-masters
1
- 启动 HBase
start-hbase.sh
1
5.2 预分区
每一个 region 维护着 StartRow 与 EndRow,如果加入的数据符合某个 Region 维护的 RowKey 范围,则该数据交给这个 Region 维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高 HBase 性能。
- 手动设定预分区
create 'staff1','info','partition1',SPLITS => ['1000','2000','3000','4000']
1
- 生成 16 进制序列预分区
create 'staff2','info','partition2',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
1
-
按照文件中设置的规则预分区
创建 splits.txt 文件内容如下:
aaaa
bbbb
cccc
dddd
然后执行:
create 'staff3','partition3',SPLITS_FILE => 'splits.txt'
5.3 RowKey 设计
一条数据的唯一标识就是 RowKey,那么这条数据存储于哪个分区,取决于 RowKey 处于哪个一个预分区的区间内,设计 RowKey 的主要目的 ,就是让数据均匀的分布于所有的 region 中,在一定程度上防止数据倾斜。(散列性、唯一性、长度原则)
- 生成随机数、hash、散列值
- 字符串反转
20170524000001 转成 10000042507102
20170524000002 转成 20000042507102
12
- 字符串拼接
20170524000001_a12e
20170524000001_93i7
12
5.4 内存优化
HBase 操作过程中需要大量的内存开销,毕竟 Table 是可以缓存在内存中的,一般会分配整个可用内存的 70%给 HBase 的 Java 堆。但是不建议分配非常大的堆内存,因为 GC 过程持续太久会导致 RegionServer 处于长期不可用状态,一般 16~48G 内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
5.5 基础优化
-
允许在 HDFS 的文件中追加内容
hdfs-site.xml、hbase-site.xml
属性: dfs.support.append
解释: 开启 HDFS 追加同步,可以优秀的配合 HBase 的数据同步和持久化。默认值为 true。 -
优化 DataNode 允许的最大文件打开数
hdfs-site.xml
属性: dfs.datanode.max.transfer.threads
解释: HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为 4096 或者更高。默认值:4096 -
优化延迟高的数据操作的等待时间
hdfs-site.xml
属性: dfs.image.transfer.timeout
解释: 如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的时间,建议把
该值设置为更大的值(默认 60000 毫秒),以确保 socket 不会被 timeout 掉。 -
优化数据的写入效率
mapred-site.xml
属性: mapreduce.map.output.compress、mapreduce.map.output.compress.codec
解释: 开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为 true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec 或者其他压缩方式。 -
设置 RPC 监听数量
hbase-site.xml
属性: Hbase.regionserver.handler.count
解释: 默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。 -
优化 HStore 文件大小
hbase-site.xml
属性: hbase.hregion.max.filesize
解释: 默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值,因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两个 Hfile。 -
优化 HBase 客户端缓存
hbase-site.xml
属性: hbase.client.write.buffer
解释: 用于指定 Hbase 客户端缓存,增大该值可以减少 RPC 调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的。 -
指定 scan.next 扫描 HBase 所获取的行数
hbase-site.xml
属性: hbase.client.scanner.caching
解释: 用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大。 -
flush、compact、split 机制
当 MemStore 达到阈值,将 Memstore 中的数据 Flush 进 Storefile;
compact 机制则是把 flush出来的小文件合并成大的 Storefile 文件;
split 则是当 Region 达到阈值,会把过大的 Region一分为二。涉及属性:
hbase.hregion.memstore.flush.size:134217728
1
即:这个参数的作用是当单个 HRegion 内所有的 Memstore 大小总和超过指定值时,flush 该 HRegion 的所有 memstore。RegionServer 的 flush 是通过将请求添加一个队列,模拟生产消费模型来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发 OOM。
hbase.regionserver.global.memstore.upperLimit:0.4
hbase.regionserver.global.memstore.lowerLimit:0.38
12
fs.support.append
解释: 开启 HDFS 追加同步,可以优秀的配合 HBase 的数据同步和持久化。默认值为 true。
-
优化 DataNode 允许的最大文件打开数
hdfs-site.xml
属性: dfs.datanode.max.transfer.threads
解释: HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为 4096 或者更高。默认值:4096 -
优化延迟高的数据操作的等待时间
hdfs-site.xml
属性: dfs.image.transfer.timeout
解释: 如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的时间,建议把
该值设置为更大的值(默认 60000 毫秒),以确保 socket 不会被 timeout 掉。 -
优化数据的写入效率
mapred-site.xml
属性: mapreduce.map.output.compress、mapreduce.map.output.compress.codec
解释: 开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为 true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec 或者其他压缩方式。 -
设置 RPC 监听数量
hbase-site.xml
属性: Hbase.regionserver.handler.count
解释: 默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。 -
优化 HStore 文件大小
hbase-site.xml
属性: hbase.hregion.max.filesize
解释: 默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值,因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两个 Hfile。 -
优化 HBase 客户端缓存
hbase-site.xml
属性: hbase.client.write.buffer
解释: 用于指定 Hbase 客户端缓存,增大该值可以减少 RPC 调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的。 -
指定 scan.next 扫描 HBase 所获取的行数
hbase-site.xml
属性: hbase.client.scanner.caching
解释: 用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大。 -
flush、compact、split 机制
当 MemStore 达到阈值,将 Memstore 中的数据 Flush 进 Storefile;
compact 机制则是把 flush出来的小文件合并成大的 Storefile 文件;
split 则是当 Region 达到阈值,会把过大的 Region一分为二。涉及属性:
hbase.hregion.memstore.flush.size:134217728
1
即:这个参数的作用是当单个 HRegion 内所有的 Memstore 大小总和超过指定值时,flush 该 HRegion 的所有 memstore。RegionServer 的 flush 是通过将请求添加一个队列,模拟生产消费模型来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发 OOM。
hbase.regionserver.global.memstore.upperLimit:0.4
hbase.regionserver.global.memstore.lowerLimit:0.38
12
即:当 MemStore 使用内存总量达到 hbase.regionserver.global.memstore.upperLimit 指定值时,将会有多个 MemStores flush 到文件中,MemStore flush 顺序是按照大小降序执行的,直到刷新到 MemStore 使用内存略小于 lowerLimit。