一、 Hbase简介
1、Hbase 定义
Apache HBase is the Hadoop database, a distributed, scalable,big data store.
Use Apache HBase when you need random, realtime read/write access to your Big Data.
2、 Hbase的数据模型
Hbase 的逻辑结构
Name Space:命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表。
Table:表,定义时只需要声明列族即可,这意味着,往HBase写入数据时,字段可以动态、按需指定。
Row:HBase表中的每行数据都由一个RowKey和多个Column(列)组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,
Column:HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定。
Time Stamp:用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。
Cell:由{rowkey, column Family:column Qualifier, time Stamp} 唯一确定的单元。没有数据结构。
Region:分区,将数据分成若干个分区
store:每一个Region中每一个列族形成一个store
Hbase 的物理结构
数据的删除以及更改是通过添加数据来完成的。
3、 Hbase 的基本架构
1)Region Server
Region Server为 Region的管理者,其实现类为HRegionServer,主要作用如下:
对于数据的操作:get, put, delete;
对于Region的操作:splitRegion、compactRegion。
2)Master
Master是所有Region Server的管理者,其实现类为HMaster,主要作用如下:
对于表的操作:create, delete, alter
对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,负载均衡和故障转移。
3)Zookeeper
HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。
4)HDFS
HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用的支持。
1)StoreFile
保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序的。
2)MemStore
写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile。
3)WAL
由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
4)BlockCache
读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询。
二、Hbase核心
1、 写流程
写流程:
1)Client先访问zookeeper,获取hbase:meta表位于哪个Region Server。
2)访问对应的Region Server,获取hbase:meta表,根据写请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
3)与目标Region Server进行通讯;
4)将数据顺序写入(追加)到WAL;
5)将数据写入对应的MemStore,数据会在MemStore进行排序;
6)向客户端发送ack;
7)等达到MemStore的刷写时机后,将数据刷写到HFile。
8)当Hfile文件达到一定的大小之后,会触发Compact合并操作,合并为一个StoreFile,(这里同时进行版本的合并和数据的删除)
9)当StoreFile大小超过一定的阈值以后,会把当前的 Region切分(split)成两个,并由Hmaster分配到相应的HregionServer,实现负载均衡。
2、MemStore Flush
MemStore刷写时机:
1.当某个memstroe的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写。
2.当region server中memstore的总大小达到阈值,region会按照其所有memstore的大小顺序(由大到小)依次进行刷写。直到region server中所有memstore的总大小减小到上述值以下。
3.到达自动刷写的时间,也会触发memstore flush。
4.当WAL文件的数量超过hbase.regionserver.max.logs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.max.log以下(该属性名已经废弃,现无需手动设置,最大值为32)。
3、 读流程
读流程
1)Client先访问zookeeper,获取hbase:meta表位于哪个Region Server。
2)访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
3)与目标Region Server进行通讯;
4)若第一次生成的文件会将文件的索引缓存到BlockCache,当查询该Hfile中的数据,会在MemStore和StoreFile(HFile)中定位数据索引,并将查到的数据的Block块缓存到BlockCache中,当再次查询该数据块的信息,可以直接从BlockCache中查询。并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
5)将查询到的新的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。Block Cache采用LRU策略和分级缓存策略,当BlockCache的大小达到上限后,会触发缓存淘汰机制,将最老的一批数据淘汰掉。
6)将合并后的最终结果返回给客户端。
4、BloomFilter
主要功能:提高随机读的性能
存储开销:BloomFilter是列族级别的配置,一旦表格中开启BloomFilter,那么在生成StoreFile的同时也会生成一份包含BloomFilter的结构文件MetaBlock,所以会增加一定的存储开销。
BloomFilter的原理:
- 内部是一个bit数组,初始值均为0;
- 插入元素时对元素进行hash并且映射到数组中的某一个index,将其置为1,在进行多次不同的hash算法,将其映射的index置为1,同一个index只需置1次
- 查询时使用和插入时相同的hash算法,若对应的index位置全为1,则可以认为此元素可能存在,若有一个不为1,则可定不存在
- BloomFilter只能过滤掉不包含的数据,不能保证包含。
5、StoreFile Compaction
Compaction分为两种,分别是Minor Compaction和Major Compaction。Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据。Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉所有过期和删除的数据。大的合并默认是7天一次合并。
6、Region Split
默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server。
①Regin Server发起split,它会在zookeeper上创建一个节点,名称叫做/hbase/region-in-transition/region-name。状态为splitting。
②因为master会监听/hbase/region-in-transition目录,所以master会知道split的发生。
③region server在父region的HDFS目录下,创建一个叫做.split的子目录。
④region server在服务内部将父region关闭,此时,所有的客户端访问父region的请求都会失败,并抛出NotServeringRegionExection的异常。
⑤region server在.split目录下创建子region的目录以及必要的数据结构。对于一个storefile会创建两个reference file,并将reference file指向父region的文件。
⑥region server创建真正的两个子region目录,并移动这些reference文件,形成真正对切分。
⑦region server发送一个PUT请求到meta表,请求将父region在meta中的状态设置为offline,并增加子region的信息。
⑧region server 同时打开两个子region 接受请求。
⑨region server将两个子region的信息加入到meta表,此时,客户端会发现新的region,并可以向新的region发送请求。
⑩region server将zookeeper上的/hbase/region-in-transition/region-name的状态改为split。master可以感知到split的完成。之后,balancer可以将子region分配至其他的region server。
最后,切分完成,垃圾清理。
注意:切分完成以后,数据并不会立即合并,当进行查找操作仍然后访问的时父region中的数据,但是进行增删改时,会将新的数据保存在子region中。当进行Major Compaction时,会将父region和子region数据进行合并。
三、HBase预分区和RowKey设计⭐
1、 HBase预分区
若没有预分区,可能①数据往一个region上写,有写热点问题;②split会消耗大量的IO资源。
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,创建多个空的region,确定startkey和endkey,以提高HBase性能。
预分区可以手动设定:eg:
hbase> create 'staff1','info','partition1',SPLITS => ['1000','2000','3000','4000']
或者指定分区的个数,分区ID的算法:eg:
//创建16个分区,分区为16进制
create 'staff2','info','partition2',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
也可以从文件中读取分区规则:eg:
create 'staff3','partition3',SPLITS_FILE => 'splits.txt'
2、 RowKey设计
① rowkey的长度越短越好,列族的长度越短越好。
② rowkey尽量散列。以实现负载均衡。尽量避免rowkey是递增的。
③rowkey保证唯一。
一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。rowkey常用的设计方案:
1.生成随机数、hash、散列值
2.字符串反转
3.字符串拼接
3、 列族设计原则
- 建表至少要有一个列族,但是列族不宜过多,因为在数据刷写的时候是以region为单位的。
- 列族名字不宜过长
- 不同列族的记录数量级不宜相差过大。
四、HBase的优化方法
1、 减少调整
① 减少region的分裂
根据RowKey设计来进行预分区的设计,减少region的动态分裂
2、减少启停
① 关闭Major Compaction。
② 当需要写入大量离线数据建议使用BulkLoad
3、减少数据量
① 开启过滤,提高查询速度
② 使用压缩。
4、 Zookeeper会话超时时间
属性:zookeeper.session.timeout
解释:默认值为90000毫秒(90s)。当某个RegionServer挂掉,90s之后Master才能察觉到。可适当减小此值,以加快Master响应,可调整至600000毫秒。
5、优化HStore文件大小
属性:hbase.hregion.max.filesize
解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。
五、整合Phoenix
1、Phoenix的介绍
Phoenix可以使用标准JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据。
Phoenix的客户端分为thin(瘦)和thick(胖),thin不能在客户端进行SQL语句的转换,因此需要在Phoenix端启动queryserver来进行转换,而thick可以在客户端进行转换。
安装:①解压按转包 ②将其phoenix/phoenix-5.0.0-HBase-2.0-server.jar拷贝分发至其他节点的hbase/lib目录 ③配置环境变量
2、Phoenix的应用
-
连接Phoenix
胖客户端phoenix/bin/sqlline.py hadoop102,hadoop103,hadoop104:2181
瘦客户端:需要先要启动queryserver.py
在Phoenix中,schema 相当于namespace的概念。
创建schema 需要在hbase的hbase-site.xml和phoenix的bin/hbase-site.xml中添加:
<property>
<name>phoenix.schema.isNamespaceMappingEnabled</name>
<value>true</value>
</property>
- 显示所有表
!table 或 !tables
- 创建表
//必须指定主键
CREATE TABLE IF NOT EXISTS student(
id VARCHAR primary key,
name VARCHAR,
addr VARCHAR);
在phoenix中,表名等会自动转换为大写,若要小写,使用双引号,如"us_population"。
- 联合主键
CREATE TABLE IF NOT EXISTS us_population (
State CHAR(2) NOT NULL,
City VARCHAR NOT NULL,
Population BIGINT
CONSTRAINT my_pk PRIMARY KEY (state, city));
-- 在us_population中插入数据
upseret into us_population values(‘NY’,’NEW YORK’,10000);
查看在Hbase中的存储:
两个联合主键拼接作为了新的Row Key。
- 数值数据的转换
其中数据值是在phoenix进行了编码转换的结果。
phoenix对数据类型进行转换的规则与Hbase的规则不一样。Hbase中对数据是按照Bytes.toBytes()方法对数据进行16进制的编码,在linux端默认是Long类型的,按照补码进行存储。而另一个警告是字节序列化的方式必须与Phoenix字节序列化的方式匹配。对于VARCHAR,CHAR和UNSIGNED_ *类型,我们使用HBase Bytes方法。 CHAR类型期望仅使用单字节字符,而UNSIGNED类型期望使用大于或等于零的值。对于有符号类型(TINYINT,SMALLINT,INTEGER和BIGINT),Phoenix将翻转第一位,以便负值将在正值之前排序。因为HBase按字典顺序对行键进行排序,并且负值的第一位为1,而正数为0,所以如果我们不翻转第一位,则负值“大于”正值。因此,如果您通过HBase本机API存储整数,并想通过Phoenix访问它们,请确保所有数据类型均为UNSIGNED类型。
也就是说,对于有符号的数值数据,phoenix将翻转第一位,负数的1将变成0,正数的0将变成1,与HBase的不一样,因此想通过Phoenix访问它们,请确保所有数据类型均为UNSIGNED类型。
- 插入数据
-- 插入数据upsert,没有inseret
upsert into student values('1001','zhangsan','beijing');
在HBase中的存储:
数据在hbase中的存储形式。其中第一行为了对应phoenix中只有主键,没有其他列值的情况也能在hbase中存储。这里的列族均为0,列名转换为16进制减少占用。
若设置为none或者0,则列名不会进行16进制的编码。在建表时可以通过
CREATE TABLE T
(
a_string varchar not null,
col1 integer
CONSTRAINT pk PRIMARY KEY (a_string)
)
COLUMN_ENCODED_BYTES = 1;
最后的一个参数来指定等级。
- 退出命令行
!quit
3、表的映射
默认情况下,直接在HBase中创建的表,通过Phoenix是查看不到的。如果要在Phoenix中操作直接在HBase中创建的表,则需要在Phoenix中进行表的映射。映射方式有两种:视图映射和表映射。
- 视图映射
Phoenix创建的视图是只读的,所以只能用来做查询,无法通过视图对源数据进行修改等操作。在phoenix中创建关联test表的视图。
-- 创建视图
create view "test"(id varchar primary key,"info1"."name" varchar, "info2"."address" varchar);
-- 删除视图
drop view "test";
- 表映射
使用Apache Phoenix创建对HBase的表映射,有两种方法:
(1)HBase中不存在表时,可以直接使用create table指令创建需要的表,并会根据指令内的参数对表结构进行初始化。
(2)当HBase中已经存在表时,可以以类似创建视图的方式创建关联表,只需要将create view改为create table即可。
-- column_encoded_bytes=0必须指定为0或者none,因为若不指定,则列名会进行编码,在Hbase中就不能找到相对应的列。
create table "test"(id varchar primary key,"info1"."name" varchar, "info2"."address" varchar) column_encoded_bytes=0;
4、JDBC操作
<!-- 瘦客户端依赖和胖客户端依赖有冲突,应放在不同的项目中-->
<!-- 瘦客户端依赖 -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-queryserver-client</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
<!-- 胖客户端-->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>5.0.0-HBase-2.0</version>
<exclusions>
<exclusion>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b06</version>
</dependency>
瘦客户端:首先要启动queryserver.py进程
//瘦
public class PhoenixTest {
public static void main(String[] args) throws SQLException {
String connectionUrl = ThinClientUtil.getConnectionUrl("hadoop102", 8765);
System.out.println(connectionUrl);
Connection connection = DriverManager.getConnection(connectionUrl);
PreparedStatement preparedStatement = connection.prepareStatement("select * from student");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
System.out.println(resultSet.getString(1) + "\t" + resultSet.getString(2));
}
//关闭
connection.close();
}
}
//胖
public class TestThickClient {
public static void main(String[] args) throws SQLException {
String url="jdbc:phoenix:hadoop102,hadoop103,hadoop104:2181";
Properties props = new Properties();
//要跟phoenix的配置保持一致
props.put("phoenix.schema.isNamespaceMappingEnabled","true");
Connection connection = DriverManager.getConnection(url,props);
PreparedStatement ps = connection.prepareStatement("select * from \"test\"");
ResultSet resultSet = ps.executeQuery();
while (resultSet.next()){
System.out.println(resultSet.getString(1)+":"+resultSet.getString(2));
}
}
}
5、Phoenix二级索引⭐
- 二级索引配置文件
添加如下配置到HBase的HRegionserver节点的hbase-site.xml
<property>
<name>hbase.regionserver.wal.codec</name>
<value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>
</property>
- 全局二级索引
Global Index是默认的索引格式,创建全局索引时,会在HBase中建立一张新表。也就是说索引数据和数据表是存放在不同的表中的,因此全局索引适用于多读少写的业务场景。
在新的索引表中的存储的数据如下:将主键与二级索引进行了拼接。
创建单个字段的全局索引
CREATE INDEX my_index ON my_table (my_col);
如果想查询的字段不是索引字段的话索引表不会被使用,也就是说不会带来查询速度的提升。这种情况要想使用索引表,需要创建携带其他字段的全局索引
CREATE INDEX my_index ON my_table (v1) INCLUDE (v2);
将需要查询的字段加入到索引中,即覆盖索引。或者强制使用索引:
-- add不是索引字段
select /*+ INDEX(student name_index) */ id,name,add from student;
- 本地二级索引
Local Index适用于写操作频繁的场景。
索引数据和数据表的数据是存放在同一张表中(且是同一个Region),避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。
CREATE LOCAL INDEX my_index ON my_table (my_column);
如果想查询的字段不是索引字段的话本地索引表也会被使用,也就是说也会带来查询速度的提升。这是与全局索引的区别。
六、与Hive的集成
在hive-site.xml中添加zookeeper的属性,如下:
<property>
<name>hive.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
<property>
<name>hive.zookeeper.client.port</name>
<value>2181</value>
</property>
方式有两种,①HBase中没有数据,通过Hive进行创建 ② HBase中有数据,通过创建Hive表进行映射。Hive外部表相当于表的映射中的视图。不会改变源数据
提示:不能将数据直接load进Hive所关联HBase的那张表中,因为hive中load数据相当于把数据放到相应的HDFS路径下,但是此时的数据存在于Hbase中,Hbase中的数据是以Hfile的形式存储的,所以只能用中间表。但是可以inseret数据。
.xml中添加zookeeper的属性,如下:
<property>
<name>hive.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
<property>
<name>hive.zookeeper.client.port</name>
<value>2181</value>
</property>
方式有两种,①HBase中没有数据,通过Hive进行创建 ② HBase中有数据,通过创建Hive表进行映射。Hive外部表相当于表的映射中的视图。不会改变源数据
提示:不能将数据直接load进Hive所关联HBase的那张表中,因为hive中load数据相当于把数据放到相应的HDFS路径下,但是此时的数据存在于Hbase中,Hbase中的数据是以Hfile的形式存储的,所以只能用中间表。但是可以inseret数据。