HBase记录
本次记录是用于:SparkStreaming对接Kafka、HBase记录
一、基本概念
1、HBase以表的形式存储数据。表有行和列族组成。列族划分为若干个列。其结构如下
2、Row Key:行键
①hbase本质上也是一种Key-Value存储系统。Key相当于RowKey,Value相当于列族数据的集合
②与nosql数据库们一样,row key是用来检索记录的主键
③访问hbase table中的行,只有三种方式:
1)通过单个row key访问
2)通过row key的range
3)全表扫描
④Row key行键 (Row key)可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组
⑤存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)
3、列族(列簇)
①hbase表中的每个列,都归属与某个列族
②列族是表的schema的一部分(而列不是),列族必须在使用表之前定义
③列名都以列族作为前缀。例如courses:history , courses:math 都属于 courses 这个列族
④访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助管理不同类型的应用:允许一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)
4、Cell与时间戳
①由{row key, column( =<family> + < label>), version} 唯一确定的单元
②cell中的数据是没有类型的,全部是字节码形式存贮
③每个 cell都保存着同一份数据的多个版本,版本通过时间戳来索引
④时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值
⑤如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳
⑥每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面
⑦为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式:
a.保存数据的最后n个版本
b.保存最近一段时间内的版本(比如最近七天)
二、系统架构
1、Hbase与hadoop的架构图
2、Hbase架构组成
1)HBase采用Master/Slave(主从)架构搭建集群,它隶属于Hadoop生态系统,由以下类型节点组成:
①HMaster节点:
i. 管理HRegionServer,实现其负载均衡,保证每一个HRegionServer上的数据量差别不大
ii. 管理和分配HRegion,比如在HRegion分裂时分配新的HRegion;在HRegionServer退出时迁移其内的HRegion到其他HRegionServer上
iii. 实现DDL操作(Data Definition Language,namespace和table的增删改,column familiy的增删改等)
iv. 管理namespace和table的元数据(实际存储在HDFS上)
v. 权限控制(ACL)
②HRegionServer节点:
i. 存放和管理本地HRegion
ii. 读写HDFS,管理Table中的数据
iii. Client直接通过HRegionServer读写数据(从HMaster中获取元数据,找到RowKey所在的HRegion/HRegionServer后)
③ZooKeeper集群
i. 存放整个 HBase集群的元数据、集群的状态信息以及RS服务器的运行状态
ii. 实现HMaster主备节点的failover
④HBase的数据存储于HDFS中,因而涉及到HDFS的NameNode、DataNode等。RegionServer和DataNode一般会放在相同的Server上实现数据的本地化(避免或减少数据在网络中的传输,节省带宽)。
2)Hbase与Hadoop的体系图
3)体系点说明
HBase Client通过RPC方式和HMaster、HRegionServer通信;一个HRegionServer可以存放1000个HRegion(1000个数字的由来是来自于Google的Bigtable论文);底层Table数据存储于HDFS中,而HRegion所处理的数据尽量和数据所在的DataNode在一起,实现数据的本地化;数据本地化并不是总能实现,比如在HRegion移动(如因Split)时,需要等下一次Compact才能继续回到本地化
三、架构说明
1、HRegion
① HBase使用RowKey将表水平切割成多个HRegion
② 从HMaster的角度,每个HRegion都纪录了它的StartKey和EndKey
③ 由于RowKey是排序的,因而Client可以通过HMaster快速的定位每个RowKey在哪个HRegion中
④ HRegion由HMaster分配到相应的HRegionServer中,然后由HRegionServer负责HRegion的启动和管理、和Client的通信以及负责数据的读(使用HDFS)
⑤ 每个HRegionServer可以同时管理1000个左右的HRegion
2、HMaster
① HMaster没有单点故障问题,因为在HBase集群中可以启动多个HMaster
② 通过ZooKeeper的Master Election机制保证同时只有一个HMaster处于Active状态,其他的HMaster则处于热备份状态
③ 一般情况下会启动两个HMaster,Backup的HMaster会定期的和Active HMaster通信以获取其最新状态,从而保证它是实时更新的,因而如果启动了多个HMaster反而增加了Active HMaster的负担
④ HMaster的主要用于HRegion的分配和管理,DDL(Data Definition Language,既Table的新建、删除、修改等)的实现等,既它主要有两方面的职责:
a. 协调HRegionServer:
i. 启动时HRegion的分配,以及负载均衡和修复时HRegion的重新分配
ii. 监控集群中所有HRegionServer的状态(通过Heartbeat和监听ZooKeeper中的状态)
b. Admin职能
管理创建、删除、修改Table的操作
3、Zookeeper
① ZooKeeper为HBase集群提供协调服务,它管理着HMaster和HRegionServer的状态(available/alive等)
② ZooKeeper协调集群所有节点的共享信息,在HMaster和HRegionServer连接到ZooKeeper后创建Ephemeral( 临时)节点,并使用Heartbeat机制维持这个节点的存活状态,如果某个Ephemeral节点实效,则HMaster会收到通知,并做相应的处理
③ 当HMaster宕机的时候实现HMaster之间的failover(失败恢复)
④在HRegionServer宕机时通知给HMaster,从而对宕机的HRegionServer中的HRegion集合的修复(将它们分配给其他的HRegionServer)
⑤ 另外,HMaster通过监听ZooKeeper中的Ephemeral节点(默认:/hbase/rs/*)来监控HRegionServer的加入和宕机。在第一个HMaster连接到ZooKeeper时会创建Ephemeral节点(默认:/hbasae/master)来表示Active的HMaster,其后加进来的HMaster则监听该Ephemeral节点,如果当前Active的HMaster宕机,则该节点消失,因而其他HMaster得到通知,而将自身转换成Active的HMaster,在变为Active的HMaster之前,它会创建在/hbase/back-masters/下创建自己的Ephemeral节点。
4、HBase的第一次读写
1. 在HBase 0.96以前,HBase有两个特殊的Table:-ROOT-和.META.,
a. -ROOT- 表的存储位置存储在ZooKeeper,它存储了.META.表的RegionInfo信息,并且它只能存在一个HRegion
b. .META. 表则存储了用户定义的Table的RegionInfo信息,它可以被切分成多个HRegion
c. 第一次访问Table时,首先从ZooKeeper中读取-ROOT- 表所在HRegionServer;然后从该HRegionServer中根据请求的TableName,RowKey读取.META. 表所在HRegionServer;最后从该HRegionServer中读取.META. 表的内容而获取此次请求需要访问的HRegion所在的位置,然后访问该HRegionSever获取请求的数据,这需要三次请求才能找到用户Table所在的位置,然后第四次请求开始获取真正的数据。当然为了提升性能,客户端会缓存-ROOT- Table位置以及-ROOT-/.META. Table的内容
d. 可是即使客户端有缓存,在初始阶段需要三次请求才能获取到用户自定义的Table真正所在的位置
2. 在HBase 0.96以后去掉了-ROOT- Table,只剩下这个特殊的目录表叫做Meta Table(hbase:meta)
它存储了集群中所有用户HRegion的位置信息,而ZooKeeper的节点中(/hbase/meta-region-server)存储的则直接是这个Meta Table的位置,并且这个Meta Table如以前的-ROOT- Table一样是不可split的。这样,客户端在第一次访问用户Table的流程是:
a. 从ZooKeeper(/hbase/meta-region-server)中获取hbase:meta的位置(HRegionServer的位置),缓存该位置信息
b. 从HRegionServer中查询用户Table对应请求的RowKey所在的HRegionServer,缓存该位置信息
c. 从查询到HRegionServer中读取Row
3、过程说明
从这个过程中,客户端会缓存这些位置信息,然而第二步它只是缓存当前RowKey对应的HRegion的位置,因而如果下一个要查的RowKey不在同一个HRegion中,则需要继续查询hbase:meta所在的HRegion,然而随着时间的推移,客户端缓存的位置信息越来越多,以至于不需要再次查找hbase:meta Table的信息
4、当某个HRegion因为宕机或Split被移动,此时需要重新查询并且更新缓存。
5、HRegionServer详解
1. HRegionServer一般和DataNode在同一台机器上运行,实现数据的本地性
2. HRegionServer存储和管理多个HRegion,由WAL(HLog)、BlockCache、Region组成:
a. WAL即Write Ahead Log
i. 在早期版本中称为HLog,它是HDFS上的一个文件,所有写操作都会先保证将写操作写入这个Log文件后,才会真正更新MemStore,最后写入HFile中
ii. 采用这种模式,可以保证HRegionServer宕机后,依然可以从该Log文件中恢复数据,重新执行所有的操作,而不至于数据丢失
iii. HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是HLogKey对象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括 sequence number和timestamp,timestamp是”写入时间”,sequence number的起始值为0,或者是最近一次存入文件系统中sequence number。HLog Sequece File的Value是HBase的KeyValue对象,即对应HFile中的KeyValue
iv. 这个Log文件会定期Roll出新的文件而删除旧的文件(那些已持久化到HFile中的Log可以删除)。WAL文件存储在/hbase/WALs/${HRegionServer_Name}的目录中(在0.94之前,存储在/hbase/.logs/目录中),一般一个HRegionServer只有一个WAL实例,也就是说一个HRegionServer的所有WAL写都是串行的(就像log4j的日志写也是串行的)
v. 一个RS服务器只有一个HLOG文件,在0.94版本之前,写HLOG的操作是串行的,所以效率很低,所以1.0版本之后,Hbase引入多管道并行写技术,从而提高性能
b. BlockCache是一个读缓存
i. 采用“引用局部性”原理(也应用于CPU,分空间局部性和时间局部性,空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能
ii. 这样设计的目的是为了提高读缓存的命中率
iii. HBase中默认采用on-heap LruBlockCache策略(LRU -evicted,是一种数据的回收策略,LRU– 最近最少使用的:移除最长时间不被使用的对象)
c. HRegion是一个Table中的一个Region在一个HRegionServer中的表达
i. 一个Table可以有一个或多个HRegion,它们可以在一个相同的HRegionServer上,也可以分布在不同的HRegionServer上
ii. 一个HRegionServer可以有多个HRegion,他们分别属于不同的Table
iii. HRegion由一个或者多个Store(HStore)构成,每个HStore对应了一个Table在这个HRegion中的一个Column Family,即每个Column Family就是一个集中的存储单元,因而最好将具有相近I/O特性的Column存储在一个Column Family,以实现高效读取(数据局部性原理,可以提高缓存的命中率)。HStore是HBase中存储的核心,它实现了读写HDFS功能,一个HStore由一个MemStore 和0个或多个StoreFile组成
iv. MemStore是一个写缓存(In Memory Sorted Buffer),所有数据的写在完成WAL日志写后,会写入MemStore中,由MemStore根据一定的算法(LSM-TREE算法,这个算法的作用是将数据顺序写入磁盘,而不是随机写,减少磁头调度时间,从而提高写入性能)将数据Flush到底层的HDFS文件中(HFile),通常每个HRegion中的每个 Column Family有一个自己的MemStore
v. HFile(StoreFile) 用于存储HBase的数据(Cell/KeyValue)。在HFile中的数据是按RowKey、Column Family、Column排序,对相同的Cell(即这三个值都一样),则按timestamp倒序排列(即最新的数据在最前面)
3. 因为Hbase的HFile是存到HDFS上,所以Hbase实际上是具备数据的副本冗余机制的。
四、物理存储原理
1、概述
①Hbase里的一个Table 在行的方向上分割为一个或者多个HRegion,即HBase中一个表的数据会被划分成一个或者很多的HRegion,每一个HRegion会被存储在一个节点上。这样做的目的是为了能做到数据的分布式存储
② HRegion可以动态扩展(即增加或者删除节点,并且会进行分裂)并且HBase保证HRegion的负载均衡
③ HRegion实际上是行键排序(默认是字典排序)后的按规则分割的连续的存储空间
④ 一张Hbase表,可能有多个HRegion,每个HRegion达到一定大小(默认是10GB)时,进行分裂。
2、拆分流程
①HRegion是按大小分割的,每个表一开始只有一个HRegion,随着数据不断插入表,HRegion不断增大,当增大到一个阀值的时候,HRegion就会等分两个新的等大的HRegion。因此随着table中的行不断增多,就会有越来越多的HRegion
② 按照现在主流硬件的配置,每个HRegion的大小可以是1~20GB。这个大小由hbase.hregion.max.filesize指定,默认为10GB
③ HRegion的拆分和转移是由HBase(HMaster)自动完成的,用户感知不到
④ HRegion是Hbase中分布式存储和负载均衡的最小单元
⑤ HRegion虽然是分布式存储的最小单元,但并不是存储的最小单元
⑥ HRegion由一个或者多个HStore组成,每个HStore保存一个columns family
⑦ 每个HStore又由一个memStore(写缓存,默认是128M)以及0个或者多个StoreFile组成,StoreFile以HFile格式保存在HDFS上
⑧ 总结:HRegion是分布式的存储最小单位,StoreFile(Hfile)是存储最小单位
五、HBase写流程
1、当客户端发起一个Put请求时,首先它从hbase:meta表中查出该Put数据最终需要去的HRegionServer。然后客户端将Put请求发送给相应的HRegionServer,在HRegionServer中它首先会将该Put操作写入WAL日志文件中(Flush到磁盘中)
2、写完WAL日志文件后,然后会将数据写到Memstore,在Memstore按Rowkey排序,以及用LSM-TREE对数据做合并处理。HRegionServer根据Put中的TableName和RowKey找到对应的HRegion,并根据Column Family找到对应的HStore,并将Put写入到该HStore的MemStore中。此时写成功,并返回通知客户端
3、MemStore是一个In Memory Sorted Buffer,在每个HStore中都有一个MemStore,一个HRegion的一个Column Family对应一个HStore实例。在MemStore中,数据的排列顺序以RowKey、Column Family、Column的顺序以及Timestamp的倒序
4、每一次Put/Delete请求都是先写入到MemStore中,当MemStore满后会Flush成一个新的StoreFile(底层实现是HFile),即一个HStore(Column Family)可以有0个或多个StoreFile(HFile)。有以下三种情况可以触发MemStore的Flush动作:
a. 当一个HRegion中的MemStore的大小超过了:hbase.hregion.memstore.flush.size的大小,默认128MB,此时当前的MemStore会Flush到HFile中
b. 当RS服务器上所有的MemStore的大小超过了:hbase.regionserver.global.memstore.upperLimit的大小,默认35%的内存使用量,此时当前HRegionServer中所有HRegion中的MemStore可能都会Flush。一般从最大的Memostore开始flush
c. 当前HRegionServer中WAL的大小超过了 1GB。hbase.regionserver.hlog.blocksize(32MB) * hbase.regionserver.max.logs(32)的数量,当前HRegionServer中所有HRegion中的MemStore都会Flush,这里指的是两个参数相乘的大小
5. 此外,在MemStore Flush过程中,还会在尾部追加一些meta数据,其中就包括Flush时最大的WAL sequence值,以告诉HBase这个StoreFile写入的最新数据的序列,那么在Recover时就知道从哪里开始。在HRegion启动时,这个sequence会被读取,并取最大的作为下一次更新时的起始sequence
6、HBase的数据以KeyValue(Cell)的形式顺序的存储在HFile中,在MemStore的Flush过程中生成HFile,由于MemStore中存储的Cell遵循相同的排列顺序,因而Flush过程是顺序写,而磁盘的顺序写性能很高,因为不需要不停的移动磁盘指针。
7、 HFile参考BigTable的SSTable和Hadoop的TFile实现,从HBase开始到现在,HFile经历了三个版本,其中V2在0.92引入
8、HFile在V1的格式:
a. V1的HFile由多个Data Block、Meta Block、FileInfo、Data Index、Meta Index、Trailer组成:
i. Data Block是HBase的最小存储单元,BlockCache就是基于Data Block的缓存的。一个Data Block由一个魔数(Magic)和一系列的KeyValue(Cell)组成,魔数是一个随机的数字,用于表示这是一个Data Block类型,以快速检测这个Data Block的格式,防止数据的破坏。Data Block的大小可以在创建Column Family时设置(HColumnDescriptor.setBlockSize()),默认值是64KB,大号的Block有利于顺序Scan,小号Block利于随机查询,因而需要权衡
ii. Meta块是可选的,FileInfo是固定长度的块,它纪录了文件的一些Meta信息,例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等
iii. Data Index和Meta Index纪录了每个Data块和Meta块的起始点、未压缩时大小、Key(起始RowKey)等
iv. Trailer纪录了FileInfo、Data Index、Meta Index块的起始位置,Data Index和Meta Index索引的数量等。其中FileInfo和Trailer是固定长度的
b. HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构
i. 开始是两个固定长度的数值,分别表示Key的长度和Value的长度
ii. 然后是Key,key的开始是固定长度的数值,表示RowKey的长度,紧接着是 RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete
iii. Value部分没有这么复杂的结构,就是纯粹的二进制数据了。随着HFile版本迁移,KeyValue(Cell)的格式并未发生太多变化,只是在V3版本,尾部添加了一个可选的Tag数组
HFileV1版本的在实际使用过程中发现它占用内存多,因而增加了启动时间。为了解决这些问题,在0.92版本中引入HFileV2版本,在这个版本中,为了提升启动速度,还引入了延迟读的功能,即在HFile真正被使用时才对其进行解析:
对HFileV2格式具体分析,它是一个多层的类B+树索引,采用这种设计,可以实现查找不需要读取整个文件:
Data Block中的Cell都是升序排列,每个block都有它自己的Leaf-Index,每个Block的最后一个Key被放入Intermediate-Index中,Root-Index指向Intermediate-Index。在HFile的末尾还有Bloom Filter(布隆过滤)用于快速定位那么没有在某个Data Block中的Row;TimeRange信息用于给那些使用时间查询的参考。在HFile打开时,这些索引信息都被加载并保存在内存中,以增加以后的读取性能。
六、HBase读实现
1、流程
① 首先对新写入的Cell,它会存在于MemStore中;然后对之前已经Flush到HFile中的Cell,它会存在于某个或某些StoreFile(HFile)中;最后,对刚读取过的Cell,它可能存在于BlockCache中
② 既然相同的Cell可能存储在三个地方,在读取的时候只需要扫瞄这三个地方,然后将结果合并即可(Merge Read),在HBase中扫瞄的顺序依次是:BlockCache、MemStore、StoreFile(HFile)(这个扫描顺序的目的也是为了减少磁盘的I/O次数)
③ 其中StoreFile的扫瞄先会使用Bloom Filter(布隆过滤算法)过滤那些不可能符合条件的HFile,然后使用Block Index快速定位Cell,并将其加载到BlockCache中,然后从BlockCache中读取。
④ 一个HStore可能存在多个StoreFile(HFile),此时需要扫瞄多个HFile,如果HFile过多又是会引起性能问题
2、compaction机制
① MemStore每次Flush会创建新的HFile,而过多的HFile会引起读的性能问题。为了解决这个问题,HBase采用Compaction机制来解决这个问题。在HBase中Compaction分为两种:Minor Compaction和Major Compaction
② Minor Compaction是指选取一些小的、相邻的StoreFile将他们合并成一个更大的StoreFile,在这个过程中不会处理已经Deleted或Expired的Cell。一次Minor Compaction的结果是减少Store File的数量但是产生更大的StoreFile
3. Major Compaction是指将所有的StoreFile合并成一个StoreFile,在这个过程中,标记为Deleted的Cell会被删除,而那些已经Expired的Cell会被丢弃,那些已经超过最多版本数的Cell会被丢弃。一次Major Compaction的结果是一个HStore只有一个StoreFile存在。Major Compaction可以手动或自动触发,然而由于它会引起很多的I/O操作而引起性能问题,因而它一般会被安排在周末、凌晨等集群比较闲的时间
4、实现Compaction的方式有2种
①API
②指令
5、Hbase默认用的是Minor compaction。之所以默认不用Major Compaction的原因是在于,Major Compaction可能会代理大量的磁盘I/O,从而阻塞Hbase其他的读写操作。所以对于Major Compactoin,一般选择在业务峰值低的时候执行
七、布隆过滤器
1、背景说明
① Hash 函数在计算机领域,尤其是数据快速查找领域,加密领域用的极广
② 作用是将一个大的数据集映射到一个小的数据集上面(这些小的数据集叫做哈希值,或者散列值)
③ Hash table(散列表,也叫哈希表),是根据哈希值(Key value)而直接进行访问的数据结构。也就是说,它通过把哈希值映射到表中一个位置来访问记录,以加快查找的速度。下面是一个典型的示意图:
④ 这种简单的Hash Table存在一定的问题,就是Hash冲突的问题。假设 Hash 函数是良好的,如果位阵列长度为 m 个点,那么如果想将冲突率降低到例如 1%, 这个散列表就只能容纳 m * 1% 个元素。显然这就不叫空间有效了(Space-efficient)。
2、Bloom Filter概述
① Bloom Filter是1970年由布隆(Burton Howard Bloom)提出的
② 它实际上是一个很长的二进制向量和一系列随机映射函数(Hash函数)
③ 布隆过滤器可以用于检索一个元素是否在一个集合中
④ 它的优点是空间效率和查询时间都远远超过一般的算法
⑤ Bloom Filter广泛的应用于各种需要查询的场合中,如:Google 著名的分布式数据库 Bigtable 使用了布隆过滤器来查找不存在的行或列,以减少磁盘查找的IO次数
⑥ 在很多Key-Value系统中也使用了布隆过滤器来加快查询过程,如 Hbase,Accumulo,Leveldb,一般而言,Value 保存在磁盘中,访问磁盘需要花费大量时间,然而使用布隆过滤器可以快速判断某个Key对应的Value是否存在,因此可以避免很多不必要的磁盘IO操作,只是引入布隆过滤器会带来一定的内存消耗
3、Bloom Filter原理
①如果想判断一个元素是不是在一个集合里,一般想到的是将所有元素保存起来,然后通过比较确定,链表,树等等数据结构都是这种思路.。但是随着集合中元素的增加,需要的存储空间越来越大,检索速度也越来越慢。
② 一个Bloom Filter是基于一个m位的位向量(b1,…bm),这些位向量的初始值为0。另外,还有一系列的hash函数(h1,…hk)(默认是3个哈希函数),这些hash函数的值域属于1~m。下图是一个bloom filter插入x,y,z并判断某个值w是否在该数据集的示意图:
③ 布隆过滤器的缺点和优点一样明显。误算率(False Positive)是其中之一。随着存入的元素数量增加,误算率随之增加。但是如果元素数量太少,则使用散列表足矣
④ 总结:Bloom Filter 通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。此外,引入布隆过滤器会带来一定的内存消耗