HBase

1、了解HBase

官方文档:Apache HBase – Apache HBase™ Home

什么是Hbase?

HBase 是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,用于存储海量的结构化或者半结构化,非结构化的数据(底层是字节数组做存储的)

  • HBase 依赖于 HDFS 做底层的数据存储
  • HBase 依赖于 MapReduce 做数据计算
  • HBase 依赖于 ZooKeeper 做服务协调

1、HBase概述

        HBase是Hadoop的生态系统之一,是建立在Hadoop文件系统(HDFS)之上的分布式、面向列的数据库,通过利用Hadoop的文件系统提供容错能力。如果需要进行实时读写或者随机访问大规模的数据集的时候,会考虑使用HBase。

        HBase作为Google Bigtable的开源实现,Google Bigtable利用GFS作为其文件存储系统类似,则HBase利用Hadoop HDFS作为其文件存储系统;Google通过运行MapReduce来处理Bigtable中的海量数据,同样,HBase利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用Chubby作为协同服务,HBase利用Zookeeper作为元数据的元数据存储和容灾。在2010年5月,成为apache顶级项目。

2、HBase处理数据

        虽然Hadoop是一个高容错、高延时的分布式文件系统和高并发的批处理系统,但是它不适用于提供实时计算;

        HBase是可以提供实时计算的分布式数据库,数据被保存在HDFS分布式文件系统上,由HDFS保证期高容错性;

        但是再生产环境中,HBase是如何基于hadoop提供实时性呢?

        HBase上的数据是以StoreFile(HFile)二进制流的形式存储在HDFS上block块儿中;

        但是HDFS并不知道的HBase用于存储什么,它只把存储文件认为是二进制文件,也就是说,HBase的存储数据对于HDFS文件系统是透明的。

3、HBase与HDFS

在下面的表格中,我们对HDFS与HBase进行比较:

HDFS

HBase

HDFS适于存储大容量文件的分布式文件系统。

HBase是建立在HDFS之上的数据库。

HDFS不支持快速单独记录查找。

HBase提供在较大的表快速查找

HDFS提供了高延迟批量处理;没有批处理概念。

HBase提供了数十亿条记录低延迟访问单个行记录(随机存取)。

HDFS提供的数据只能顺序访问。

HBase内部使用哈希表和提供随机接入,并且其存储索引,可将在HDFS文件中的数据进行快速查找。

Hbase ---> HashMap(看作)

4、HBase相关概念

1、分布式数据库

2、列式存储

3、稀疏性

空缺的数据不存储 null 值,直接不占用空间

HBase中需要根据行键、列簇、列限定符和时间戳来确定一个单元格,因此,可以视为一个“四维坐标”,即[行键, 列簇, 列限定符, 时间戳]

4、数据模型

HBase通过表格的模式存储数据,每个表格由列和行组成,其中,每个列又被划分为若干个列簇(colnum family),请参考下面的图:

        表:HBase的数据同样是用表来组织的,表由行和列组成,列分为若干个列簇,行和列的坐标交叉决定了一个单元格。

        行键:每个表由若干行组成,每个行有一个行键作为这一行的唯一标识。访问表中的行只有三种方式:通过单个行键进行查询、通过一个行键的区间来访问、全表扫描。

        列簇:一个HBase表被分组成许多“列簇”的集合,它是基本的访问控制单元。

        列修饰符(列限定符):列簇里的数据通过列限定符(或列)来定位

        单元格:在HBase表中,通过行、列簇和列限定符确定一个“单元格”(cell),单元格中存储的数据没有数据类型,总被视为字节数组byte[]

        时间戳:每个单元格都保存着同一份数据的多个版本,这些版本采用时间戳进行索引

5、HBase数据坐标

HBase中需要根据行键、列簇、列限定符和时间戳来确定一个单元格(cell),cell中的数据是没有类型的,全部是字节码形式存贮。,因此,可以视为一个“四维坐标”,即[行键, 列簇, 列限定符, 时间戳]。

对于上图这样一个HBase表,其数据坐标举例如下:

[“201505003”, “Info”, “email”, 1174184619081]

“xie@qq.com”

[“201505003”, “Info”, “email”, 1174184620720]

“you@163.com”

6、HBase区域

        HBase自动把表水平划分为区域(Region),每个区域都是有若干连续行构成的,一个区域由所属的表、起始行、终止行(不包括这行)三个要素来表示。

  一开始,一个表只有一个区域,但是随着数据的增加,区域逐渐变大,等到它超出设定的阈值(128M)大小,就会在某行的边界上进行拆分,分成两个大小基本相同的区域。然后随着数据的再增加,区域就不断的增加,如果超出了单台服务器的容量,就可以把一些区域放到其他节点上去,构成一个集群。也就是说:集群中的每个节点(Region Server)管理整个表的若干个区域。所以,我们说:区域是HBase集群上分布数据的最小单位。

5、HBase系统架构

架构图

组件介绍

HBase由三种类型的服务器以主从模式构成:

  • Region Server:负责数据的读写服务,用户通过与Region server交互来实现对数据的访问。
  • HBase HMaster:负责Region的分配及数据库的创建和删除等操作。
  • ZooKeeper:负责维护集群的状态(某台服务器是否在线,服务器之间数据的同步操作及master的选举等)。

HDFS的DataNode负责存储所有Region Server所管理的数据,即HBase中的所有数据都是以HDFS文件的形式存储的。出于使Region server所管理的数据更加本地化的考虑,Region server是根据DataNode分布的。HBase的数据在写入的时候都存储在本地。但当某一个region被移除或被重新分配的时候,就可能产生数据不在本地的情况。这种情况只有在所谓的compaction之后才能解决。

Client

        包含访问HBase的接口并维护cache来加快对HBase的访问

Zookeeper

        保证任何时候,集群中只有一个master

        存贮所有Region的寻址入口。

        实时监控Region server的上线和下线信息。并实时通知Master

        存储HBase的schema和table元数据的meta信息

Master

        为Region server分配region

        负责Region server的负载均衡

        发现失效的Region server并重新分配其上的region

        管理用户对table的增删改操作

RegionServer

        Region server维护region,处理对这些region的IO请求

        Region server负责切分在运行过程中变得过大的region 

HLog(WAL log):

        HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是 HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和 region名字外,同时还包括sequence number和timestamp,timestamp是” 写入时间”,sequence number的起始值为0,或者是最近一次存入文件系 统sequence number。

        HLog SequeceFile的Value是HBase的KeyValue对象,即对应HFile中的 KeyValue

Region

        HBase自动把表水平划分成多个区域(region),每个region会保存一个表里面某段连续的数据;每个表一开始只有一个region,随着数据不断插 入表,region不断增大,当增大到一个阀值的时候,region就会等分会两个新的region(裂变);

        当table中的行不断增多,就会有越来越多的region。这样一张完整的表被保存在多个Regionserver上。

Memstore 与 storefile
  1. 一个region由多个store组成,一个store对应一个CF(列簇)
  2. store包括位于内存中的memstore和位于磁盘的storefile写操作先写入 memstore,当memstore中的数据达到某个阈值,hregionserver会启动 flashcache进程写入storefile,每次写入形成单独的一个storefile
  3. 当storefile文件的数量增长到一定阈值后,系统会进行合并(minor、 major compaction),在合并过程中会进行版本合并和删除工作 (majar),形成更大的storefile。
  4. 当一个region所有storefile的大小和超过一定阈值后,会把当前的region 分割为两个,并由hmaster分配到相应的regionserver服务器,实现负载均衡。
  5. 客户端检索数据,先在memstore找,找不到再找storefile
  6. HRegion是HBase中分布式存储和负载均衡的最小单元。最小单元就表示不同的HRegion可以分布在不同的HRegion server上。
  7. HRegion由一个或者多个Store组成,每个store保存一个columns family。
  8. 每个Strore又由一个memStore和0至多个StoreFile组成。

如图:StoreFile 以HFile格式保存在HDFS上。

Table => Region => Store => memStor 和 StoreFile

理解难点

1、flush刷新在HDFS上呈现究竟是怎么刷新的呢??

  我们目前刚刚学习的时候,添加数据,都是一条一条的put进去,而我们在put的数据比较少(小于128M)的时候,我们put完去HDFS上并未查看到我们put的文件,这是因为数据还在内存中,也就是还在memStore中,所以要想在HDFS中查看到,我们必须手动刷新到磁盘中,这是将memStore的数据刷新到StoreFile中去,这样我们在HDFS中就可以查看到了。

2、为什么Hbase不可以使用像Mysql那样进行查询??

  首先,我们应该可以感受到,我们在插入的时候,每行数据,有多少列,列名叫什么完全是我们自己定义的,之所以不支持像MySql那样对列进行查询和操作,因为不确定列的个数和名称。

3、数据最后存在HDFS上的,HDFS不支持删改,为什么Hbase就可以呢??

  这里有个思想误区,的确,数据是以HFile形式存在HDFS上的,而且HDFS的确是不支持删改的,但是为什么Hbase就支持呢?首先,这里的删除并不是真正意义上的对数据进行删除,而是对数据进行打上标记,我们再去查的时,就不会查到这个打过标记的数据,这个数据Hmaster会每隔1小时清理。修改是put两次,Hbase会取最新的数据,过期数据也是这个方式被清理。

2、HBase2.2.7安装搭建

1、上传解压配置环境变量

# 1、解压
tar -xvf hbase-2.2.7-bin.tar.gz.gz
​
# 2、配置环境变量
vim /etc/profile
​
# 3、在最后增加配置
export HBASE_HOME=/usr/local/soft/hbase-2.2.7
export PATH=$PATH:$HBASE_HOME/bin
​
# 4、使环境变量剩下
source /etc/profile

2、修改配置文件

#1、修改hbase-site.xml文件
vim hbase-site.xml
​
# 增加以下配置
<!--指定 zookeeper 服务器 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>master,node1,node2</value>
</property>
​
<!--指定 hbase 根路径 -->
<property>
<name>hbase.rootdir</name>
<value>hdfs://master:9000/hbase</value>
</property>
​
<!--将 hbase 设置为分布式部署。 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
​
<!-- 避免出现启动错误。 -->
<property>
<name>hbase.unsafe.stream.capability.enforce</name>
<value>false</value>
</property>
​
#2、修改hbase-env.sh文件
vim hbase-env.sh
​
# 增加配置
export HBASE_MANAGES_ZK=false
export JAVA_HOME=/opt/module/jdk1.8
​
​
#3、修改regionservers文件
vim regionservers
# 增加配
node1
node2

#4、同步到所有节点
scp -r hbase-1.4.6 node1:`pwd`
scp -r hbase-1.4.6 node2:`pwd`

3、启动Hbase集群

# 启动(在master上执行)
start-hbase.sh

# hbase web ui
http://master:16010
​
# 关闭hbase启动
#stop-hbase.sh

#连接服务端
hbase shell

4、解决 log4j 兼容性问题(非必要)

警告信息,不影响使用 LF4J: Class path contains multiple SLF4J bindings.

cd /usr/local/soft/hbase-2.2.7/lib/client-facing-thirdparty

# HBase 与 Hadoop 在运行时会出现 log4j 兼容性问题,这是因为 HBase 的 log4j 版本与 Hadoop 的产生了冲突,我们这里将 HBase 的 log4j 设置为备份。
mv slf4j-log4j12-1.7.25.jar slf4j-log4j12-1.7.25.jar.bak

5、重置HBase

1、关闭hbase集群

1)杀死进程

2)stop-hbase.sh

2、删除数据 hdfs

        hdfs dfs -rmr /hbase

3、删除元数据 zk

        zkCli.sh rmr /hbase

4、重新启动hbase

        start-hbase.sh

时间同步

        yum install ntp -y ​ ntpdate -u time.windows.com

3、shell命令

命名

描述

语法

help ‘命名名’

查看命令的使用描述

help ‘命令名’

whoami

我是谁

whoami

version

返回hbase版本信息

version

status

返回hbase集群的状态信息

status

table_help

查看如何操作表

table_help

create

创建表

create ‘表名’, ‘列簇名1’, ‘列簇名2’, ‘列簇名N’

alter

修改列簇

添加一个列簇:alter ‘表名’, ‘列簇名’ 删除列簇:alter ‘表名’, {NAME=> ‘列簇名’, METHOD=> ‘delete’}

desc/describe

显示表相关的详细信息

describe ‘表名’

list

列出hbase中存在的所有表

list

exists

测试表是否存在

exists ‘表名’

put

添加或修改的表的值

put ‘表名’, ‘行键’, ‘列簇名’, ‘列值’ put ‘表名’, ‘行键’, ‘列簇名:列名’, ‘列值’

scan

通过对表的扫描来获取对用的值

scan ‘表名’ 扫描某个列簇: scan ‘表名’, {COLUMN=>‘列簇名’} 扫描某个列簇的某个列: scan ‘表名’, {COLUMN=>‘列簇名:列名’} 查询同一个列簇的多个列: scan ‘表名’, {COLUMNS => [ ‘列簇名1:列名1’, ‘列簇名1:列名2’, …]}

get

获取行或单元(cell)的值

get ‘表名’, ‘行键’ get ‘表名’, ‘行键’, ‘列簇名’

count

统计表中行的数量

count ‘表名’

incr

增加指定表行或列的值

incr ‘表名’, ‘行键’, ‘列簇:列名’, 步长值

get_counter

获取计数器

get_counter ‘表名’, ‘行键’, ‘列簇:列名’

delete

删除指定对象的值(可以为表,行,列对应的值,另外也可以指定时间戳的值)

删除列簇的某个列: delete ‘表名’, ‘行键’, ‘列簇名:列名’

deleteall

删除指定行的所有元素值

deleteall ‘表名’, ‘行键’

truncate

重新创建指定表

truncate ‘表名’

enable

使表有效

enable ‘表名’

is_enabled

是否启用

is_enabled ‘表名’

disable

使表无效

disable ‘表名’

is_disabled

是否无效

is_disabled ‘表名’

drop

删除表

drop的表必须是disable的 disable ‘表名’ drop ‘表名’

shutdown

关闭hbase集群(与exit不同)

tools

列出hbase所支持的工具

exit

退出hbase shell

详解

1、general 类

status; 显示集群状态

version; 查询数据库版本和时间等信息

whoami; 显示当前用户及用户组

table_help; 查看操作表的命令

exit; 退出shell客户端

2、DDL数据库定义语言

#创建表

#只创建表和列簇(最简表结构,每一个大括号写一个列簇)
create '表名', {NAME => '列簇1'},{NAME => '列簇2'},... 

#简写(不加大括号和NAME关键字)
create '表名','列簇1','列簇2','列簇3'...

#最完整写法
#TTL秒级,默认forever。BLOCKCHE是否开启缓存,默认true。
create '表名',{NAME => '列簇1',VERSIONS => 版本个数,TTL => 过期时间,BLOCKCHE => bool}, {...}, ...

#修改表(增加/删除列簇和其他,重命名等操作不能执行)

#添加一个列簇
alter '表名','列簇名'
alter 't1','list1'

#删除一个列簇
alter '表名',{NAME => '列簇名',METHOD=>'delete'}
alter 't1',{NAME => 'list1', METHOD => 'delete'}

#修改列簇的属性(可以修改VERSIONS,)
# 修改f1列族的版本为5
alter 't1', NAME => 'f1', VERSIONS => 5

# 修改多个列族,修改f2为内存,版本号为5
alter 't1', 'f1', {NAME => 'f2', IN_MEMORY => true}, {NAME => 'f3', VERSIONS => 5}

# 也可以修改table-scope属性,例如MAX_FILESIZE, READONLY,MEMSTORE_FLUSHSIZE, DEFERRED_LOG_FLUSH等。
# 例如,修改region的最大大小为128MB:
alter 't1', MAX_FILESIZE => '134217728'

#获取表的描述desc/describe
desc '表名'

#列举所有表
list

#判断表是否存在exists
exists '表名'

#启用表和禁用表
#启用表
enable '表名'
#禁用表
disable '表名'
#判断表是否处于可用状态
is_enabled '表名'
#判断表是否处于不可用状态
is_disabled '表名'

#禁用满足正则表达式的所有表
disable_all '表达式'
disable_all 't.*' #禁用所有以t开头的表
#启用满足正则表达式的所有表
enable_all '表达式'

#删除表(先禁用,在删除)
disable '表名'
drop '表名'
#删除满足正则表达式的所有表drop_all
drop_all '表达式'

#获取rowKey所在的区locate_region
locate_region '表名','行键'

#显示hbase所支持的所有过滤器 show_filters

3、namespace

列举所有命名空间
list_namespace

描述namespace
describe_namespace '命名空间'

查看命名空间下的所有表
list_namespace_tables '命名空间'

创建命名空间
create_namespace '命名空间'

删除命名空间
drop_namespace '命名空间'

4、DML数据库操作语言

增/改put
删delete/deleteall/truncate
查get/scan

#自增
# 语法
incr '表名', '行键', '列族:列名', 步长值

# 示例 
# 注意:incr 可以对不存的行键操作,如果行键已经存在会报错,如果使用put修改了incr的值再使用incr也会报错
# ERROR: org.apache.hadoop.hbase.DoNotRetryIOException: Field is not a long, it's 2 bytes wide
incr 'tbl_user', 'xiaohong', 'info:age', 1
#计数器get_counter获取自增列的值(get无法获取具体值)
get '表名', '行键', '列族:列名'

5、其他

#一次增加多列
scan '表名', {COLUMNS => [ '列族名1:列名1', '列族名1:列名2', ...]} ...
scan 'tbl', {COLUMNS => [ 'info:id', 'info:age']}

#TIMESTAMP指定时间戳
scan '表名',{TIMERANGE=>[timestamp1, timestamp2]}
scan 'tbl_user',{TIMERANGE=>[1551938004321, 1551938036450]}

#VERSIONS
#默认情况下一个列只能存储一个历史版本
#创建时指定
create '表名', {NAME=>'列簇', VERSIONS => 个数}
#建表后添加或修改
alter '表名',{NAME=>'列簇', VERSIONS => 个数}

#起始列和戒指列
scan '表名',{STARTROW => '列簇名'};查看指定列簇后的所有列簇数据。按字典排序
scan '表名',{STOPROW => '列簇'};查看指定列簇之前的所有数据

限制查看行数LIMIT
scan '表名',{LIMIT => 行数}

查询结果反转REVERSED
scan 'teacher',{REVERSED => true}

6、过滤器

1、ValueFilter:值过滤器
#查询列等于给定值得数据
scan '表名',FILTER=>"ValueFilter(=,'binary:列值')"
#查询列包含指定值得数据
scan '表名',FILTER=>"ValueFilter(=,'substring:列值')"
#示例
#查询值等于26得数据
scan 'tb1',FILTER=>"ValueFilter(=,'binary:26')" 
#查询值包含6得数据
scan 'tb1',FILTER=>"ValueFilter(=,'substring:6')"
2、ColumnPrefixFilter:列名过滤器
#查询指定前缀得数据,相当于startWiths
scan '表名',FILTER=>"ColumnPrefixFilter('前缀')"

# 示例
scan 'tbl_user', FILTER=>"ColumnPrefixFilter('birth')"
# 通过括号、AND和OR的条件组合多个过滤器
scan 'tbl_user', FILTER=>"ColumnPrefixFilter('birth') AND ValueFilter(=,'substring:26')"

其他略

4、Region相关

刷新数据:flush 'tb'
合并数据:major_compact 'tb'

1、Region信息观察

1.1 查看表的所有region

list_regions '表名'

1.2 强制将表切分出来一个region

split '表名','行键'

但是在页面上可以看到三个:过一会会自动的把原来的删除

1.3 查看某一行在哪个region中

locate_region '表名','行键'

可以hbase hfile -p -f xxxx 查看

2、预分region解决热点问题(面试题)

1.热点现象

         检索habse的记录首先要通过row key来定位数据行,当大量的client访问hbase集群的一个或少数几个节点,

         造成少数region server的读/写请求过多、负载过大,而其他region server负载却很小,就造成了“热点”现象.

        大量访问会使热点region所在的单个主机负载过大,引起性能下降甚至region不可用。

2.产生原因

        有大量连续编号的row key  ==>  大量row key相近的记录集中在个别region ==>  client检索记录时,对个别region访问过多  ==>  此region所在的主机过载  ==>  热点

3、解决方法

        1、预分区

        2、加盐

        3、制定合适的rowKey

预分区详解

        (在建表的时候根据具体的查询业务 设计rowkey 预拆分)

        在默认的拆分策略中 ,region的大小达到一定的阈值以后才会进行拆分,并且拆分的region在同一个regionserver中 ,只有达到负载均衡的时机时才会进行region重分配!并且开始如果有大量的数据进行插入操作,那么并发就会集中在单个RS中, 形成热点问题,所以如果有并发插入的时候尽量避免热点问题 ,应当预划分 Region的rowkeyRange范围 ,在建表的时候就指定预region范围

        查看命令使用(指定4个切割点,就会有5个region)

help 'create'

create 'tb_split','cf',SPLITS => ['e','h','l','r']

list_regions 'tb_split'

添加数据试试

put 'tb_split','c001','cf:name','first'
put 'tb_split','f001','cf:name','second'
put 'tb_split','z001','cf:name','last'

        hbase hfile -p --f xxxx 查看数据

        如果没有数据,因为数据还在内存中,需要手动刷新内存到HDFS中,以HFile的形式存储

3、日志查看

演示不启动hdfs 就启动hbase

日志目录:
/usr/local/soft/hbase-1.7.1/logs

start-all.sh发现HMaster没启动,hbase shell客户端也可以正常访问再启动hbase就好了

5、命令进阶

1、scan进阶使用

查看所有的命名空间

list_namespace

查看某个命名空间下的所有表

list_namespace_tables 'default'

修改命名空间,设置一个属性

alter_namespace 'bigdata25',{METHOD=>'set','author'=>'wyh'}

查看命名空间属性

describe_namespace 'bigdata17'

删除一个属性

alter_namespace 'bigdata17',{METHOD=>'unset', NAME=>'author'}

删除一个命名空间

drop_namespace 'bigdata17'

创建一张表

create 'teacher','cf'

添加数据

put 'teacher','tid0001','cf:tid',1
put 'teacher','tid0002','cf:tid',2
put 'teacher','tid0003','cf:tid',3
put 'teacher','tid0004','cf:tid',4
put 'teacher','tid0005','cf:tid',5
put 'teacher','tid0006','cf:tid',6

显示三行数据

scan 'teacher',{LIMIT=>3}

从后查三行

scan 'teacher',{LIMIT=>3,REVERSED=>true}

查看包含指定列的行

scan 'teacher',{LIMIT=>3,COLUMNS=>['cf:name']}

简化写法:

scan 'teacher',LIMIT=>3,COLUMNS=>['cf:name']

在已有的值后面追加值

append 'teacher','tid00001','cf:name','123'

2、get进阶使用

简单使用,获取某一行数据

get 'teacher','tid0001'

获取某一行的某个列簇

get 'teacher','tid0001','cf'

获取某一行的某一列(属性 )

get 'teacher','tid0001','cf:name'

可以新增一个列簇数据测试

查看历史版本1、修改表可以存储多个版本

alter 'teacher',NAME=>'cf',VERSIONS=>3

2、put四次相同rowkey和列的数据

put 'teacher','tid0001','cf:name','xiaohu1' 
put 'teacher','tid0001','cf:name','xiaohu2' 
put 'teacher','tid0001','cf:name','xiaohu3' 
put 'teacher','tid0001','cf:name','xiaohu4'

3、查看历史数据,默认是最新的

get 'teacher','tid0001',COLUMN=>'cf:name',VERSIONS=>2

修改列簇的过期时间 TTL单位是秒,这个时间是与插入的时间比较,而不是现在开始60s

alter 'teacher',{NAME=>'cf2',TTL=>'60'}

3、插入时间指定时间戳

put 'teacher','tid0007','cf2:job','bigdata25',1693644893843

6、HBase读写流程

1、读流程

Hbase读取数据的流程:
1)是由客户端发起读取数据的请求,首先会与zookeeper建立连接
2)从zookeeper中获取一个hbase:meta表位置信息,被哪一个regionserver所管理着
     hbase:meta表:hbase的元数据表,在这个表中存储了自定义表相关的元数据,包括表名,表有哪些列簇,表有哪些region,每个region存储的位置,每个region被哪个regionserver所管理,这个表也是存储在某一个region上的,并且这个meta表只会被一个regionserver所管理。这个表的位置信息只有zookeeper知道。
3)连接这个meta表对应的regionserver,从meta表中获取当前你要读取的这个表对应的regionsever是谁。
     当一个表多个region怎么办呢?
     如果我们获取数据是以get的方式,只会返回一个regionserver
     如果我们获取数据是以scan的方式,会将所有的region对应的regionserver的地址全部返回。
4)连接要读取表的对应的regionserver,从regionserver上的开始读取数据:
       读取顺序:memstore-->blockcache-->storefile-->Hfile中
       注意:如果是scan操作,就不仅仅去blockcache了,而是所有都会去找。

2、写流程

--------------------------1-4步是客户端写入数据的流程-----------------

Hbase的写入数据流程:
1)由客户端发起写数据请求,首先会与zookeeper建立连接
2)从zookeeper中获取hbase:meta表被哪一个regionserver所管理
3)连接hbase:meta表中获取对应的regionserver地址 (从meta表中获取当前要写入数据的表对应的region所管理的regionserver) 只会返回一个regionserver地址
4)与要写入数据的regionserver建立连接,然后开始写入数据,将数据首先会写入到HLog,然后将数据写入到对应store模块中的memstore中
(可能会写多个),当这两个地方都写入完成之后,表示数据写入完成。

-------------------------后面的步骤是服务器内部的操作-----------------
异步操作
5)随着客户端不断地写入数据,memstore中的数据会越来多,当内存中的数据达到阈值(128M/1h)的时候,放入到blockchache中,生成新的memstore接收用户过来的数据,然后当blockcache的大小达到一定阈值(0.85)的时候,开始触发flush机制,将数据最终刷新到HDFS中形成小的Hfile文件。
​
6)随着不断地刷新,storefile不断地在HDFS上生成小HFIle文件,当小的HFile文件达到阈值的时候(3个及3个以上),就会触发Compaction机制,将小的HFile合并成一个大的HFile.
​
7)随着不断地合并,大的HFile文件会越来越大,当达到一定阈值(2.0版本之后最终10G)的时候,会触发分裂机制(split),将大的HFile文件进行一分为二,同时管理这个大的HFile的region也会被一分为二,形成两个新的region和两个新的HFile文件,一对一的进行管理,将原来旧的region和分裂之前大的HFile文件慢慢地就会下线处理。

3、面对百亿数据,HBase为什么查询速度依然非常快?(面试题)

roeKey范围 => region => 列簇 => HFile => key => value

10T => 2G => 500M => 250M => 10M => 0.1秒内找到

        HBase适合存储PB级别的海量数据(百亿千亿量级条记录),如果根据记录主键Rowkey来查询,能在几十到百毫秒内返回数据。

        那么HBase是如何做到的呢?

        接下来,简单阐述一下数据的查询思路和过程。

查询过程

第1步:

        项目有100亿业务数据,存储在一个HBase集群上(由多个服务器数据节点构成),每个数据节点上有若干个Region(区域),每个Region实际上就是HBase中一批数据的集合(一段连续范围rowkey的数据)。

        我们现在开始根据主键RowKey来查询对应的记录,通过meta表可以帮我们迅速定位到该记录所在的数据节点,以及数据节点中的Region,目前我们有100亿条记录,占空间10TB。所有记录被切分成5000个Region,那么现在,每个Region就是2G。

        由于记录在1个Region中,所以现在我们只要查询这2G的记录文件,就能找到对应记录。

第2步:

        由于HBase存储数据是按照列族存储的。比如一条记录有400个字段,前100个字段是人员信息相关,这是一个列簇(列的集合);中间100个字段是公司信息相关,是一个列簇。另外100个字段是人员交易信息相关,也是一个列簇;最后还有100个字段是其他信息,也是一个列簇

        这四个列簇是分开存储的,这时,假设2G的Region文件中,分为4个列族,那么每个列族就是500M。

        到这里,我们只需要遍历这500M的列簇就可以找到对应的记录。

第3步:

        如果要查询的记录在其中1个列族上,1个列族在HDFS中会包含1个或者多个HFile。

        如果一个HFile一般的大小为100M,那么该列族包含5个HFile在磁盘上或内存中。

        由于HBase的内存进入磁盘中的数据是排好序(字典顺序)的,要查询的记录有可能在最前面,也有可能在最后面,按平均来算,我们只需遍历2.5个HFile共250M,即可找到对应的记录。

第4步:

        每个HFile中,是以键值对(key/value)方式存储,只要遍历文件中的key位置并判断符合条件即可

        一般key是有限的长度,假设key/value比是1:24,最终只需要10M的数据量,就可获取的对应的记录。

        如果数据在机械磁盘上,按其访问速度100M/S,只需0.1秒即可查到。

        如果是SSD的话,0.01秒即可查到。

        当然,扫描HFile时还可以通过布隆过滤器快速定位到对应的HFile,以及HBase是有内存缓存机制的,如果数据在内存中,效率会更高

总结

        正因为以上大致的查询思路,保证了HBase即使随着数据量的剧增,也不会导致查询性能的下降。

        同时,HBase是一个面向列存储的数据库(列簇机制),当表字段非常多时,可以把其中一些字段独立出来放在一部分机器上,而另外一些字段放到另一部分机器上,分散存储,分散列查询。

        正由于这样复杂的存储结构和分布式的存储方式,保证了HBase海量数据下的查询效率。

7、JavaAPI操作HBase

1、导入依赖

<dependencies>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

<dependency>
    <groupId>org.apache.hbase</groupId>
    <artifactId>hbase-client</artifactId>
    <version>2.2.7</version>
</dependency>

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>3.1.1</version>
</dependency>

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>3.1.1</version>
</dependency>
</dependencies>

2、JavaAPI实现增删改查

1、对命名空间操作

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
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 NameSpaceTest {
    private Connection connection = null;
    private Admin admin = null;

    @Before
    public void initTest() throws IOException {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "master:2181,node1:2181,node2:2181");
        connection = ConnectionFactory.createConnection(conf);
        admin = connection.getAdmin();
    }

    //创建命名空间
    @Test
    public void createNameSpace() throws IOException {
        NamespaceDescriptor.Builder builder = NamespaceDescriptor.create("test");
        admin.createNamespace(builder.build());
    }

    //删除命名空间,需要先删除所有表才能删除命名空间
    @Test
    public void dropNameSpace() throws IOException {
        //获取命名空间得所有的表
        TableName[] javaAPIS = admin.listTableNamesByNamespace("javaAPI");
        for (TableName javaAPI : javaAPIS) {
            System.out.println(Bytes.toString(javaAPI.getName()));
            //禁用表
            admin.disableTable(javaAPI);
            //删除表
            admin.deleteTable(javaAPI);
        }
        //删除命名空间
        admin.deleteNamespace("javaAPI");
    }

    //查看所有命名空间
    @Test
    public void listNameSpace() throws IOException {
        NamespaceDescriptor[] listNamespaces = admin.listNamespaceDescriptors();
        for (NamespaceDescriptor listNamespace : listNamespaces) {
            String name = listNamespace.getName();
            System.out.println(name);
        }
    }

    //描述namespace
    @Test
    public void descNameSpace() throws IOException {
        NamespaceDescriptor test = admin.getNamespaceDescriptor("test");
        System.out.println(test.toString());
    }

    @After
    public void closeTest() throws IOException {
        admin.close();
        connection.close();
    }
}

2、对表结构操作

import org.apache.hadoop.conf.Configuration;
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.Arrays;

public class TableTest {
    private Connection connection;
    private Admin admin;

    @Before
    public void initTest() throws IOException {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "master:2181,node1:2181,node2:2181");
        connection = ConnectionFactory.createConnection(conf);
        admin = connection.getAdmin();
    }

    //创建表
    @Test
    public void createTable() throws IOException {
        //创建TableName对象
        TableName test1 = TableName.valueOf("test:test1");
        //使用TableDescriptorBuilder创建TableDescriptor
        TableDescriptorBuilder newBuilder = TableDescriptorBuilder.newBuilder(test1);
        //使用ColumnFamilyDescriptorBuilder创建列簇ColumnFamilyDescriptor信息
        ColumnFamilyDescriptorBuilder column1 = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("column1"));
        //给表添加列簇信息
        TableDescriptorBuilder tableDescriptorBuilder = newBuilder.setColumnFamily(column1.build());
        //利用方法创建表,需要TableDescriptor类型得函数
        admin.createTable(tableDescriptorBuilder.build());
    }

    //删除表。先禁用、在删除
    @Test
    public void dropTable() throws IOException {
        TableName test1 = TableName.valueOf("test1");
        admin.disableTable(test1);//禁用表
        admin.deleteTable(test1);
    }

    //修改表
    @Test
    public void changeTable() throws IOException {
        //添加一个列簇
        TableName tableName = TableName.valueOf("test:test1");
//        ColumnFamilyDescriptor test2 = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("column2")).build();
//        admin.addColumnFamily(tableName,test2);

        //删除一个列簇
//        admin.deleteColumnFamily(tableName,Bytes.toBytes("test2"));

        //修改列簇得属性
        ColumnFamilyDescriptorBuilder column2 = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("column2"));
        column2.setMaxVersions(5);//设置最大版本数5个
        column2.setTimeToLive(60 * 60 * 24 * 60);//设置列簇存活时间,秒级
        ColumnFamilyDescriptor build = column2.build();
        admin.modifyColumnFamily(tableName, build);
    }

    //获取表信息,(查)
    @Test
    public void descTable() throws IOException {
        TableName tableName = TableName.valueOf("test:test1");
        Table table = connection.getTable(tableName);
        TableDescriptor descriptor = table.getDescriptor();
        System.out.println(descriptor.toString());
    }

    //获取所有表
    @Test
    public void listTable() throws IOException {
        TableName[] tables = admin.listTableNames();
        System.out.println(Arrays.toString(tables));

        //获取命名空间下的所有表
        TableName[] tableByNamespaces = admin.listTableNamesByNamespace("test");
        System.out.println(Arrays.toString(tableByNamespaces));
    }

    //判断表是否存在
    @Test
    public void isExists() throws IOException {
        TableName table1 = TableName.valueOf("test:test1");
        TableName table2 = TableName.valueOf("test:test2");
        boolean t1 = admin.tableExists(table1);
        boolean t2 = admin.tableExists(table2);
        System.out.println("test:test1 is exitst => " + t1);
        System.out.println("test:test2 is exitst => " + t2);
    }

    @After
    public void cloceTest() throws IOException {
        admin.close();
        connection.close();
    }
}

3、对数据进行操作

import org.apache.hadoop.conf.Configuration;
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.Arrays;
import java.util.Collections;

public class DMLTest {
    private Table table;
    private Connection connection;

    @Before
    public void initTest() throws IOException {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "master:2181,node1:2181,node2:2181");
        connection = ConnectionFactory.createConnection(conf);
        table = connection.getTable(TableName.valueOf("test:test1"));
    }

    //添加/修改数据
    @Test
    public void insetData() throws IOException {
        //添加一条数据
//        Put put = new Put(Bytes.toBytes("1001"));
//        put.addColumn(Bytes.toBytes("column1"), Bytes.toBytes("name"), Bytes.toBytes("alice"));
//        table.put(put);

        //添加多条数据
        ArrayList<Put> puts = new ArrayList<>();
        Put data1 = new Put(Bytes.toBytes("1001"));
        data1.addColumn(Bytes.toBytes("column2"), Bytes.toBytes("name"), Bytes.toBytes("alice"));
        Put data2 = new Put(Bytes.toBytes("1001"));
        data2.addColumn(Bytes.toBytes("column2"), Bytes.toBytes("age"), Bytes.toBytes("18"));
        Put data3 = new Put(Bytes.toBytes("1001"));
        data3.addColumn(Bytes.toBytes("column2"), Bytes.toBytes("sex"), Bytes.toBytes("nan"));
        Put data4 = new Put(Bytes.toBytes("1001"));
        data4.addColumn(Bytes.toBytes("column2"), Bytes.toBytes("phone"), Bytes.toBytes("110119"));
        Collections.addAll(puts, data1, data2, data3, data4);
        table.put(puts);
    }

    //删除数据
    @Test
    public void deleteData() throws IOException {
        //删除一条数据
//        Delete delete = new Delete(Bytes.toBytes("1001"));
//        delete.addColumn(Bytes.toBytes("column2"),Bytes.toBytes("phone"));
//        table.delete(delete);

        //删除多条数据
        ArrayList<Delete> deletes = new ArrayList<>();
        Delete delete1 = new Delete(Bytes.toBytes("1001"));
        delete1.addColumn(Bytes.toBytes("column2"), Bytes.toBytes("age"));
        Delete delete2 = new Delete(Bytes.toBytes("1001"));
        delete2.addColumn(Bytes.toBytes("column2"), Bytes.toBytes("sex"));
        deletes.add(delete1);
        deletes.add(delete2);
        table.delete(deletes);
    }

    //查询数据
    @Test
    public void getAndScan() throws IOException {
        //get查询
//        Get get = new Get(Bytes.toBytes("1001"));
//        get.addFamily(Bytes.toBytes("column1"));
//        Result result = table.get(get);
//        Cell[] cells = result.rawCells();
//        for (Cell cell : cells) {
//            byte[] bytes = CellUtil.cloneQualifier(cell);
//            byte[] bytes1 = CellUtil.cloneValue(cell);
//            System.out.println(Bytes.toString(bytes) + ": " + Bytes.toString(bytes1));
//        }

        //scan查询
        Scan scan = new Scan();
        scan.setId("1001");
        ResultScanner scanner = table.getScanner(scan);
        Result next = null;
        while ((next = scanner.next()) != null) {
            Cell[] cells = next.rawCells();
            for (Cell cell : cells) {
                byte[] bytes = CellUtil.cloneQualifier(cell);
                byte[] bytes1 = CellUtil.cloneValue(cell);
                System.out.println(Bytes.toString(bytes) + ": " + Bytes.toString(bytes1));
            }
        }
    }

    @After
    public void closeTest() throws IOException {
        table.close();
        connection.close();
    }
}

3、过滤器详解

        HBase 的基本 API,包括增、删、改、查等。增、删都是相对简单的操作,与传统的 RDBMS 相比,这里的查询操作略显苍白,只能根据特性的行键进行查询(Get)或者根据行键的范围来查询(Scan)。HBase 不仅提供了这些简单的查询,而且提供了更加高级的过滤器(Filter)来查询。

        过滤器可以根据列族、列、版本等更多的条件来对数据进行过滤,

        基于 HBase 本身提供的三维有序(行键,列,版本有序),这些过滤器可以高效地完成查询过滤的任务,带有过滤器条件的 RPC 查询请求会把过滤器分发到各个 RegionServer(这是一个服务端过滤器),这样也可以降低网络传输的压力。

使用过滤器至少需要两类参数:

        一类是抽象的操作符,另一类是比较器

作用

  • 过滤器的作用是在服务端判断数据是否满足条件,然后只将满足条件的数据返回给客户端
  • 过滤器的类型很多,但是可以分为三大类:
    • 比较过滤器:可应用于rowkey、列簇、列、列值过滤器
    • 专用过滤器:只能适用于特定的过滤器
    • 包装过滤器:包装过滤器就是通过包装其他过滤器以实现某些拓展的功能。

比较过滤器

        所有比较过滤器均继承自 CompareFilter。创建一个比较过滤器需要两个参数,分别是比较运算符和比较器实例。

 public CompareFilter(final CompareOp compareOp,final ByteArrayComparable comparator) {
    this.compareOp = compareOp;
    this.comparator = comparator;
  }

比较运算符

  • LESS
  • LESS_OR_EQUAL
  • EQUAL =
  • NOT_EQUAL <>
  • GREATER_OR_EQUAL >=
  • GREATER >
  • NO_OP 排除所有

常见的六大比较器

        BinaryComparator (常用)

按字节索引顺序比较指定字节数组,采用Bytes.compareTo(byte[])

        BinaryPrefixComparator (常用)

通BinaryComparator,只是比较左端前缀的数据是否相同

        NullComparator

判断给定的是否为空

        BitComparator

按位比较

        RegexStringComparator

提供一个正则的比较器,仅支持 EQUAL 和非EQUAL

        SubstringComparator (常用)

判断提供的子串是否出现在中

可对行键、列簇、列名、值进行过滤。分为:

  1. rowKey过滤器:RowFilter 行键过滤器
  2. 列簇过滤器:FamilyFilter
  3. 列名过滤器:QualifierFilter
  4. 列值过滤器:ValueFilter

比较过滤器代码演示

过滤器 ( 运算符, 比较器 );

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.*;
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.Arrays;

public class FilterTest {
    private Connection connection;
    private Table table;

    @Before
    public void initTest() throws IOException {
        Configuration conf = new Configuration();
        conf.set("hbase.zookeeper.quorum", "master:2181,node1:2181,node2:2181");
        connection = ConnectionFactory.createConnection(conf);
        table = connection.getTable(TableName.valueOf("students"));
    }

    //rowKey过滤器:RowFilter  行键过滤器
    //通过RowFilter与BinaryComparator过滤比rowKey 1500100010小的所有值出来
    @Test
    public void rowFilterTest() throws IOException {
        Scan scan = new Scan();

        BinaryComparator binaryComparator = new BinaryComparator(Bytes.toBytes("1500100010"));
        RowFilter rowFilter = new RowFilter(CompareOperator.LESS, binaryComparator);

        scan.setFilter(rowFilter);
        ResultScanner scanner = table.getScanner(scan);
        Result next = null;
        while ((next = scanner.next()) != null) {
            StringBuilder sb = new StringBuilder();
            byte[] id = next.getRow();
            sb.append(Bytes.toString(id));
            Cell[] cells = next.rawCells();
            for (Cell cell : cells) {
                byte[] column = CellUtil.cloneQualifier(cell);
                byte[] value = CellUtil.cloneValue(cell);
                sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
            }
            System.out.println(sb);
        }
    }

    //列簇过滤器:FamilyFilter
    // 通过FamilyFilter与SubstringComparator查询列簇名包含in的所有列簇下面的数据
    @Test
    public void FamilyFilterTest() throws IOException {
        Scan scan = new Scan();

//        SubstringComparator in = new SubstringComparator("in");
        BinaryPrefixComparator i = new BinaryPrefixComparator(Bytes.toBytes("sex"));

//        FamilyFilter familyFilter = new FamilyFilter(CompareOperator.EQUAL, i);
//        scan.setFilter(familyFilter);
        QualifierFilter qualifierFilter = new QualifierFilter(CompareOperator.EQUAL, i);
        scan.setFilter(qualifierFilter);
        ResultScanner scanner = table.getScanner(scan);

        Result next = null;
        while ((next = scanner.next()) != null) {
            StringBuilder sb = new StringBuilder();
            byte[] id = next.getRow();
            sb.append(Bytes.toString(id));
            Cell[] cells = next.rawCells();
            for (Cell cell : cells) {
                byte[] column = CellUtil.cloneQualifier(cell);
                byte[] value = CellUtil.cloneValue(cell);
                sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
            }
            System.out.println(sb);
        }
    }

    @After
    public void closeTest() throws IOException {
        table.close();
        connection.close();
    }
}

专用过滤器

1、单列值过滤器:SingleColumnValueFilter (最常用)

        SingleColumnValueFilter会返回满足条件的cell所在行的所有cell的值(即会返回一行数据)

2、列值排除过滤器:SingleColumnValueExcludeFilter

        与SingleColumnValueFilter相反,会排除掉指定的列,其他的列全部返回

3、rowkey前缀过滤器:PrefixFilter

4、分页过滤器PageFilter

        使用PageFilter分页效率比较低,每次都需要扫描前面的数据,直到扫描到所需要查的数据

        可设计一个合理的rowkey来实现分页需求

代码示例:省去了初始化和关闭资源操作

    //单列值过滤器:SingleColumnValueFilter
//    SingleColumnValueFilter会返回满足条件的cell所在行的所有cell的值(即会返回一行数据)
//    通过SingleColumnValueFilter与查询文科班所有学生信息
    @Test
    public void singleFilterTest() throws IOException {
        Scan scan = new Scan();

        BinaryPrefixComparator prefixComparator = new BinaryPrefixComparator(Bytes.toBytes("文科"));
        SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(Bytes.toBytes("info"), Bytes.toBytes("clazz"), CompareOperator.EQUAL, prefixComparator);
        scan.setFilter(singleColumnValueFilter);

        ResultScanner scanner = table.getScanner(scan);
        Result next = null;
        while ((next = scanner.next()) != null) {
            StringBuilder sb = new StringBuilder();
            byte[] id = next.getRow();
            sb.append(Bytes.toString(id));
            Cell[] cells = next.rawCells();
            for (Cell cell : cells) {
                byte[] column = CellUtil.cloneQualifier(cell);
                byte[] value = CellUtil.cloneValue(cell);
                sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
            }
            System.out.println(sb);
        }
    }
    
    //列值排除过滤器:SingleColumnValueExcludeFilter
//    与SingleColumnValueFilter相反,会排除掉指定的列,其他的列全部返回
//    查询文科班所有学生信息,最终返回除clazz列的其他所有列
    @Test
    public void singleFilterExcludeTest() throws IOException {
        Scan scan = new Scan();

        BinaryPrefixComparator prefixComparator = new BinaryPrefixComparator(Bytes.toBytes("文科"));
        SingleColumnValueFilter singleColumnValueExcludeFilter = new SingleColumnValueExcludeFilter(Bytes.toBytes("info"), Bytes.toBytes("clazz"), CompareOperator.EQUAL, prefixComparator);
        scan.setFilter(singleColumnValueExcludeFilter);

        ResultScanner scanner = table.getScanner(scan);
        Result next = null;
        while ((next = scanner.next()) != null) {
            StringBuilder sb = new StringBuilder();
            byte[] id = next.getRow();
            sb.append(Bytes.toString(id));
            Cell[] cells = next.rawCells();
            for (Cell cell : cells) {
                byte[] column = CellUtil.cloneQualifier(cell);
                byte[] value = CellUtil.cloneValue(cell);
                sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
            }
            System.out.println(sb);
        }
    }

    //rowkey前缀过滤器:PrefixFilter
    //通过PrefixFilter查询以15001001开头的所有前缀的rowkey
    @Test
    public void prefixFilterTest() throws IOException {
        Scan scan = new Scan();

        PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes("150010005"));
        scan.setFilter(prefixFilter);
        ResultScanner scanner = table.getScanner(scan);
        Result next = null;
        while ((next = scanner.next()) != null) {
            StringBuilder sb = new StringBuilder();
            byte[] id = next.getRow();
            sb.append(Bytes.toString(id));
            Cell[] cells = next.rawCells();
            for (Cell cell : cells) {
                byte[] column = CellUtil.cloneQualifier(cell);
                byte[] value = CellUtil.cloneValue(cell);
                sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
            }
            System.out.println(sb);
        }
    }

    //分页过滤器PageFilter
    //使用PageFilter分页效率比较低,每次都需要扫描前面的数据,直到扫描到所需要查的数据
    //可设计一个合理的rowkey来实现分页需求
    @Test
    public void pageFilterText() throws IOException {
        Scan scan = new Scan();

        scan.withStartRow(Bytes.toBytes("1500100050"));
        PageFilter pageFilter = new PageFilter(10);

        scan.setFilter(pageFilter);

        ResultScanner scanner = table.getScanner(scan);

        Result next = null;
        while ((next = scanner.next()) != null) {
            StringBuilder sb = new StringBuilder();
            byte[] id = next.getRow();
            sb.append(Bytes.toString(id));
            Cell[] cells = next.rawCells();
            for (Cell cell : cells) {
                byte[] column = CellUtil.cloneQualifier(cell);
                byte[] value = CellUtil.cloneValue(cell);
                sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
            }
            System.out.println(sb);
        }
    }
        /**
     * 分页过滤器 需求:前30行分3页出来
     * 通过给每一页的最后一行的行键加0,并把这个值重新传定义每页第一行的方法,作为下一页的开始行。达到下一页的目的。
     * 具体代码在138,139两行。
     */
    @Test
    public void pageFilterFun() {
        try {
            //将表名封装成TableName的对象
            TableName students2 = TableName.valueOf("students2");
            //获取表的实例对象
            Table table = conn.getTable(students2);
            //定义你要查询的页数
            int pageNum = 3;
            //定义每一页的行数
            int pageSize = 10;
            //定义开始行数的变量
            String currentPageStartRow = "";
            //创建一个Scan对象
            Scan scan = new Scan();
            
            for (int i = 1; i <= pageNum; i++) {
                System.out.println("================================当前是第"+i+"页=====================================");
                //创建分页过滤器
                PageFilter pageFilter = new PageFilter(pageSize);
                scan.setFilter(pageFilter);
                ResultScanner resultScanner = table.getScanner(scan);
//                printResultScanner(resultScanner);
                //获取到每一行
                for (Result result : resultScanner) {
                    //获取当前行的rowkey+0作为下一页的开始行(最关键点)
                    currentPageStartRow = Bytes.toString(result.getRow())+0;
                    scan.withStartRow(Bytes.toBytes(currentPageStartRow));
                    //学号
                    String id = Bytes.toString(result.getRow());
                    String name = Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("姓名")));
                    String age = Bytes.toString(result.getValue(Bytes.toBytes("info"), Bytes.toBytes("年龄")));
                    String gender = Bytes.toString(result.getValue(Bytes.toBytes("shujia"), Bytes.toBytes("性别")));
                    String clazz = Bytes.toString(result.getValue(Bytes.toBytes("shujia"), Bytes.toBytes("班级")));
                    System.out.println("学号:"+id+", 姓名:"+name+", 年龄:"+age+", 性别:"+gender+", 班级"+clazz);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

包装过滤器

1、SkipFilter过滤器

        SkipFilter包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,则拓展过滤整行数据。

2、WhileMatchFilter过滤器

        WhileMatchFilter 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,WhileMatchFilter 则结束本次扫描,返回已经扫描到的结果。

//SkipFilter过滤器
//SkipFilter包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时,
// 则拓展过滤整行数据。下面是一个使用示例:
@Test
public void skipFilterTest() throws IOException {
    Scan scan = new Scan();

    BinaryComparator binaryComparator = new BinaryComparator(Bytes.toBytes("男"));

    ValueFilter valueFilter = new ValueFilter(CompareOperator.NOT_EQUAL, binaryComparator);

    SkipFilter skipFilter = new SkipFilter(valueFilter);
    scan.setFilter(skipFilter);

    ResultScanner scanner = table.getScanner(scan);

    Result next = null;
    while ((next = scanner.next()) != null) {
        StringBuilder sb = new StringBuilder();
        byte[] id = next.getRow();
        sb.append(Bytes.toString(id));
        Cell[] cells = next.rawCells();
        for (Cell cell : cells) {
            byte[] column = CellUtil.cloneQualifier(cell);
            byte[] value = CellUtil.cloneValue(cell);
            sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
        }
        System.out.println(sb);
    }
}

//WhileMatchFilter过滤器
//WhileMatchFilter 包装一个过滤器,当被包装的过滤器遇到一个需要过滤的 KeyValue 实例时
// WhileMatchFilter 则结束本次扫描,返回已经扫描到的结果。下面是其使用示例:
@Test
public void whileMatchFilterTest() throws IOException {
    Scan scan = new Scan();

    BinaryComparator binaryComparator = new BinaryComparator(Bytes.toBytes("文科一班"));

    ValueFilter valueFilter = new ValueFilter(CompareOperator.NOT_EQUAL, binaryComparator);

    WhileMatchFilter whileMatchFilter = new WhileMatchFilter(valueFilter);
    scan.setFilter(whileMatchFilter);

    ResultScanner scanner = table.getScanner(scan);

    Result next = null;
    while ((next = scanner.next()) != null) {
        StringBuilder sb = new StringBuilder();
        byte[] id = next.getRow();
        sb.append(Bytes.toString(id));
        Cell[] cells = next.rawCells();
        for (Cell cell : cells) {
            byte[] column = CellUtil.cloneQualifier(cell);
            byte[] value = CellUtil.cloneValue(cell);
            sb.append(",").append(Bytes.toString(column)).append(":").append(Bytes.toString(value));
        }
        System.out.println(sb);
    }
}

多个过滤器结合查询

        以上都是讲解单个过滤器的作用,当需要多个过滤器共同作用于一次查询的时候,就需要使用 FilterList。FilterList 支持通过构造器或者 addFilter 方法传入多个过滤器。

//过滤器1
SingleColumnValueFilter filter1 = new SingleColumnValueFilter(
        Bytes.toBytes("info"),
        Bytes.toBytes("姓名"),
        CompareOperator.EQUAL,
        new BinaryPrefixComparator(Bytes.toBytes("于"))
);
//过滤器2
SingleColumnValueFilter filter2 = new SingleColumnValueFilter(
        Bytes.toBytes("info"),
        Bytes.toBytes("年龄"),
        CompareOperator.LESS_OR_EQUAL,
        new BinaryComparator(Bytes.toBytes("23"))
);
//过滤器3
SingleColumnValueFilter filter3 = new SingleColumnValueFilter(
        Bytes.toBytes("shujia"),
        Bytes.toBytes("性别"),
        CompareOperator.EQUAL,
        new SubstringComparator("女")
);
//过滤器4
SingleColumnValueFilter filter4 = new SingleColumnValueFilter(
        Bytes.toBytes("shujia"),
        Bytes.toBytes("班级"),
        CompareOperator.EQUAL,
        new RegexStringComparator("^理科")
);

//创建一个过滤器列表(最关键)
FilterList filterList = new FilterList();

//将每一个过滤器添加到列表中(最关键)
filterList.addFilter(filter1);
filterList.addFilter(filter2);
filterList.addFilter(filter3);
filterList.addFilter(filter4);

//将这个过滤器集合添加到scan中即可
 scan.setFilter(filterList);

布隆过滤器

        本质上布隆过滤器是一种数据结构,比较巧妙的概率型数据结构,特点是高效地插入和查询,可以用来告诉你 “某样东西一定不存在或者可能存在”。

        相比于传统的 List、Set、Map 等数据结构,它更高效、占用空间更少,但是缺点是其返回的结果是概率性的,而不是确切的。

        实际上,布隆过滤器广泛应用于网页黑名单系统、垃圾邮件过滤系统、爬虫网址判重系统等,Google 著名的分布式数据库 Bigtable 使用了布隆过滤器来查找不存在的行或列,以减少磁盘查找的 IO 次数,Google Chrome 浏览器使用了布隆过滤器加速安全浏览服务。

        在很多 Key-Value 系统中也使用了布隆过滤器来加快查询过程,如 Hbase,Accumulo,Leveldb,一般而言,Value 保存在磁盘中,访问磁盘需要花费大量时间,然而使用布隆过滤器可以快速判断某个 Key 对应的 Value 是否存在,因此可以避免很多不必要的磁盘 IO 操作。

        通过一个 Hash 函数将一个元素映射成一个位阵列(Bit Array)中的一个点。这样一来,我们只要看看这个点是不是 1 就知道可以集合中有没有它了。这就是布隆过滤器的基本思想。

运用场景

1、目前有 10 亿数量的自然数,乱序排列,需要对其排序。限制条件在 32 位机器上面完成,内存限制为 2G。如何完成?

2、如何快速在亿级黑名单中快速定位 URL 地址是否在黑名单中?(每条 URL 平均 64 字节)

3、需要进行用户登陆行为分析,来确定用户的活跃情况?

4、网络爬虫-如何判断 URL 是否被爬过?

5、快速定位用户属性(黑名单、白名单等)?

6、数据存储在磁盘中,如何避免大量的无效 IO?

7、判断一个元素在亿级数据中是否存在?

8、缓存穿透。

实现原理

        假设我们有个集合 A,A 中有 n 个元素。利用k个哈希散列函数,将A中的每个元素映射到一个长度为 a 位的数组 B中的不同位置上,这些位置上的二进制数均设置为 1。如果待检查的元素,经过这 k个哈希散列函数的映射后,发现其 k 个位置上的二进制数全部为 1,这个元素很可能属于集合A,反之,一定不属于集合A。

        比如我们有 3 个 URL {URL1,URL2,URL3},通过一个hash 函数把它们映射到一个长度为 16 的数组上,如下:

若当前哈希函数为 Hash1(),通过哈希运算映射到数组中,假设Hash1(URL1) = 4,Hash1(URL2) = 6,Hash1(URL3) = 6,如下:

        因此,如果我们需要判断URL1是否在这个集合中,则通过Hash(urL1)计算出其下标,并得到其值若为 1 则说明存在。

        由于 Hash 存在哈希冲突,如上面URL2,URL3都定位到一个位置上,假设 Hash 函数是良好的,如果我们的数组长度为 m 个点,那么如果我们想将冲突率降低到例如 1%, 这个散列表就只能容纳 m/100 个元素,显然空间利用率就变低了,也就是没法做到空间有效(space-efficient)。

        解决方法也简单,就是使用多个 Hash 算法,如果它们有一个说元素不在集合中,那肯定就不在,如下:

Hash1(URL1) = 3,Hash2(URL1) = 5,Hash3(URL1) = 6
Hash1(URL2) = 5,Hash2(URL2) = 7,Hash3(URL2) = 13
Hash1(URL3) = 4,Hash2(URL3) = 7,Hash3(URL3) = 10

        以上就是布隆过滤器做法,使用了k个哈希函数,每个字符串跟 k 个 bit 对应,从而降低了冲突的概率。

误判现象

        上面的做法同样存在问题,因为随着增加的值越来越多,被置为 1 的 bit 位也会越来越多,这样某个值即使没有被存储过,但是万一哈希函数返回的三个 bit 位都被其他值置位了 1 ,那么程序还是会判断这个值存在。比如此时来一个不存在的 URL1000,经过哈希计算后,发现 bit 位为下:

Hash1(URL1000) = 7,Hash2(URL1000) = 8,Hash3(URL1000) = 14

        但是上面这些 bit 位已经被URL1,URL2,URL3置为 1 了,此时程序就会判断 URL1000 值存在。

        这就是布隆过滤器的误判现象,所以,布隆过滤器判断存在的不一定存在,但是,判断不存在的一定不存在。

        布隆过滤器可精确的代表一个集合,可精确判断某一元素是否在此集合中,精确程度由用户的具体设计决定,达到 100% 的正确是不可能的。但是布隆过滤器的优势在于,利用很少的空间可以达到较高的精确率。

控制粒度

a)ROW
    根据KeyValue中的行来过滤storefile 
    举例:假设有2个storefile文件sf1和sf2, 
        sf1包含kv1(r1 cf:q1 v),kv2(r2 cf:q1 v) 
        sf2包含kv3(r3 cf:q1 v),kv4(r4 cf:q1 v) 
        若是设置了CF属性中的bloomfilter为ROW,那么得(r1)时就会过滤sf2,get(r3)就会过滤sf1 
b)ROWCOL
    根据KeyValue中的行+限定符来过滤storefile
    举例:假设有2个storefile文件sf1和sf2, 
        sf1包含kv1(r1 cf:q1 v),kv2(r2 cf:q1 v) 
        sf2包含kv3(r1 cf:q2 v),kv4(r2 cf:q2 v) 
        若是设置了CF属性中的布隆过滤器为ROW,不管获得(R1,Q1)仍是获得(R1,Q2),都会读取SF1 + SF2;而若是设置了CF属性中的布隆过滤器为        ROWCOL,那么GET(R1, q1)就会过滤sf2,get(r1,q2)就会过滤sf1
c)NO
    默认的值,默认不开启布隆过滤器

实现:

        在建立表时加入一个参数就能够了

try {
    //使用HTableDescriptor类创建一个表对象
    HTableDescriptor students = new HTableDescriptor("students");

    //在创建表的时候,至少指定一个列簇
    HColumnDescriptor info = new HColumnDescriptor("info");
    
    //BloomType是一个枚举类此处设置粒度级别
    info.setBloomFilterType(BloomType.ROW); 

    //将列簇添加到表中
    students.addFamily(info);
    //真正的执行,是由HMaster
    //hAdmin
    hAdmin.createTable(students);
    System.out.println(Bytes.toString(students.getName()) + "表 创建成功。。。");
} catch (IOException e) {
    e.printStackTrace();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值