HBase Shell
常用命令:
注意:HBase在linux命令行下操作时,区分大小写
HBase API
HBase 提供Java方式的原生接口,其中需要注意的有:
(1)创建Connection是重量级的,并且,创建过多Connection会导致HBase拒绝连接。
(2)HBase Client不需要我们维护连接池,Connection对象已经帮我们做好了。
(3)Connection是线程安全的。
(4)科学的方式就是:在整个应用范围内只维护一个共用的Connection,比如以单例的形式,应用退出时关闭连接。
(5)由Connection取得的Table和Admin对象是轻量级的,并且不是线程安全的,所以它们应该即用即弃。
HBase Rest Gateway
HBase附带的REST服务器,该服务器将HBase表,行,单元和元数据作为URL指定的资源公开。包含的REST服务器可以作为守护程序运行,该守护程序启动嵌入式Jetty servlet容器并将servlet部署到其中。使用以下命令之一在前台或后台启动REST服务器。端口是可选的,默认为8080。在此不详细展开。
HBase到Hive
将HBase的数据作为数据源,建立Hive外部表关联到HBase,利用Hive的HQL查询HBase数据,更进一步,将Hive的元数据同步到Impala,利用Impala的SQL快速查表。
Impala就是一个查询外壳,利用Hive的元数据进行SQL快速查询。而Hive和HBase利用HDFS和MapReduce进行数据存储和计算,利用ZooKeeper进行集群管理。Hive又是数据仓库,提供了丰富的内置函数,以便于做数据多维度分析。
RowKey的设计
内容包含一下几个部分:
一、HBase基础原理;
二、合理的需求调研方法;
三、RowKey设计的常见技巧、原则;
索引的设计目标:读具有数据局部性特点,写具有避免热点的需求。为HBase提供更多维度的查询能力,根据业务需求场景力求在读和写之间找到最佳的平衡点。在实际应用中应该通过构建尽量少的索引,来满足更多的查询场景。
一、HBase基础原理
这部分包括:
- 基础概念与数据模型介绍
- 快速浏览读写流程
- 介绍RowKey在读写流程中发挥的作用。
1. 基础概念与数据模型介绍
(1)Table:同传统数据库中的表是类似的,不同之处在于它是基于SchemaLess的设计,比传统数据库表更灵活。
(2)Region:将表横向切割成一个个子表,即Region,它关联了数据的一个区间。
(3)Column Family:HBase可以将一行数据分成不同列的集合,这些列的集合称为Column Family,不同的Column Family文件被存储在不同的路径中。
(4)RegionServer:每个Region必须要分到RegionServer上才能提供正常的读写服务。
(5)MemStore:用来在内存中缓存一定大小的数据,达到一定大小后批量写入到底层文件系统中。在RegionServer上一个Region的一个Column Family对应一个MemStore。
(6)HFile:HBase数据在底层分布式文件系统中的文件组织格式。
关于进程角色,主要有ZooKeeper、Master、RegionServer等角色。Meta表的路由信息在ZooKeeper中;Master负责表管理操作,Region到各个RegionServer的分配以及RegionServer Failover的处理等;RegionServer提供数据读写服务。HBase的所有数据文件都存放在HDFS中。
关于Column Family,前面提到它是列的集合。每个Column Family里面关联了一个MemStore,关联了多个HFile文件。当我们在选择是否要应用多个Column Family的时候,需要调研所读写应用的业务特点,有些数据可能会一起写入,有时候临时增加数据,此时可以考虑用两个Column Family。设置多个列族时一行数据可能存在于两个路径中。整行读取的时候,需要将两个路径中的数据合并在一起才可以获取到完整的一行记录。
尽管我们在使用HBase表存放数据的时候,需要预先做好列设计。但这个设计仅仅由应用层感知,HBase并没有存放任何的Schema信息来描述这个设计。也就是说,应用层需要知道为每一表/每一行设计了什么样的列(KeyValue),然后在读取的时候做相应的解析。然HBase中并没有Schema信息,那么每一行中的列,也可以是任意添加的。
2.快速浏览读写流程
读写数据的简单路由机制。一开始会先去ZooKeeper中获取Meta表的路由信息,然后在Meta中定位每条数据关联的用户Region路径。下面这部分是基于RowKey从Meta表定位关联Region方法,通过一个反向扫描的方式进行。
下面介绍一下写入流程。客户端通过发请求到RegionServer端,写入的时候会先写入WAL日志中,其次会将数据写入memstore内存,当数据达到一定大小后会flush成一个个的HFile文件,当文件达到一定数量后,通过compaction的操作合并成更大文件,这样数据读取会更快。
Region Split。有人可能会有疑问,分裂的时候需不需搬迁数据?当一个Region变的过大后,会触发Split操作,将一个Region分裂成两个子Region。Region Split过程并不会真正将父Region中的HFile数据搬到子Region目录中。Split过程仅是在子Region中创建了到父Region的HFile引用文件,子 Region1中的引用文件指向原HFile的上部,而子Region2的引用文件指向原HFile2的下部。数据的真正搬迁工作是在 Compaction过程完成的。
下面是读取流程。当进行读取时,客户端会先发送scan请求到RegionServer,打开scanner,然后调next请求获取一行数据,也可以将一批数据先放入Result的Cache中去,客户端不断迭代获取内容,scan每次获取多少行数据通常需要结合自己业务特点去获取合理的值。
关于Scanner的抽象。由于数据一开始会先写入MemStore,当数据达到一定大小以后再Flush成底层文件,那么在读取的时候首先需要解决的问题是什么?因为数据可能存在于多个列族中,然后每个列族里又有内存里面的数据,还有些数据可能存在于多个文件中,那么应该如何读取呢?这里涉及到数据的抽象,这里将Region的读取会封装成ResultScanner对象,每个列族封装成StoreScanner对象,每个StoreScanner里面又有多个HFile、MemStore等。StoreScanner包含一个SegmentScanner和多个StoreFileScanner,这些Scanner会被组织在优先级队列里面,在Scan的时候一定会优先指定一个起始Key的值,Scanner在打开的时候会将指针定位到指定Key的位置,每个Scanner在打开的时候会对KeyValue 进行排序,然后放入一个优先级队列中。然后客户端每次通过Next请求驱动Scan的调用,Scanner Next请求调用如下图所示,用ScannerA-D表示上面提到的各种Scanner,当依次Next请求调用时,会判断哪个Scanner的数据是最小的。比如ScannerA先读,读取KeyValue数据,然后判断Scanner是否读取完毕,是否超出了Scan范围,假如没有读完会被再次丢回队列,重新排序,如此循环获取数据。
关于HFile的组织结构。HFile中数据按Block组织,一个Data Block的默认大小为64KB,Data Block中直接存储了KeyValue信息,最底层的Block是Leaf Index Block,Data Block的索引信息存储在Leaf Index Block中。而Leaf Index Block的信息存储在 Root Index中(不考虑Intermediate IndexBlock情形)。从Root Index Block到Leaf Index Block Leaf Index BlockLeaf Index Block Leaf Index Block LeafIndex BlockLeaf Index Block再到 DataBlock,以及从 Data Block到用户数据 KeyValue的数据组织,正是一种典型的B Tree结构。
3.RowKey在读写流程中发挥的作用
下面我们回顾一下RowKey在读写流程中发挥的作用:
读写数据时通过RowKey路由到对应的Region,MemStore中的数据按RowKey排序,HFile中的数据按RowKey排序。
RowKey的设计直接关乎Region的划分,我们如何划分Region?
首先通过分析业务读写吞吐量以及总的数据量信息,设定合理的Region数量目标,接下来预先定义RowKey的结构以及数据分布特点划分RowKey区间,然后按照设定的Split信息建表。
RowKey查询的局限性。根据下表信息,基于Name Phone ID构建RowKey。如果提供的查询条件能够尽可能丰富的描述RowKey的前缀信息,则查询时延越能得到保障。如下面几种组合条件场景:Name Phone ID、Name Phone、Name。如果查询条件不能提供Name信息,则RowKey的前缀条件是无法确定的,此时只能通过全表扫描的方式来查找结果。一种业务模型的用户数据RowKey,只能采用单一结构设计。但事实上,查询场景可能是多维度的。例如在上面的场景基础上,还需要单独基于Phone列进行查询。这是HBase二级索引出现的背景。即二级索引是为了让HBase能够提供更多维度的查询能力。
注意:HBase原生并不支持二级索引方案,但基于HBase的KeyValue数据模型与API,可以轻易地构建出二级索引数据。Phoneix提供了两种索引方案,而一些大厂家也都提供了自己的二级索引实现。
二、合理的需求调研
1.负载特点
(1)读写TPS;读写比重
重写轻读?重读轻写?读写相当?
(2)数据负载均衡与高效读取时常是矛盾的。
(3)在重读轻写的大数据场景中,RowKey设计应该更侧重于如何高效读取
(4)而在重写轻读的大数据场景中,在满足基本查询需求的前提下,应该更关注整体的吞吐量,这就对数据的负载均衡提出了很高的要求。
2.查询场景
(1)需要支持哪些查询场景?时延要求?
(2)最高频的查询场景是什么?
最有价值的数据排序场景是什么
(3)是否有其它维度的价值查询场景?频度?
(4)是否是组合字段场景?
(5)各个字段的匹配类型?
Equal? Prefix Match? Wildcard? Text-Search?
3.数据特点
(1)查询条件字段的离散度信息?
字段离散度的定义:字段A的离散度 = (字段A的可能的枚举值数目)/数据总记录条数
(2)查询条件字段的数据分布特点?
数据分布影响RowKey的设计,更进一步影响如何合理的划分Region信息
(3)数据生命周期?
影响到一个表的一次Major Compaction发生时涉及到的最大数据量
三、RowKey设计的常见技巧、原则
1.影响查询性能的关键因素
- 基于某一个索引/RowKey进行查询时,影响查询的最关键因素在于能否将扫描的候选结果集限定在一个合理的范围内。
- 知识点备注:查询驱动条件与查询过滤条件:直接影响数据扫描范围的查询条件,称之为查询驱动条件。而其它的能够起到过滤作用的查询条件,则称之为查询过滤条件。影响查询的关键因素在于如何合理的设置查询驱动条件。
2.RowKey字段的选取
遵循的最基本原则:
(1)唯一性: RowKey必须能够唯一的识别一行数据。
(2)无论应用是什么样的负载特点,RowKey字段都应该参考最高频的查询场景。数据库通常都是以如何高效的读取和消费数据为目的,而不是数据存储本身。
(3)而后,结合具体的负载特点,再对选取的RowKey字段值进行改造,组合字段场景下需要重点考虑字段的顺序。
3.避免数据热点的方法 - Reversing
如果经初步设计出的RowKey在数据分布上不均匀,但RowKey尾部的数据却呈现出了良好的随机性,此时,可以考虑将RowKey的信息翻转,或者直接将尾部的bytes提前到RowKey的前部。
缺点:更场景利于Get但不利于Scan,因为数据在原RowKey上的自然顺序已被打乱。
4.避免数据热点的方法 - Salting
Salting的原理是在原RowKey的前面添加固定长度的随机bytes,随机bytes能保障数据在所有Regions间的负载均衡。
缺点:既然是随机bytes,基于原RowKey查询时无法获知随机bytes信息是什么,也就需要去各个可能的Regions中去查看。可见,Salting对于读取是利空的。
5.避免数据热点的方法 - Hashing
基于RowKey的完整或部分数据进行Hash,而后将Hashing后的值完整替换原RowKey或部分替换RowKey的前缀部分。
缺点:与Reversing类似,Hashing也不利于Scan,因为打乱了原RowKey的自然顺序。
一般性设计思路:
在HBase中,row key可以是任意字符串,最大长度64KB,实际应用中一般为10~100bytes,存为byte[]字节数组,一般设计成定长的。
row key是按照字典序存储,因此,设计row key时,要充分利用这个排序特点,将经常一起读取的数据存储到一块,将最近可能会被访问的数据放在一块。举个例子:如果最近写入HBase表中的数据是最可能被访问的,可以考虑将时间戳作为row key的一部分。
尽量将需要查询的维度或者信息存储在行键中,因为它筛选数据的效率最高。