HBase
简介
Hadoop Database,是一个高可靠性、高性能、面向列、可伸缩、实时读写的分布式数据库。
利用Hadoop HDFS作为其文件存储系统,利用Zookeeper作为其分布式协同服务主要用来存储非结构化和半结构化的松散数据(列存 NoSQL 数据库)
HBase优点
容量大:面向列:多版本:稀疏性:拓展性:高可靠性:高性能:
HBase数据模型
HBase 是一个稀疏的、分布式、持久、多维、排序的映射,它以行键(row key),列簇(column Family),列名(Column Qualifier)和时间戳(timestamp)为索引。
命名空间NameSpace:
命名空间,类似于关系型数据库的 DatabBase 概念,每个命名空间下有多个表。HBase 有两个自带的命名空间,分别是 hbase 和 default,hbase 中存放的是 HBase 内置的表,default 表是用户默认使用的命名空间。
表Table:
Hbase的table由多个行组成,行有多个列组成
行键RowKey:
行键,每一行的主键列,每行的行键要唯一,行键的值为任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在HBase内部,rowKey保存为字节数组byte[] 。 行的一次读写是原子操作 (不论一次读写多少列)
列族Column Family:
列簇在物理上包含了许多的列与列的值,每个列簇都有一些存储的属性可配置。
将功能属性相近的列放在同一个列族,而且同一个列族中的列会存放在同一个Store中
列族一般需要在创建表的时候就进行声明,而且一般一个表中的列族数不要超过3个
列隶属于列族,列族隶属于表
列限定符Column Qualifier:
就是列族下的每个子列名称,或者称为相关列,或者称为限定符。 列簇的限定词,理解为列的唯一标识。但是列标识是可以改变的,因此每一行可能有不同的列标识 使用的时候必须 列族:列 列可以根据需求动态添加或者删除,同一个表中不同行的数据列都可
时间戳版本号Timestamp:
通过rowkey和column family,column qualifier确定的一个存贮单元通过时间戳来索引。
每个cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本(比如最近七天)。用户可以针对每个列族进行设置。
存储单元Cell:
Cell是由row,column family,column qualifier,version 组成的 cell中的数据是没有类型的,全部是字节码形式存贮。 因为HDFS上的数据都是字节数组
HBase高可用集群搭建
启动集群
【123】 zkServer.sh start
[root@node01 conf]# start-all.sh
[root@node01 conf]# start-hbase.sh
HBase访问方式
HBase shell
我们可以先用Hbase提供的命令行工具,位于hbase的/bin/目录下
进入退出 hbase shell exit
查看帮助信息, help
查询服务器状态 status
查询hive版本 version
hell实现DDL操作
数据库定义语言(表的建立,删除,添加删除列族,控制版本)
命令
-
创建一个表 语法:create, {NAME => , VERSIONS => } 1.创建一个表
语法:create
例如:创建表t1,有两个family name:f1,f2,且版本数均为2
hbase(main)> create 't1',{NAME => 'f1', VERSIONS => 2},{NAME => 'f2', VERSIONS=> 2}
还有一种非标准创建的语法,创建表member,列族是member_id,address,info,版本为1。如下:
hbase(main):011:0>create 'member','member_id','address','info'
2.获得表的描述
语法:describe
hbase(main):012:0>list
hbase(main):006:0>describe 'member'
3.删除一个列族,alter,disable,enable
我们之前建了3个列族,但是发现member_id这个列族是多余的,因为他就是主键,所以我们要将其删除。
hbase(main):003:0>alter 'member',{NAME=>'member_id',METHOD=>'delete'}
将表enable
hbase(main):008:0> enable 'member'
4.列出所有的表
hbase(main):028:0>list
5.drop一个表
create 'temp_table','member_id','address','info'
hbase(main):029:0>disable 'temp_table'
hbase(main):030:0>drop 'temp_table'
6.查询表是否存在
hbase(main):021:0>exists 'member'
7.判断表是否enable
hbase(main):034:0>is_enabled 'member'
8.判断表是否disable
hbase(main):032:0>is_disabled 'member'
9.查看文件存储路径
hdfs dfs -ls /yjx/hbase/data/default/t1
10.truncate此命令将删除并重新创建一个表。
truncate 't1'
Shell实现DML操作
数据库操作语言(增删改)
Shell实现DQL操作
数据库查询语言(查询–全表扫描–基于主键–基于过滤器)
Shell实现Region管理
HBase架构模型
HBase有三个主要组成部分:客户端库,主服务器和区域服务器。
Client 客户端
客户端负责发送请求到数据库,客户端连接的方式有很多种 hbase shell 类JDBC 发送的请求主要包括 DDL:数据库定义语言(表的建立,删除,添加删除列族,控制版本) DML:数据库操作语言(增删改) DQL:数据库查询语言(查询–全表扫描–基于主键–基于过滤器) client维护着一些cache来加快对hbase的访问,比如regione的位置信息。
HMaster
HBase集群的主节点,HMaster也可以实现高可用(active–standby) 通过Zookeeper来维护主副节点的切换 为Region server分配region并负责region server的负载均衡 管理用户对table的结构DDL(创建,删除,修改)操作 表的元数据信息–》Zookeeper上面 表的数据–》HRegionServer上 当HRegionServer下线的时候,HMaster会将当前HRegionServer上的Region转移到其他的 HRegionServer
zookeeper
保证任何时候,集群中只有一个master 存贮所有Region的寻址入口,存储所有的的元数据信息。 实时监控Region Server的状态,将Region server的上线和下线信息实时通知给Master 存储Hbase的schema,包括有哪些table,每个table有哪些column family
HRegionServer
Region server属于HBase具体数据的管理者 (工作节点)
Region server维护Master分配给它的region,处理对这些region的IO请求
会实时的和HMaster保持心跳,汇报当前节点的信息
当接收到Hmaster命令创建表的时候,分配一个Region对应一张表
Region server负责切分在运行过程中变得过大的region
当客户端发送DML和DQL操作的时候,HRegionServer负责和客户端建立连接
当意外关闭的时候,当前节点的Region会被其他HRegionServer管理
HRegion
HRegion是HBase中分布式存储和负载均衡的最小单元;
不同的HRegion可以分别在不同的HRegionServer上,同一个HRegion不会拆分到多个server上;
HRegion按大小分隔,每个表一般是只有一个HRegion,当HRegion的某个列族达到一个阈值(默认256M)就会分成两个新的HRegion,HRegion被分配给哪个HRegionServer是完全动态透明的;
每个HRegion由以下信息标识:< 表名,startRowkey,创建时间>,由目录表(-ROOT-和.META.)记录该HRegion的endRowkey 。
Store
HRegion是表获取和分布的基本元素,由一个或者多个Store组成,每个store保存一个columns family。 每个Store又由1个memStore和0或多个StoreFile组成。
HFile是Hbase在HDFS中存储数据的格式,它包含多层的索引,这样在Hbase检索数据的时候就不 用完全的加载整个文件。 索引的大小(keys的大小,数据量的大小)影响block的大小,在大数据集的情况下,block的大小设 置为每个RegionServer 1GB也是常见的
Storefile
memStore内存中的数据写到文件后就是StoreFile,StoreFile底层是以HFile的格式保存在HDFS上;
StoreFile文件的数量增长到一定阈值后,系统会进行合并(minor/major compaction),在合并过程中会进行版本合并和删除工作,形成更大的StoreFile。
MemStore
memStore 是放在内存里的,其保存修改的数据即Key-Values;
? 当memStore的大小达到一个阀值(默认128MB)时,memStore会被flush到文件,即生成一个快照
Hlog
一个HRegionServer只有一个Log文档 WAL(Write After Log)做任何操作之前先写日志
HLog->WAL log-Write Ahead Log,数据的所有变更均会写入HLog,一旦HRegionServer 宕机,就可以从log中进行恢复;
HLog文件就是一个Hadoop Sequence File ,其中value是HBase的Key-Value对象,即对应HFile中的Key-Value,除此之外还记录了数据的归属信息,除了table和region名字外,还同时包括sequence number和写入时间timestamp。
公共流程
0.96版本之前:
Client–>Zookeeper–>-ROOT-表–>.META.表–>HRegion–>HRegionServer–>Client
0.96版本后:
? (新增了namespace,删去了-ROOT-表):Client–>Zookeeper–>.META.表–>HRegion–>HRegionServer–>Client
去掉-ROOT-的原因:
其一:提高性能
其二:2 层结构已经足以满足集群的需求
读取数据流程
读流程:
- 1)Client 先访问zookeeper,获取hbase:meta表位于哪个Region Server。
-
- 访问对应的Region Server, 获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的metacache,方便下次访问。
-
- 与目标RegionServer进行通讯;
-
- 分别在Block Cache (读缓存),MemStore 和Store File (HFile) 中查询目标数据,并将.查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本( time stamp)或者不同的类型(PutDelete) 。 .
-
- 将从文件中查询到的数据块(Block, HFile 数据存储单元,默认大小为64KB)缓存到Block Cache。
-
- 将合并后的最终结果返回给客户端。
写入数据流程
写流程:
-
- Client 先访问zookeeper,获取hbase:meta表位于哪个Region Server。
- 2)访问对应的Region Server,获取meta表,根据读请求的rowkey,查询出目标数据位于哪个RegionServer中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的metacache,方便下次访问。
- 3)与目标RegionServer进行通讯;
- 4)将数据顺序写入(追加)到WAL(在操作之前要先写入日志);
- 5)将数据写入对应的MemStore,数据会在MemStore进行排序;
- 6)向客户端发送ack(确认信息);
- 7)等达到MemStore的刷写时机后,将数据刷写到HFile。
数据刷写(Memstore Flush)
触发时机
-
Region 中某个MemStore 占用的内存超过相关阈值(128M) 内存占用达到512M的时候会阻塞客户端的写入 -
如果整个 RegionServer 的 MemStore 占用内存总和大于阈值将会触发 MemStore 的刷写。 阈值为总内存*40%*95% -
Wal(做任何操作先写日志)日志的阈值大小 比如一直做插入删除数据,日志就会越来越大 -
定期自动刷写 默认值 3600000(即 1 小时) -
数据更新超过一定阈值 ? HBase 的某个 Region 更新的很频繁,而且既没有达到自动刷写阀值,也没有达到内存的使用限制,但是内存中的更新数量已经足够多的时候 -
手动触发刷写 ? 通过shell执行flush命令 把内存中的数据弄到硬盘上
刷写策略
-
1.1之前: ? MenStore 刷写是Region级别的。列族不超过三个 -
2.2之后: ? 1.Region中所有的MemStore都进行刷写 ? 2.判断 Region 中每个 MemStore 的使用内存是否大于某个阀值,大于这个阀值的MemStore 将会被刷写。
刷写流程
数据合并
HBase合并原因:
HBase不停的刷写,导致存储目录中有过多的数据文件,文件太多会导致维护困难、降低数据查询性能和效率。对一堆的文件进行I/O操作,耗时太多。所以HBase定期会对这些琐碎的文件进行整理,即合并Compaction。
合并分类
HBase 根据合并规模将 Compaction 分为了两类:MinorCompaction 和 MajorCompaction
Minor Compaction(小型):
- 是指选取一些小的、相邻的StoreFile将他们合并成一个更大的StoreFile,在这个过程中不会处理已经Deleted或Expired的Cell
- 但是会处理超过TTL的数据
- 一次Minor Compaction的结果是让小的storefile变的更少并且产生更大的StoreFile。
Major Compaction (大型):
- 是指将所有的StoreFile合并成一个StoreFile
- 清理三类无意义数据:被删除的数据、TTL过期数据、版本号超过设定版本号的数据。
- 一般情况下,Major Compaction时间会持续比较长,整个过程会消耗大量系统资源,对上层业务有比较大的影响。因此线上业务都会将关闭自动触发Major Compaction功能,改为手动在业务低峰期触发。
合并时机
触发compaction的方式有三种:Memstore刷盘、后台线程周期性检查、手动触发 。
Memstore Flush:
- 对Store的StoreFile文件进行判断,是否达到合并的阈值(文件的数量为10个)
周期性检测:
- 默认一个合并的周期 10000S,如果达到阈值也会检查文件的数目
- 如果文件数目超过3个进行小合并,如果不超过3个,检查最早一次合并时间,如果超过7天则进行大合并
手动触发
- 是因为很多业务担心自动major compaction影响读写性能,因此会选择低峰期手动触发;
- 执行alter操作之后希望立刻生效,执行手动触发major compaction;
- 是HBase管理员发现硬盘容量不够的情况下手动触发major compaction删除大量过期数据;
合并策略
承载了大量IO请求但是文件很小的HFile,compaction本身不会消耗太多IO,而且合并完成之后对读的 性能会有显著提升。
线程池选择
- HBase CompacSplitThread类内部对于Split、Compaction等操作专门维护了各自所使用的线程池
- 和Compaction相关的是如下的longCompactions和shortCompactions
- 前者用来处理大规模compaction,后者处理小规模compaction
- 默认值为2 * maxFlilesToCompact * hbase.hregion.memstore.flush.size
- 如果flush size 大小是128M,该参数默认值就是2 * 10 * 128M = 2.5G
合并策略选择
HBase 主要有两种 minor 策略: RatioBasedCompactionPolicy (0.96.x之前) 和ExploringCompactionPolicy(当前默认)
- RatioBasedCompactionPolicy(基于比列的合并策略)
从老到新逐一扫描HFile文件,满足以下条件之一停止扫描
当前文件大小<比当前文件新的所有文件大小总和*ratio(高峰期1.2,非高峰期5)
当前所剩候选文件数<=阈值(默认为3)
- ExploringCompactionPolicy策略(默认策略)
基于Ratio策略,不同之处在于Ratio策略找到一个合适文件集合就停止扫描,而Exploring策略
会记录所有合适的文件集合,然后寻找最优解,待合并文件数最多或者待合并文件数相同的情况下
文件较小的进行合并
-
FIFO Compaction策略 收集过期文件并删除,对应业务的列簇必须设置有TTL
-
Tier-Based Compaction策略(分层策略) 针对数据热点情况设计的策略,根据候选文件的新老程度将其划分为不同的等级,每个等级都有对
应的Ratio,表示该等级文件比选择为参与Compation的概率
-
Stripe Compation策略(条纹策略) 将整个Store中的文件按照key划分为多个range,此处称为stripe,一个Stripe内部就类似于
一个小Region,可以执行Minon Compation和major Compation
数据切分(Region Split)
通过切分,一个region变为两个近似相同大小的子region,再通过balance机制均衡到不同 regionserver上,使系统资源使用更加均衡。
切分原因
-
数据分布不均匀
-
compaction性能损耗严重 compaction本质上是一个排序合并的操作,合并操作需要占用大量内存,因此文件越大,占用内存越多
compaction有可能需要迁移远程数据到本地进行处理(balance之后的compaction就会存在
这样的场景),如果需要迁移的数据是大文件的话,带宽资源就会损耗严重
-
资源耗费严重 HBase的数据写入量也是很惊人的,每天都可能有上亿条的数据写入
不做切分的话一个热点region的新增数据量就有可能几十G,用不了多长时间大量读请求就会把单台region server的资源耗光。
触发时机
- 每次数据合并之后都会针对相应region生成一个requestSplit请求,requestSplit首先会执行
checkSplit,检测file size是否达到阈值,如果超过阈值,就进行切分。 - 检查阈值算法主要有两种:ConstantSizeRegionSplitPolicy( 0.94版本)和
IncreasingToUpperBoundRegionSplitPolicy(当前)
- ConstantSizeRegionSplitPolicy :
系统会遍历region所有store的文件大小,如果有文件大小 > hbase.hregion.max.filesize(默 认10G),就会触发切分操作。 - IncreasingToUpperBoundRegionSplitPolicy:
- 如果store大小大于一个变化的阀值就允许split。
- 默认只有1个region,那么逻辑这个region的store大小超过 1 * 1 * 1 * flushsize * 2 =
128M * 2 =256M 时,才会允许split - 切分之后会有两个region,其中一个region中的某个store大小大于 2 * 2 * 2 * flushsize * 2= 2048M 时,则允许split
- 后续超过hbase.hregion.max.filesize + hbase.hregion.max.filesize * 随机小数 *
hbase.hregion.max.filesize.jitter才允许split - 基本也就固定了,如果粗劣的计算可以把这个hbase.hregion.max.filesize的大小作为最后的阀值,默认是10G
切分流程
- 寻找切分点
- 先找最大的Store,然后再找最大的StoreFile,再找到中心点位置的Row Key。
- 切分流程
- 1.首先regionserver在父region下创建切分目录,当创建成功后会关闭该region
- 2.在切分目录下创建相应的文件结构:两个子region目录以及引用文件,每个引用文件指向原始reion的一
半记录。若此过程成功,则将两个子region目录移动至表目录下。同时meta表进行更新,表示父region已 经被切分,避免再被错误打开 - 3.开启多个线程异步将原region的数据真正的写成两半,取代引用文件。 发生在子region的temp目录下,
一旦文件生成,则替代引用文件 - 4.当切分完成后,原region 被删除。Master 收到这次切分的通知后根据reionserver的负载情况选择是
否进行region迁移
切分优化
- 对于预估数据量较大的表,需要在创建表的时候根据rowkey执行 region 的预分配。
- 通过region预分配,数据会被均衡到多台机器上,这样可以一定程度解决热点应用数据量剧增导致
的性能问题
Hbase表设计要点
行键设计
行健不能改变,唯一可以改变的方式是先删除后插入 ,尽量所有的操作都依赖RowKey进行查询
长度原则:
? RowKey是一个二进制码流,最大长度为64KB,建议越短越好。长度1-100就行,不要超过16个字节
为什么Rowkey 的字节长度越短越好? MemStore 将部分数据缓存到内存,如果 Rowkey 字段过长内存的有效利用率会降低,系统将无法缓存更多的数据,这会降低检索效率
散列原则:
? 尽量将连续的数据存放到更多的RegionServer中
如果没有散列字段,首字段直接是时间信息将产生所有的新数据都放在一个 RegionServer 上造成堆积,就会产生热点现象
建议将 Rowkey 的高位作为散列字段,低位放时间字段,这样就提高数据均衡分布在每个Regionserver上,实现负载均衡的几率。
唯一原则:
? 必须在设计上保证唯一性,rowkey 是按照字典顺序排序存储的
? 设计 rowkey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问 的数据放到一块。
数据热点
原因:
- 热点发生在大量的 client 直接访问集群的一个或极少数个节点(访问可能是读, 写或者其他操作)。
- 大量访问会使热点region所在的单个机器超出自身承受能力,性能下降甚至 region 不可用,主机无法服务其他 region 的请求
策略:
反转策略:
- 反转固定长度或者数字格式的 rowkey。这样可以使得 rowkey 中经常改变的部分放在前面。这样可以有效的随机 rowkey,但是牺 牲了 rowkey 的有序性。
加盐策略:
- 在 rowkey 的前面增加随机数,具体就是给 rowkey 分配一个随机前缀以使得它和之前的 rowkey 的开头不同。
哈希策略:
- 哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。
- 使用确定的哈希可以让客户端重构完整的 rowkey,可以使用 get 操作准确获取 某一个行数据
列族设计
追求的原则是: 在合理范围内尽量能减少列族就减少列族,列族的名字也尽可能短一些,优先使用首字母
最优设计是: 将所有相关性很强的 key-value 都放在同一个列簇下
|