【大数据】——Hbase总结
一、前言
HBase – Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统,利用HBase技术可在廉价PC Server上搭建起大规模结构化存储集群。HBase是Google Bigtable的开源实现,类似Google Bigtable利用GFS作为其文件存储系统,HBase利用Hadoop HDFS作为其文件存储系统;Google运行MapReduce来处理Bigtable中的海量数据,HBase同样利用Hadoop MapReduce来处理HBase中的海量数据;Google Bigtable利用 Chubby作为协同服务,HBase利用Zookeeper作为对应。Hbase介绍视频:[https://www.bilibili.com/video/av21629305?from=search&seid=6409533564158911048][https_www.bilibili.com_video_av21629305_from_search_seid_6409533564158911048];Hbase原理:[https://cloud.tencent.com/developer/article/1120649][https_cloud.tencent.com_developer_article_1120649]
二、Hbase原理
Hadoop HDFS为HBase提供了高可靠性的底层存储支持,Hadoop MapReduce为HBase提供了高性能的计算能力,Zookeeper为HBase提供了稳定服务和failover机制。Pig和Hive还为HBase提供了高层语言支持,使得在HBase上进行数据统计处理变的非常简单。 Sqoop则为HBase提供了方便的RDBMS(关系型数据库)数据导入功能,使得[传统数据库][Link 1]数据向HBase中迁移变的非常方便。
1、什么是Hbase: Hbase是一个可以运行在Hadoop集群上的NoSQL数据库。
Hbase组件
• Hbase Master
• Region Server
• Region
• Zookeeper
Hbase的架构图如下图所示:
2、我们先看下 HBase 的写流程:
通常 MapReduce 在写HBase时使用的是 TableOutputFormat 方式,在reduce中直接生成put对象写入HBase,该方式在大数据量写入时效率低下(HBase会block写入,频繁进行flush,split,compact等大量IO操作),并对HBase节点的稳定性造成一定的影响(GC时间过长,响应变慢,导致节点超时退出,并引起一系列连锁反应),而HBase支持 bulk load 的入库方式,它是利用hbase的数据信息按照特定格式存储在hdfs内这一原理,直接在HDFS中生成持久化的HFile数据格式文件,然后上传至合适位置,即完成巨量数据快速入库的办法。配合mapreduce完成,高效便捷,而且不占用region资源,增添负载,在大数据量写入时能极大的提高写入效率,并降低对HBase节点的写入压力。
通过使用先生成HFile,然后再BulkLoad到Hbase的方式来替代之前直接调用HTableOutputFormat的方法有如下的好处:
(1)消除了对HBase集群的插入压力
(2)提高了Job的运行速度,降低了Job的执行时间
目前此种方式仅仅适用于只有一个列族的情况,在新版 HBase 中,单列族的限制会消除。
3、bulkload 流程与实践
bulkload 方式需要两个Job配合完成:
(1)第一个Job还是运行原来业务处理逻辑,处理的结果不直接调用HTableOutputFormat写入到HBase,而是先写入到HDFS上的一个中间目录下(如 middata)
(2)第二个Job以第一个Job的输出(middata)做为输入,然后将其格式化HBase的底层存储文件HFile
(3)调用BulkLoad将第二个Job生成的HFile导入到对应的HBase表中
三、常用命令
说明:新版hbase取消了对HQL的支持,只能使用shell
命令:disable 'tableName' --disable表。注:修改表结构时,必须要先disable表。
命令:enable 'tableName' --使表可用
命令:drop 'tableName' --删除表
下面我们再看看看HBase的一些基本操作命令,我列出了几个常用的HBase Shell命令,如下:
# 登陆hbase
hbase shell
# 浏览所有表
hbase(main):001:0> list
# 创建表
语法:create <table>, {NAME => <family>, VERSIONS => <VERSIONS>}
创建一个User表,并且有一个info列族
hbase(main):002:0> create 'User','info'
0 row(s) in 1.5890 seconds
=> Hbase::Table - User
# 查看表详情
hbase(main):004:0> describe 'OTTable'
# 插入数据到表
hbase(main) > put 'student','1001','info:name','Thomas'
hbase(main) > put 'student','1001','info:sex','male'
hbase(main) > put 'student','1001','info:age','18'
hbase(main) > put 'student','1002','info:name','Janna'
hbase(main) > put 'student','1002','info:sex','female'
hbase(main) > put 'student','1002','info:age','20'
# 根据rowKey查询某个记录
语法:get <table>,<rowkey>,[<family:column>,....]
hbase(main):008:0> get 'User', 'row2'
COLUMN CELL
info:age timestamp=1502368069926, value=18
1 row(s) in 0.0280 seconds
hbase(main):028:0> get 'User', 'row3', 'info:sex'
COLUMN CELL
info:sex timestamp=1502368093636, value=man
hbase(main):036:0> get 'User', 'row1', {COLUMN => 'info:name'}
COLUMN CELL
info:name timestamp=1502368030841, value=xiaoming
1 row(s) in 0.0120 seconds
# 查询所有记录
语法:scan <table>, {COLUMNS => [ <family:column>,.... ], LIMIT => num}
扫描所有记录
hbase(main):009:0> scan 'User'
ROW COLUMN+CELL
row1 column=info:name, timestamp=1502368030841, value=xiaoming
row2 column=info:age, timestamp=1502368069926, value=18
row3 column=info:sex, timestamp=1502368093636, value=man
3 row(s) in 0.0380 seconds
# 扫描前2条
hbase(main):037:0> scan 'User', {LIMIT => 2}
ROW COLUMN+CELL
row1 column=info:name, timestamp=1502368030841, value=xiaoming
row2 column=info:age, timestamp=1502368069926, value=18
2 row(s) in 0.0170 seconds
# 范围查询
hbase(main):011:0> scan 'User', {STARTROW => 'row2'}
ROW COLUMN+CELL
row2 column=info:age, timestamp=1502368069926, value=18
row3 column=info:sex, timestamp=1502368093636, value=man
2 row(s) in 0.0170 seconds
hbase(main):012:0> scan 'User', {STARTROW => 'row2', ENDROW => 'row2'}
ROW COLUMN+CELL
row2 column=info:age, timestamp=1502368069926, value=18
1 row(s) in 0.0110 seconds
hbase(main):013:0> scan 'User', {STARTROW => 'row2', ENDROW => 'row3'}
ROW COLUMN+CELL
row2 column=info:age, timestamp=1502368069926, value=18
1 row(s) in 0.0120 seconds
# 统计表记录数
语法:count <table>, {INTERVAL => intervalNum, CACHE => cacheNum}
INTERVAL设置多少行显示一次及对应的rowkey,默认1000;CACHE每次去取的缓存区大小,默认是10,调整该参数可提高查询速度
例子:每隔一万行显示一条
hbase(main):020:0> count 'OTTable', {INTERVAL => 10000, CACHE => 10}
#删除某 rowkey 的全部数据:
hbase(main) > deleteall 'student','1001
#删除某 rowkey 的某一列数据:
hbase(main) > delete 'student','1002','info:sex'
#删除一个列族,alter,disable,enable
我们之前建了3个列族,但是发现member_id这个列族是多余的,因为他就是主键,所以我们要将其删除。
报错,删除列簇的时候必须先将表给disable掉。
再删除列簇‘member_id’
该列族已经删除,我们继续将表enable
#清空表数据
清空表的操作顺序为先 disable,然后再 truncating。
hbase(main) > truncate 'student'
# drop一个表
Drop之前需要先disable
# 查询表是否存在
# 判断表是否enable
# 判断表是否disable
四、Hbase读写流程及寻址
#写操作流程
(1) Client通过Zookeeper的调度,向RegionServer发出写数据请求,在Region中写数据。
(2) 数据被写入Region的MemStore,直到MemStore达到预设阈值。
(3) MemStore中的数据被Flush成一个StoreFile。
(4) 随着StoreFile文件的不断增多,当其数量增长到一定阈值后,触发Compact合并操作,将多个StoreFile合并成一个StoreFile,同时进行版本合并和数据删除。
(5) StoreFiles通过不断的Compact合并操作,逐步形成越来越大的StoreFile。
(6) 单个StoreFile大小超过一定阈值后,触发Split操作,把当前Region Split成2个新的Region。父Region会下线,新Split出的2个子Region会被HMaster分配到相应的RegionServer上,使得原先1个Region的压力得以分流到2个Region上。
可以看出HBase只有增添数据,所有的更新和删除操作都是在后续的Compact历程中举行的,使得用户的写操作只要进入内存就可以立刻返回,实现了HBase I/O的高机能。
#读操作流程
(1) Client访问Zookeeper,查找-ROOT-表,获取.META.表信息。
(2) 从.META.表查找,获取存放目标数据的Region信息,从而找到对应的RegionServer。
(3) 通过RegionServer获取需要查找的数据。
(4) Regionserver的内存分为MemStore和BlockCache两部分,MemStore主要用于写数据,BlockCache主要用于读数据。读请求先到MemStore中查数据,查不到就到BlockCache中查,再查不到就会到StoreFile上读,并把读的结果放入BlockCache。
寻址过程:client—>Zookeeper—>-ROOT-表—>.META.表—>RegionServer—>Region—>client
table在行的方向上分隔为多个Region。Region是HBase中分布式存储和负载均衡的最小单元,即不同的region可以分别在不同的Region Server上,但同一个Region是不会拆分到多个server上。
Region按大小分隔,每个表一行是只有一个region。随着数据不断插入表,region不断增大,当region的某个列族达到一个阈值(默认256M)时就会分成两个新的region。
每个region由以下信息标识:
- <表名,startRowkey,创建时间>
- 由目录表(-ROOT-和.META.)可值该region的endRowkey
HRegion定位:
Region被分配给哪个Region Server是完全动态的,所以需要机制来定位Region具体在哪个region server。
HBase使用三层结构来定位region:
- 1、 通过zk里的文件/hbase/rs得到-ROOT-表的位置。-ROOT-表只有一个region。
- 2、通过-ROOT-表查找.META.表的第一个表中相应的region的位置。其实-ROOT-表是.META.表的第一个region;.META.表中的每一个region在-ROOT-表中都是一行记录。
- 3、通过.META.表找到所要的用户表region的位置。用户表中的每个region在.META.表中都是一行记录。
-ROOT-表永远不会被分隔为多个region,保证了最多需要三次跳转,就能定位到任意的region。client会讲查询的位置信息保存缓存起来,缓存不会主动失效,因此如果client上的缓存全部失效,则需要进行6次网络来回,才能定位到正确的region,其中蚕丝用来发现缓存失效,另外三次用来获取位置信息。
五、Hbase中的三个重要机制
# flush机制
当MemStore达到阈值,将Memstore中的数据Flush进Storefile
涉及属性:hbase.hregion.memstore.flush.size:134217728(即:128M就是Memstore的默认阈值)
hbase.regionserver.global.memstore.upperLimit:0.4
即:这个参数的作用是当单个HRegion内所有的Memstore大小总和超过指定值时,flush该HRegion的所有memstore。RegionServer的flush是通过将请求添加一个队列,模拟生产消费模式来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发OOM。
hbase.regionserver.global.memstore.lowerLimit:0.38
即:当MemStore使用内存总量达到hbase.regionserver.global.memstore.upperLimit指定值时,将会有多个MemStores flush到文件中,MemStore flush 顺序是按照大小降序执行的,直到刷新到MemStore使用内存略小于lowerLimit
# compact机制
把小的Memstore文件合并成大的Storefile文件。
# split机制
当Region达到阈值,会把过大的Region一分为二。
六、HFile
HBase中KeyValue数据的存储格式,是hadoop的二进制格式文件。首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。Trailer中又指针指向其他数据块的起始点,FileInfo记录了文件的一些meta信息。Data Block是hbase io的基本单元,为了提高效率,HRegionServer中又基于LRU的block cache机制。每个Data块的大小可以在创建一个Table的时候通过参数指定(默认块大小64KB),大号的Block有利于顺序Scan,小号的Block利于随机查询。每个Data块除了开头的Magic以外就是一个个KeyValue对拼接而成,Magic内容就是一些随机数字,目的是烦着数据损坏,结构如下。
HFile结构图如下:
Data Block段用来保存表中的数据,这部分可以被压缩。Meta Block段(可选的)用来保存用户自定义的kv段,可以被压缩.FileInfo段用来保存HFile的元信息,本能被压缩,用户也可以在这一部分添加自己的元信息。Data Block Index段(可选的)用来保存Meta Blcok的索引。Trailer这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个 block读取到内存中,再找到需要的key。DataBlock Index采用LRU机制淘汰。HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。目标HFile的压缩支持两种方式:gzip、lzo。
七、补充
1、补充1:
我们观察上面这一幅图:
一 张表,有两个列族(红颜色的一个,黄颜色的一个),一个列族有两个列,从图中可以看出,这就是列式数据库的最大特点,同一个列族的数据在在一起的,我们还 发现如果是有多个版本,同时也会存多个版本。最后我们还发现里面存了这样的值:r1:键值,cf1:列族的名字,c1:列明。t1:版本号,value值 (最后一幅图说明的是value值可以存放的位置)。通过这样的看法,我们发现如果我们设计表的时候把这几个东西:r1:键值,cf1:列族的名 字,c1:列明的名字取短一点是不是我们会省出好多存储的空间!
还有,我们从这一幅图中还应该得到这样的认识:
我 们看倒数第二张图,字段筛选的效率从左到右明显下降,所以在keyvalue的设计时用户可以考虑把一些重要的筛选信息左移到合适的位置,从而在不改变数 据量的情况下,提高查询性能。那么简单的说就是用户应当尽量把查询维度或信息存储在行健中,因为它筛选数据的效率最高。
得到上面的认识后,我们应该还要会有这样的觉悟:
HBase 的数据存储时会被有顺序的存储到一个特定的范围,因为我们存储的时候一般都是按顺序的,所以会一直存到同一个region上,由于一个region只能由 一个服务器管理,这样我们老是添加到同一个region上,会造成读写热点,从而使集群性能下降。那么解决这个的办法还是有的,我能想到的就是,比如我们 有9台服务器,那么我们就回去当前时间,然后摸9,加到行健前缀,这样就会被平均的分到不同的region服务器上了,这样带来的好处是,因为相连的数据 都分布到不同的服务器上了,用户可以多线程并行的读取数据,这样查询的吞吐量会提高。关于我们版本的控制,我们要么就让多台服务器上的时间都同步,要么干脆就在put插入数据的时候,就设置一个客户端的时间戳来代替。(因为我们要是不显示的添加,人家就给我们在自己的服务器上添加了自己的时间了。)
补充2:
设 计表的时候,有两种设计方式,一种是高表设计,一种是胖表设计。根据HBase的拆分规则,我们的高表设计更容易拆分(使用组合键),不过,如果我们设计 成胖表,而我们的这个胖里的数据需要经常修改,这样设计是很合理的,因为我们的HBase保证了行级的原子性,如果设计成高表,反而就不合适了,因为不能 保证跨行的原子性。
补充3:
写缓存:每 一个put的操作实际上是RPC的操作,它将客户端的数据传送到服务器然后返回,这只适合小数据量的操作,如果有个应用程序需要每秒存储上千行数据到 HBase表中,这样处理就不太合适了。HBase的API配备了一个客户端的写缓冲区,缓冲区负责收集put操作,然后调用RPC操作一次性将put送 往服务器。默认情况下,客户端缓冲区是禁止的。可以通过自动刷写设置为FALSE来激活缓冲区。 table.setAutoFlush(false);void flushCommits () throws IOException这个方法是强制 将数据写到服务器。用户还可以根据下面的方法来配置客户端写缓冲区的大小。 void setWritaeBufferSize(long writeBufferSize) throws IOException;默认大小是 2MB,这个也是适中的,一般用户插入的数据不大,不过如果你插入的数据大的话,可能要考虑增大这个值。从而允许客户端更高效地一定数量的数据组成一组通 过一次RPC请求来执行。给每个用户的HTable设置一个写缓冲区也是一件麻烦的事,为了避免麻烦,用户可以在Hbase-site.xml中给用户设置一个较大的预设值。
八、其他
#Hbase常见问题及解决方案汇总
#Hbase架构视频
#Hbase批量导入说明:https://cloud.tencent.com/developer/article/1446572
#Hbase查询快的原因:https://blog.csdn.net/haoshuai2015/article/details/80984544
#记一次Hbase查询速度优化经历
还没有评论,来说两句吧...