[整理] HBase 基础
一、介绍HBase
Apache HBase 是 Hadoop 数据库,一个分布式、可伸缩的大数据存储。
HBase是依赖Hadoop的。为什么HBase能存储海量的数据?
因为HBase是在HDFS的基础之上构建的,HDFS是分布式文件系统。
二、为什么要用HBase
能够处理数据的中间件(系统),比如说分布式搜索引擎「Elasticsearch」、分布式文件系统「HDFS」、分布式消息队列「Kafka」、缓存数据库「Redis」等等...
这些中间件基本都会有持久化的功能。为什么?
如果某一个时刻挂了,那还在内存但还没处理完的数据不就凉了?
Redis有AOF和RDB、Elasticsearch会把数据写到translog然后结合FileSystemCache将数据刷到磁盘中、Kafka本身就是将数据顺序写到磁盘....
这些中间件会实现持久化(像HDFS和MySQL我们本身就用来存储数据的),为什么我们还要用HBase呢?
-
MySQL?
MySQL数据库我们是算用得最多了的吧?但众所周知,MySQL是单机的。MySQL能存储多少数据,取决于那台服务器的硬盘大小。以现在互联网的数据量,很多时候MySQL是没法存储那么多数据的。- 比如有个系统,一天就能产生1TB的数据,这数据是不可能存MySQL的。(如此大的量数据,可以先写到Kafka,然后落到Hive中)
-
Kafka?
Kafka我们主要用来处理消息的(解耦异步削峰)。数据到Kafka,Kafka会将数据持久化到硬盘中,并且Kafka是分布式的(很方便的扩展),理论上Kafka可以存储很大的数据。但是Kafka的数据我们不会「单独」取出来。持久化了的数据,最常见的用法就是重新设置offset,做「回溯」操作 -
Redis?
Redis是缓存数据库,所有的读写都在内存中,速度贼快。AOF/RDB存储的数据都会加载到内存中,Redis不适合存大量的数据(因为内存太贵了!)。 -
Elasticsearch?
Elasticsearch是一个分布式的搜索引擎,主要用于检索。理论上Elasticsearch也是可以存储海量的数据(毕竟分布式),我们也可以将数据用『索引』来取出来,似乎已经是非常完美的中间件了。- 但是如果我们的数据没有经常「检索」的需求,其实不必放到Elasticsearch,数据写入Elasticsearch需要分词,无疑会浪费资源。
-
HDFS?
显然HDFS是可以存储海量的数据的,它就是为海量数据而生的。它也有明显的缺点:不支持随机修改,查询效率低,对小文件支持不友好。
文中的开头已经说了,HBase是基于HDFS分布式文件系统去构建的。换句话说,HBase的数据其实也是存储在HDFS上的。
那肯定有好奇宝宝就会问:HDFS和HBase有啥区别阿?
HDFS是文件系统,而HBase是数据库,其实也没啥可比性。「你可以把HBase当做是MySQL,把HDFS当做是硬盘。HBase只是一个NoSQL数据库,把数据存在HDFS上」。
数据库是一个以某种有组织的方式存储的数据集合。
扯了这么多,那我们为啥要用HBase呢?
HBase在HDFS之上提供了高并发的随机写和支持实时查询,这是HDFS不具备的。
我一直都说在学习某一项技术之前首先要了解它能干什么。如果仅仅看上面的"对比",我们可以发现HBase可以以低成本来存储海量的数据并且支持高并发随机写和实时查询。
但HBase还有一个特点就是:存储数据的”结构“可以地非常灵活。
三、入门HBase
听过HBase的同学可能都听过「列式存储」这个词。我最开始的时候觉得HBase很难理解,就因为它这个「列式存储」我一直理解不了它为什么是「列式」的。
在网上也有很多的博客去讲解什么是「列式」存储,它们会举我们现有的数据库,比如MySQL。存储的结构我们很容易看懂,就是一行一行数据嘛。
转换成所谓的列式存储是什么样的呢?
可以很简单的发现,无非就是把每列抽出来,然后关联上Id。
这个叫列式存储吗?
转换后的数据还是一行一行的。
这样做有什么好处吗?
很明显以前我们一行记录多个属性(列),有部分的列是空缺的,但是我们还是需要空间去存储。现在把这些列全部拆开,有什么我们就存什么,这样空间就能被我们充分利用。
这种形式的数据更像什么?明显是Key-Value嘛。那我们该怎么理解HBase所谓的列式存储和Key-Value结构呢?
3.1 HBase的数据模型
在看HBase数据模型的时候,里边也有表、行和列的概念,其实最好还是不要用「关系型数据库」的知识去理解它。
- 表没什么好说的,就是一张表
- 一行数据由一个行键和一个或多个相关的列以及它的值所组成
好了,现在比较抽象了。在HBase里边,定位一行数据会有一个唯一的值,这个叫做行键(RowKey)。而在HBase的列不是我们在关系型数据库所想象中的列。
HBase的列(Column)都得归属到列族(Column Family)中。在HBase中用列修饰符(Column Qualifier['kwɑlə.faɪər])来标识每个列。
在HBase里边,先有列族,后有列。
什么是列族?
可以简单理解为:列的属性类别
什么是列修饰符?
先有列族后有列,在列族下用列修饰符来标识一列。
还很抽象是不是?来画个图:
我们再放点具体的值去看看,就更加容易看懂了:
上面的那个图看起来可能不太好懂,画一个我们熟悉的:
HBase表的每一行中,列的组成都是灵活的,行与行之间的列不需要相同。如图下:
换句话说:一个列族下可以任意添加列,不受任何限制
数据写到HBase的时候都会被记录一个时间戳,这个时间戳被我们当做一个版本。比如说,我们修改或者删除某一条的时候,本质上是往里边新增一条数据,记录的版本加一了而已。
比如现在我们有一条记录:
现在要把这条记录的值改为40,实际上就是多添加一条记录,在读的时候按照时间戳读最新的记录。在外界「看起来」就是把这条记录改了。
3.2 HBase 的Key-Value
HBase本质上其实就是Key-Value的数据库。那在HBase里边,Key是什么?Value是什么?
我们看一下下面的HBaseKey-Value结构图:
Key由RowKey(行键)+ColumnFamily(列族)+Column Qualifier(列修饰符)+TimeStamp(时间戳--版本)+KeyType(类型)组成,而Value就是实际上的值。
对比上面的例子,其实很好理解,因为我们修改一条数据其实上是在原来的基础上增加一个版本的,那我们要准确定位一条数据,那就得(RowKey+Column+时间戳)。
KeyType是什么?我们上面只说了「修改」的情况,你们有没有想过,如果要删除一条数据怎么做?实际上也是增加一条记录,只不过我们在KeyType里边设置为“Delete”就可以了。
3.3 HBase架构
扯了这么一大堆,已经说了HBase的数据模型和Key-Value了,我们还有一个问题:「为什么经常会有人说HBase是列式存储呢?」
其实HBase更多的是「列族存储」,要谈列族存储,就得先了解了解HBase的架构是怎么样的。
我们先来看看HBase的架构图:
1、Client客户端,它提供了访问HBase的接口,并且维护了对应的cache来加速HBase的访问。
2、Zookeeper存储HBase的元数据(meta表),无论是读还是写数据,都是去Zookeeper里边拿到meta元数据告诉给客户端去哪台机器读写数据
3、HRegionServer它是处理客户端的读写请求,负责与HDFS底层交互,是真正干活的节点。
总结大致的流程就是:client请求到Zookeeper,然后Zookeeper返回HRegionServer地址给client,client得到Zookeeper返回的地址去请求HRegionServer,HRegionServer读写数据后返回给client。
3.4 HRegionServer内部
我们来看下面的图:
前面也提到了,HBase可以存储海量的数据,HBase是分布式的。所以我们可以断定:HBase一张表的数据会分到多台机器上的。那HBase是怎么切割一张表的数据的呢?用的就是RowKey来切分,其实就是表的横向切割。
说白了就是一个HRegion上,存储HBase表的一部分数据。
HRegion下面有Store,那Store是什么呢?我们前面也说过,一个HBase表首先要定义列族,然后列是在列族之下的,列可以随意添加。
一个列族的数据是存储在一起的,所以一个列族的数据是存储在一个Store里边的。
看到这里,其实我们可以认为HBase是基于列族存储的(毕竟物理存储,一个列族是存储到同一个Store里的)
Store里边有啥?有Mem Store、Store File、HFile,我们再来看看里边都代表啥含义。
HBase在写数据的时候,会先写到Mem Store,当MemStore超过一定阈值,就会将内存中的数据刷写到硬盘上,形成StoreFile,而StoreFile底层是以HFile的格式保存,HFile是HBase中KeyValue数据的存储格式。
所以说:Mem Store我们可以理解为内存 buffer,HFile是HBase实际存储的数据格式,而StoreFile只是HBase里的一个名字。
回到HRegionServer上,我们还漏了一块,就是HLog
这里其实特别好理解了,我们写数据的时候是先写到内存的,为了防止机器宕机,内存的数据没刷到磁盘中就挂了。我们在写Mem store的时候还会写一份HLog。
这个HLog是顺序写到磁盘的,所以速度还是挺快的(是不是有似曾相似的感觉)...
稍微总结一下:
- HRegionServer是真正干活的机器(用于与hdfs交互),我们HBase表用RowKey来横向切分表
- HRegion里边会有多个Store,每个Store其实就是一个列族的数据(所以我们可以说HBase是基于列族存储的)
- Store里边有Mem Store和StoreFile(HFile),其实就是先走一层内存,然后再刷到磁盘的结构
3.5 被遗忘的HMaster
我们在上面的图会看到有个Hmaster,它在HBase的架构中承担一种什么样的角色呢?读写请求都没经过Hmaster呀。
那HMaster在HBase里承担什么样的角色呢??
HMaster is the implementation of the Master Server. The Master server is responsible for monitoring all RegionServer instances in the cluster, and is the interface for all metadata changes.
-
HMaster会处理 HRegion 的分配或转移。如果我们HRegion的数据量太大的话,HMaster会对拆分后的Region重新分配RegionServer。(如果发现失效的HRegion,也会将失效的HRegion分配到正常的HRegionServer中)
-
HMaster会处理元数据的变更和监控RegionServer的状态。
四、RowKey的设计
到这里,我们已经知道RowKey是什么了。不难理解的是,我们肯定是要保证RowKey是唯一的,毕竟它是行键,有了它我们才可以唯一标识一条数据的。
在HBase里边提供了三种的查询方式:
- 全局扫描
- 根据一个RowKey进行查询
- 根据RowKey过滤的范围查询
4.1 根据一个RowKey查询
首先我们要知道的是RowKey是会按字典序排序的,我们HBase表会用RowKey来横向切分表。
无论是读和写我们都是用RowKey去定位到HRegion,然后找到HRegionServer。这里有一个很关键的问题:那我怎么知道这个RowKey是在这个HRegion上的?
HRegion上有两个很重要的属性:start-key和end-key。
我们在定位HRegionServer的时候,实际上就是定位我们这个RowKey在不在这个HRegion的start-key和end-key范围之内,如果在,说明我们就找到了。
这个时候会带来一个问题:由于我们的RowKey是以字典序排序的,如果我们对RowKey没有做任何处理,那就有可能存在热点数据
的问题。
举个例子,现在我们的RowKey如下:
java111
java222
java333
java444
java555
aaa
bbb
java777
java666
java...
Java开头的RowKey很多,而其他的RowKey很少。如果我们有多个HRegion的话,那么存储Java的HRegion的数据量是最大的,而分配给其他的HRegion数量是很少的。
关键是我们的查询也几乎都是以java%的数据去查,这会导致某部分数据会集中在某台HRegionServer上存储以及查询,而其他的HRegionServer却很空闲。
如果是这种情况,我们要做的是什么?
对RowKey散列就好了,那分配到HRegion的时候就比较均匀,少了热点的问题。
HBase优化提示:
建表申请时的预分区设置,对于经常使用HBase的小伙伴来说,HBase管理平台里申请HBase表流程必然不陌生了。
'给定split的RowKey组例如:aaaaa,bbbbb,ccccc;或给定例如:startKey=00000000,endKey=xxxxxxxx,regionsNum=x'
- 第一种方式:
是自己指定RowKey的分割点来划分region个数.比如有一组数据RowKey为[1,2,3,4,5,6,7],此时给定split RowKey是3,6,那么就会划分为[1,3),[3,6),[6,7)的三个初始region了.如果对于RowKey的组成及数据分布非常清楚的话,可以使用这种方式精确预分区.
- 第二种方式 :
如果只是知道RowKey的组成大致的范围,可以选用这种方式让集群来均衡预分区,设定始末的RowKey,以及根据数据量给定大致的region数,一般建议region数最多不要超过集群的rs节点数,过多region数不但不能增加表访问性能,反而会增加master节点压力.如果给定始末RowKey范围与实际偏差较大的话,还是比较容易产生数据热点问题.
最后:生成RowKey时,尽量进行加盐或者哈希的处理,这样很大程度上可以缓解数据热点问题.
4.2根据RowKey范围查询
上面的情况是针对通过RowKey单个查询的业务的,如果我们是根据RowKey范围查询的,那没必要上面那样做。
HBase将RowKey设计为字典序排序,如果不做限制,那很可能类似的RowKey存储在同一个HRegion中。那我正好有这个场景上的业务,那我查询的时候不是快多了吗?在同一个HRegion就可以拿到我想要的数据了。
举个例子:我们会间隔几秒就采集直播间热度,将这份数据写到HBase中,然后业务方经常要把主播的一段时间内的热度给查询出来。
我设计好的RowKey,将该主播的一段时间内的热度都写到同一个HRegion上,拉取的时候只要访问一个HRegionServer就可以得到全部我想要的数据了,那查询的速度就快很多。
最后
总结
- HBase是一个NoSQL数据库,一般我们用它来存储海量的数据(因为它基于HDFS分布式文件系统上构建的)
- HBase的一行记录由一个RowKey和一个或多个的列以及它的值所组成。先有列族后有列,列可以随意添加。
- HBase的增删改记录都有「版本」,默认以时间戳的方式实现。
- RowKey的设计如果没有特殊的业务性,最好设计为散列的,这样避免热点数据分布在同一个HRegionServer中。
- HBase的读写都经过Zookeeper去拉取meta数据,定位到对应的HRegion,然后找到HRegionServer