(作者:Angelvn大数据研发工作室)
第1章 HBase简介
1.1 HBase定义
Apache HBase是Hadoop数据库,一种分布式、可扩展的大数据存储。是当下比较火的NoSQL数据库之一。
1.2 HBase数据模型
HBase的设计理念依据Google的BigTable论文,论文中对于数据模型的首句介绍。
Bigtable 是一个稀疏的、分布式的、持久的多维排序map。
之后对于映射的解释如下:
该映射由行键、列键和时间戳索引组成;映射中的每个值都是一个未解释的字节数组。
最终HBase关于数据模型和BigTable的对应关系如下:
HBase 使用与 Bigtable 非常相似的数据模型。用户将数据行存储在带标签的表中。数据行具有可排序的键和任意数量的列。该表存储稀疏,因此如果用户喜欢,同一表中的行可以具有疯狂变化的列。
最终理解HBase数据模型的关键在于稀疏、分布式、多维、排序的映射。其中映射map指代非关系型数据库的key-Value结构。
1.2.1 HBase逻辑结构
HBase可以用于存储多种结构的数据,以JSON为例,存储的数据原貌为:
{
"row_key1":{
"personal_info":{
"name":"zhangsan",
"city":"北京",
"phone":"131********"
},
"office_info":{
"tel":"010-1111111",
"address":"Angelvn"
}
},
"row_key11":{
"personal_info":{
"city":"上海",
"phone":"132********"
},
"office_info":{
"tel":"010-1111111"
}
},
"row_key2":{
......
}

1.2.2 HBase物理存储结构
物理存储结构即为数据映射关系,而在概念视图的空单元格,底层实际根本不存储。

1.2.3 数据模型
1)NameSpace
命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表。HBase两个自带的命名空间,分别是hbase和default,hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。
2)Table
类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,不需要声明具体的列。因为数据存储时稀疏的,所有往HBase写入数据时,字段可以动态、按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。
3)Row
HBase表中的每行数据都由一个RowKey和多个Column(列)组成,数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。
4)Column
HBase中的每个列都由Column Family(列族)和Column Qualifier(列限定符)进行限定,例如info:name,info:age。建表时,只需指明列族,而列限定符无需预先定义。
5)TimeStamp
用于标识数据的不同版本(version),每条数据写入时,系统会自动为其加上该字段,其值为写入HBase的时间。
6)Cell
由{rowkey, column Family:column Qualifier, timestamp} 唯一确定的单元。cell中的数据全部是字节码形式存贮。
1.3 HBase基本架构

架构角色:
1)Master
实现类为HMaster,负责监控集群中所有的 RegionServer 实例。主要作用如下:
(1)管理元数据表格hbase:meta,接收用户对表格创建修改删除的命令并执行
(2)监控region是否需要进行负载均衡,故障转移和region的拆分。
通过启动多个后台线程监控实现上述功能:
①LoadBalancer负载均衡器
周期性监控region分布在regionServer上面是否均衡,由参数hbase.balancer.period控制周期时间,默认5分钟。
②CatalogJanitor元数据管理器
定期检查和清理hbase:meta中的数据。meta表内容在进阶中介绍。
③MasterProcWAL master预写日志处理器
把master需要执行的任务记录到预写日志WAL中,如果master宕机,让backupMaster读取日志继续干。
2)Region Server
Region Server实现类为HRegionServer,主要作用如下:
(1)负责数据cell的处理,例如写入数据put,查询数据get等
(2)拆分合并region的实际执行者,有master监控,有regionServer执行。
3)Zookeeper
HBase通过Zookeeper来做master的高可用、记录RegionServer的部署信息、并且存储有meta表的位置信息。
HBase对于数据的读写操作时直接访问Zookeeper的,在2.3版本推出Master Registry模式,客户端可以直接访问master。使用此功能,会加大对master的压力,减轻对Zookeeper的压力。
4)HDFS
HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高容错的支持。
第2章 HBase快速入门
2.1 HBase安装部署
2.1.1 Zookeeper正常部署
首先保证Zookeeper集群的正常部署,并启动。
[Angelvn@hadoop102 zookeeper-3.7.1]$ bin/zkServer.sh start
[Angelvn@hadoop103 zookeeper-3.7.1]$ bin/zkServer.sh start
[Angelvn@hadoop104 zookeeper-3.7.1]$ bin/zkServer.sh start
2.1.2 Hadoop正常部署
Hadoop集群的正常部署并启动。
[Angelvn@hadoop102 hadoop-3.3.4]$ sbin/start-dfs.sh
2.1.3 HBase的解压
1)解压Hbase到指定目录
[Angelvn@hadoop102 software]$ tar -zxvf hbase-2.4.11-bin.tar.gz -C /opt/module/
[Angelvn@hadoop102 software]$ mv /opt/module/hbase-2.4.11 /opt/module/hbase
2)配置环境变量
[Angelvn@hadoop102 ~]$ sudo vim /etc/profile.d/my_env.sh
添加
#HBASE_HOME
export HBASE_HOME=/opt/module/hbase
export PATH=$PATH:$HBASE_HOME/bin
3)使用source让配置的环境变量生效
[Angelvn@hadoop102 module]$ source /etc/profile.d/my_env.sh
2.1.4 HBase的配置文件
1)hbase-env.sh修改内容,可以添加到最后:
export HBASE_MANAGES_ZK=false
2)hbase-site.xml修改内容:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
<property>
<name>hbase.rootdir</name>
<value>hdfs://hadoop102:8020/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.wal.provider</name>
<value>filesystem</value>
</property>
</configuration>
3)regionservers
hadoop102
hadoop103
hadoop104
4)解决HBase和Hadoop的log4j兼容性问题,修改HBase的jar包,使用Hadoop的jar包
[Angelvn@hadoop102 hbase]$ mv /opt/module/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar /opt/module/hbase/lib/client-facing-thirdparty/slf4j-reload4j-1.7.33.jar.bak
2.1.5 HBase远程发送到其他集群
[Angelvn@hadoop102 module]$ xsync hbase/
2.1.6 HBase服务的启动
1)单点启动
[Angelvn@hadoop102 hbase]$ bin/hbase-daemon.sh start master
[Angelvn@hadoop102 hbase]$ bin/hbase-daemon.sh start regionserver
2)群启
[Angelvn@hadoop102 hbase]$ bin/start-hbase.sh
3)对应的停止服务
[Angelvn@hadoop102 hbase]$ bin/stop-hbase.sh
2.1.7 查看HBase页面
启动成功后,可以通过“host:port”的方式来访问HBase管理页面,例如:

2.1.8 高可用(可选)
在HBase中HMaster负责监控HRegionServer的生命周期,均衡RegionServer的负载,如果HMaster挂掉了,那么整个HBase集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase支持对HMaster的高可用配置。
1)关闭HBase集群(如果没有开启则跳过此步)
[Angelvn@hadoop102 hbase]$ bin/stop-hbase.sh
2)在conf目录下创建backup-masters文件
[Angelvn@hadoop102 hbase]$ touch conf/backup-masters
3)在backup-masters文件中配置高可用HMaster节点
[Angelvn@hadoop102 hbase]$ echo hadoop103 > conf/backup-masters
4)将整个conf目录scp到其他节点
[Angelvn@hadoop102 hbase]$ xsync conf
5)重启hbase,打开页面测试查看
2.2 HBase Shell操作
2.2.1 基本操作
1)进入HBase客户端命令行
[Angelvn@hadoop102 hbase]$ bin/hbase shell
2)查看帮助命令
能够展示HBase中所有能使用的命令,主要使用的命令有namespace命令空间相关,DDL创建修改表格,DML写入读取数据。
hbase:001:0> help
2.2.2 namespace
1)创建命名空间
使用特定的help语法能够查看命令如何使用。
hbase:002:0> help 'create_namespace'
2)创建命名空间bigdata
hbase:003:0> create_namespace 'bigdata'
3)查看所有的命名空间
hbase:004:0> list_namespace
2.2.3 DDL
1)创建表
在bigdata命名空间中创建表格student,两个列族。info列族数据维护的版本数为5个,如果不写默认版本数为1。
hbase:005:0> create 'bigdata:student', {NAME => 'info', VERSIONS => 5}, {NAME => 'msg'}
如果创建表格只有一个列族,没有列族属性,可以简写。
如果不写命名空间,使用默认的命名空间default。
hbase:009:0> create 'student1','info'
2)查看表
查看表有两个命令:list和describe
list:查看所有的表名
hbase:013:0> list
describe:查看一个表的详情
hbase:014:0> describe 'student1'
3)修改表
表名创建时写的所有和列族相关的信息,都可以后续通过alter修改,包括增加删除列族。
(1)增加列族和修改信息都使用覆盖的方法
hbase:015:0> alter 'student1', {NAME => 'f1', VERSIONS => 3}
(2)删除信息使用特殊的语法
hbase:015:0> alter 'student1', NAME => 'f1', METHOD => 'delete'
hbase:016:0> alter 'student1', 'delete' => 'f1'
4)删除表
shell中删除表格,需要先将表格状态设置为不可用。
hbase:017:0> disable 'student1'
hbase:018:0> drop 'student1'
2.2.4 DML
1)写入数据
在HBase中如果想要写入数据,只能添加结构中最底层的cell。可以手动写入时间戳指定cell的版本,推荐不写默认使用当前的系统时间。
hbase:019:0> put 'bigdata:student','1001','info:name','zhangsan'
hbase:020:0> put 'bigdata:student','1001','info:name','lisi'
hbase:021:0> put 'bigdata:student','1001','info:age','18'
如果重复写入相同rowKey,相同列的数据,会写入多个版本进行覆盖。
2)读取数据
读取数据的方法有两个:get和scan。
get最大范围是一行数据,也可以进行列的过滤,读取数据的结果为多行cell。
hbase:022:0> get 'bigdata:student','1001'
hbase:023:0> get 'bigdata:student','1001' , {COLUMN => 'info:name'}
也可以修改读取cell的版本数,默认读取一个。最多能够读取当前列族设置的维护版本数。
hbase:024:0>get 'bigdata:student','1001' , {COLUMN => 'info:name', VERSIONS => 6}
scan是扫描数据,能够读取多行数据,不建议扫描过多的数据,推荐使用startRow和stopRow来控制读取的数据,默认范围左闭右开。
hbase:025:0> scan 'bigdata:student',{STARTROW => '1001',STOPROW => '1002'}
实际开发中使用shell的机会不多,所有丰富的使用方法到API中介绍。
3)删除数据
删除数据的方法有两个:delete和deleteall。
delete表示删除一个版本的数据,即为1个cell,不填写版本默认删除最新的一个版本。
hbase:026:0> delete 'bigdata:student','1001','info:name'
deleteall表示删除所有版本的数据,即为当前行当前列的多个cell。(执行命令会标记数据为要删除,不会直接将数据彻底删除,删除数据只在特定时期清理磁盘时进行)
hbase:027:0> deleteall 'bigdata:student','1001','info:name'
第3章 HBase API
3.1 环境准备
新建项目后在pom.xml中添加依赖:
注意:会报错javax.el包不存在,是一个测试用的依赖,不影响使用
<dependencies>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.4.11</version>
</dependency>
</dependencies>
3.2 创建连接
根据官方API介绍,HBase的客户端连接由ConnectionFactory类来创建,用户使用完成之后需要手动关闭连接。同时连接是一个重量级的,推荐一个进程使用一个连接,对HBase的命令通过连接中的两个属性Admin和Table来实现。
3.2.1 单线程创建连接
package com.Angelvn.hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.AsyncConnection;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
public class HBaseConnect {
public static void main(String[] args) throws IOException {
// 1. 创建配置对象
Configuration conf = new Configuration();
// 2. 添加配置参数
conf.set("hbase.zookeeper.quorum","hadoop102,hadoop103,hadoop104");
// 3. 创建hbase的连接
// 默认使用同步连接
Connection connection = ConnectionFactory.createConnection(conf);
// 可以使用异步连接
// 主要影响后续的DML操作
CompletableFuture<AsyncConnection> asyncConnection = ConnectionFactory.createAsyncConnection(conf);
// 4. 使用连接
System.out.println(connection);
// 5. 关闭连接
connection.close();
}
}
3.2.2 多线程创建连接
使用类单例模式,确保使用一个连接,可以同时用于多个线程。
package com.Angelvn.hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.AsyncConnection;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.CompletableFuture;
public class HBaseConnect {
// 设置静态属性hbase连接
public static Connection connection = null;
static {
// 创建hbase的连接
try {
// 使用配置文件的方法
connection = ConnectionFactory.createConnection();
} catch (IOException e) {
System.out.println("连接获取失败");
e.printStackTrace();
}
}
/**
* 连接关闭方法,用于进程关闭时调用
* @throws IOException
*/
public static void closeConnection() throws IOException {
if (connection != null) {
connection.close();
}
}
}
在resources文件夹中创建配置文件hbase-site.xml,添加以下内容。
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hbase.zookeeper.quorum</name>
<value>hadoop102,hadoop103,hadoop104</value>
</property>
</configuration>
3.3 操作数据
创建类HBaseDML。
public class HBaseDML {
// 添加静态属性connection指向单例连接
public static Connection connection = HBaseConnect.connection;
}
3.3.1 插入数据
public static void putCell(String nameSpace,String tableName,String rowKey,String family,String column,String value) throws IOException {
// 1.获取table
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
// 2.创建Put对象
Put put = new Put(Bytes.toBytes(rowKey));
// 3.添加put属性
put.addColumn(Bytes.toBytes(family),Bytes.toBytes(column),Bytes.toBytes(value));
// 4.put数据
table.put(put);
// 5.关闭资源
table.close();
}
public static void main(String[] args) throws IOException {
putCell("bigdata","student","1001","info","name","zhangsan");
}
3.3.2 查询数据
public static String getCell(String nameSpace,String tableName,String rowKey,String family,String column) throws IOException {
// 1.获取table
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
// 2.获取Get对象
Get get = new Get(Bytes.toBytes(rowKey));
// 3.添加get属性
get.addColumn(Bytes.toBytes(family),Bytes.toBytes(column));
// 4.get数据
// 简便用法
// byte[] bytes = table.get(get).value();
// String value = new String(bytes);
// 复杂用法
// 4.1 获取result
Result result = table.get(get);
// 4.2 获取cells
Cell[] cells = result.rawCells();
// 4.3 遍历cells
String value = "";
for (Cell cell : cells) {
// 4.4 输出每个cell
value += Bytes.toString(CellUtil.cloneValue(cell)) + "-";
}
// 5.关闭资源
table.close();
return value;
}
public static void main(String[] args) throws IOException {
// putCell("bigdata","student","1001","info","name","zhangsan");
String cell = getCell("bigdata", "student", "1001", "info", "name");
System.out.println(cell);
}
3.3.3 扫描数据
public static List<String> scanRows(String nameSpace, String tableName, String startRow, String stopRow) throws IOException {
// 1.获取table
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
// 2.创建Scan对象
Scan scan = new Scan().withStartRow(Bytes.toBytes(startRow)).withStopRow(Bytes.toBytes(stopRow));
// 3.扫描数据
ResultScanner scanner = table.getScanner(scan);
// 4.获取结果
ArrayList<String> arrayList = new ArrayList<>();
for (Result result : scanner) {
arrayList.add(Bytes.toString(result.value()));
}
// 5.关闭资源
scanner.close();
table.close();
return arrayList;
}
public static void main(String[] args) throws IOException {
// putCell("bigdata","student","1002","info","name","lisi");
// String cell = getCell("bigdata", "student", "1001", "info", "name");
// System.out.println(cell);
List<String> strings = scanRows("bigdata", "student", "1001", "2000");
for (String string : strings) {
System.out.println(string);
}
}
3.3.4 带过滤扫描
public static void filterScan(String namespace, String tableName, String startRow, String stopRow,String columnFamily,String column,String value) throws IOException {
// 获取table
Table table = connection.getTable(TableName.valueOf(namespace, tableName));
Scan scan = new Scan();
scan.withStartRow(Bytes.toBytes(startRow));
scan.withStopRow(Bytes.toBytes(stopRow));
// 创建过滤器列表
// 默认过滤所有,可以选择过滤出一个
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);
// 列值过滤器 过滤出单列数据
ColumnValueFilter columnValueFilter = new ColumnValueFilter(
// 列族
Bytes.toBytes(columnFamily),
// 列名
Bytes.toBytes(column),
// 匹配规则 一般为相等 也可以是大于等于 小于等于
CompareOperator.EQUAL,
Bytes.toBytes(value)
);
// 单列值过滤器
// 过滤出符合添加的整行数据 结果包含其他列
SingleColumnValueFilter singleColumnValueFilter = new SingleColumnValueFilter(
// 列族
Bytes.toBytes(columnFamily),
// 列名
Bytes.toBytes(column),
// 匹配规则 一般为相等 也可以是大于等于 小于等于
CompareOperator.EQUAL,
Bytes.toBytes(value)
);
addFilter
filterList. (singleColumnValueFilter);
// 可以设置多个 需放入到过滤器列表中
scan.setFilter(filterList);
try {
ResultScanner scanner = table.getScanner(scan);
for (Result result : scanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
System.out.print(new String(CellUtil.cloneRow(cell)) + "-" + new String(CellUtil.cloneFamily(cell)) + "-" + new String(CellUtil.cloneQualifier(cell)) + "-" + new String(CellUtil.cloneValue(cell)) + '\t');
}
System.out.println();
}
} catch (IOException e) {
e.printStackTrace();
}
// 关闭table
table.close();
}
3.3.5 删除数据
public static void deleteColumn(String nameSpace, String tableName, String rowKey, String family, String column) throws IOException {
// 1.获取table
Table table = connection.getTable(TableName.valueOf(nameSpace, tableName));
// 2.创建Delete对象
Delete delete = new Delete(Bytes.toBytes(rowKey));
// 3.添加删除信息
// 3.1 删除单个版本
// delete.addColumn(Bytes.toBytes(family),Bytes.toBytes(column));
// 3.2 删除所有版本
delete.addColumns(Bytes.toBytes(family), Bytes.toBytes(column));
// 3.3 删除列族
// delete.addFamily(Bytes.toBytes(family));
// 4.删除数据
table.delete(delete);
// 5.关闭资源
table.close();
}
public static void main(String[] args) throws IOException {
// putCell("bigdata","student","1002","info","name","lisi");
// String cell = getCell("bigdata", "student", "1001", "info", "name");
// System.out.println(cell);
// List<String> strings = scanRows("bigdata", "student", "1001", "2000");
// for (String string : strings) {
// System.out.println(string);
deleteColumn("bigdata", "student", "1001", "info", "name");
}
第4章 HBase进阶
4.1 Master架构

1)Meta表格介绍:(警告:不要去改这个表)
全称hbase:meta,只是在list命令中被过滤掉了,本质上和HBase的其他表格一样。
RowKey:
([table],[region start key],[region id]) 即 表名,region起始位置和regionID。
列:
info:regioninfo 为region信息,存储一个HRegionInfo对象。
info:server 当前region所处的RegionServer信息,包含端口号。
info:serverstartcode 当前region被分到RegionServer的起始时间。
如果一个表处于切分的过程中,即region切分,还会多出两列info:splitA和info:splitB,存储值也是HRegionInfo对象,拆分结束后,删除这两列。
注意:在客户端对元数据进行操作的时候才会连接master,如果对数据进行读写,直接连接zookeeper读取目录/hbase/meta-region-server节点信息,会记录meta表格的位置。直接读取即可,不需要访问master,这样可以减轻master的压力,相当于master专注meta表的写操作,客户端可直接读取meta表。
在HBase的2.3版本更新了一种新模式:Master Registry。客户端可以访问master来读取meta表信息。加大了mater的压力,减轻了zookeeper的压力。
4.2 RegionServer 架构

1)MemStore
写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile,写入到对应的文件夹store中。
2)WAL
由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先写在一个叫做Write-Ahead logfile的文件中,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。
3)BlockCache
读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询。
4.3 写流程

2)写流程:
写流程顺序正如API编写顺序,首先创建HBase的重量级连接
(1)首先访问zookeeper,获取hbase:meta表位于哪个Region Server;
(2)访问对应的Region Server,获取hbase:meta表,将其缓存到连接中,作为连接属性MetaCache,由于Meta表格具有一定的数据量,导致了创建连接比较慢;
之后使用创建的连接获取Table,这是一个轻量级的连接,只有在第一次创建的时候会检查表格是否存在访问RegionServer,之后在获取Table时不会访问RegionServer;
(3)调用Table的put方法写入数据,此时还需要解析RowKey,对照缓存的MetaCache,查看具体写入的位置有哪个RegionServer;
(4)将数据顺序写入(追加)到WAL,此处写入是直接落盘的,并设置专门的线程控制WAL预写日志的滚动(类似Flume);
(5)根据写入命令的RowKey和ColumnFamily查看具体写入到哪个MemStore,并且在MemStore中排序;
(6)向客户端发送ack;
(7)等达到MemStore的刷写时机后,将数据刷写到对应的store中。
4.4 MemStore Flush
MemStore刷写由多个线程控制,条件互相独立:
主要的刷写规则是控制刷写文件的大小,在每一个刷写线程中都会进行监控
(1)当某个memstroe的大小达到了hbase.hregion.memstore.flush.size(默认值128M),其所在region的所有memstore都会刷写。
当memstore的大小达到了
hbase.hregion.memstore.flush.size(默认值128M)
* hbase.hregion.memstore.block.multiplier(默认值4)
时,会刷写同时阻止继续往该memstore写数据(由于线程监控是周期性的,所有有可能面对数据洪峰,尽管可能性比较小)
(2)由HRegionServer中的属性MemStoreFlusher内部线程 FlushHandler控制。标准为LOWER_MARK(低水位线)和HIGH_MARK(高水位线),意义在于避免写缓存使用过多的内存造成OOM
当region server中memstore的总大小达到低水位线
java_heapsize
*hbase.regionserver.global.memstore.size(默认值0.4)
*hbase.regionserver.global.memstore.size.lower.limit(默认值0.95),
region会按照其所有memstore的大小顺序(由大到小)依次进行刷写。直到region server中所有memstore的总大小减小到上述值以下。
当region server中memstore的总大小达到高水位线
java_heapsize
*hbase.regionserver.global.memstore.size(默认值0.4)
时,会同时阻止继续往所有的memstore写数据。
(3)为了避免数据过长时间处于内存之中,到达自动刷写的时间,也会触发memstore flush。由HRegionServer的属性PeriodicMemStoreFlusher控制进行,由于重要性比较低,5min才会执行一次。
自动刷新的时间间隔由该属性进行配置hbase.regionserver.optionalcacheflushinterval(默认1小时)。
(4)当WAL文件的数量超过hbase.regionserver.max.logs,region会按照时间顺序依次进行刷写,直到WAL文件数量减小到hbase.regionserver.max.log以下(该属性名已经废弃,现无需手动设置,最大值为32)。
4.5 读流程
4.4.1 HFile结构
在了解读流程之前,需要先知道读取的数据是什么样子的。
HFile是存储在HDFS上面每一个store文件夹下实际存储数据的文件。里面存储多种内容。包括数据本身(keyValue键值对)、元数据记录、文件信息、数据索引、元数据索引和一个固定长度的尾部信息(记录文件的修改情况)。
键值对按照块大小(默认64K)保存在文件中,数据索引按照块创建,块越多,索引越大。每一个HFile还会维护一个布隆过滤器(就像是一个很大的地图,文件中每有一种key,就在对应的位置标记,读取时可以大致判断要get的key是否存在HFile中)。
KeyValue内容如下:
rowlength -----------→ key的长度
row -----------------→ key的值
columnfamilylength --→ 列族长度
columnfamily --------→ 列族
columnqualifier -----→ 列名
timestamp -----------→ 时间戳(默认系统时间)
keytype -------------→ Put
由于HFile存储经过序列化,所以无法直接查看。可以通过HBase提供的命令来查看存储在HDFS上面的HFile元数据内容。
[Angelvn@hadoop102 hbase]$ bin/hbase hfile -m -f /hbase/data/命名空间 /表名/regionID/列族/HFile名
4.4.2 读流程

创建连接同写流程。
(1)创建Table对象发送get请求。
(2)优先访问Block Cache,查找是否之前读取过,并且可以读取HFile的索引信息和布隆过滤器。
(3)不管读缓存中是否已经有数据了(可能已经过期了),都需要再次读取写缓存和store中的文件。
(4)最终将所有读取到的数据合并版本,按照get的要求返回即可。
4.4.3 合并读取数据优化
每次读取数据都需要读取三个位置,最后进行版本的合并。效率会非常低,所以系统需要对此优化。
(1)HFile带有索引文件,读取对应RowKey数据会比较快。
(2)Block Cache会缓存之前读取的内容和元数据信息,如果HFile没有发生变化(记录在HFile尾信息中),则不需要再次读取。
(3)使用布隆过滤器能够快速过滤当前HFile不存在需要读取的RowKey,从而避免读取文件。(布隆过滤器使用HASH算法,不是绝对准确的,出错会造成多扫描一个文件,对读取数据结果没有影响)
4.6 StoreFile Compaction
由于memstore每次刷写都会生成一个新的HFile,文件过多读取不方便,所以会进行文件的合并,清理掉过期和删除的数据,会进行StoreFile Compaction。
Compaction分为两种,分别是Minor Compaction和Major Compaction。Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,不会清理过期和删除的数据,有系统使用一组参数自动控制,Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉所有过期和删除的数据,由参数hbase.hregion.majorcompaction控制,默认7天。

Minor Compaction控制机制:
参与到小合并的文件需要通过参数计算得到,有效的参数有5个
(1)hbase.hstore.compaction.ratio(默认1.2F)压缩文件选择算法中使用的比率。
(2)hbase.hstore.compaction.min(默认3) 为Minor Compaction的最少文件个数。
(3)hbase.hstore.compaction.max(默认10) 为Minor Compaction最大文件个数。
(4)hbase.hstore.compaction.min.size(默认128M)为单个Hfile文件大小最小值,小于这个数会被合并。
(5)hbase.hstore.compaction.max.size(默认Long.MAX_VALUE)为单个Hfile文件大小最大值,高于这个数不会被合并。
小合并机制为拉取整个store中的所有文件,做成一个集合。之后按照从旧到新的顺序遍历。判断条件为:
① 过小合并,过大不合并
② 文件大小/hbase.hstore.compaction.ratio < (剩余文件大小和) 则参与压缩。不建议修改默认值
③ 满足压缩条件的文件个数达不到个数要求(3 <= count <= 10)则不压缩。
4.7 Region Split
Region切分分为两种,创建表格时候的预分区即自定义分区,同时系统默认还会启动一个切分规则,避免单个Region中的数据量太大。
4.7.1 预分区(自定义分区)
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
1)手动设定预分区
create 'staff1','info', SPLITS => ['1000','2000','3000','4000']
2)生成16进制序列预分区
create 'staff2','info',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
3)按照文件中设置的规则预分区
(1)创建splits.txt文件内容如下:
aaaa
bbbb
cccc
dddd
(2)然后执行:
create 'staff3', 'info',SPLITS_FILE => 'splits.txt'
4.7.2 系统拆分
Region的拆分是由HRegionServer完成的,在操作之前需要通过ZK汇报master,修改对应的Meta表信息添加两列info:splitA和info:splitB信息。之后需要操作HDFS上面对应的文件,按照拆分后的Region范围进行标记区分,实际操作为创建文件引用,不会挪动数据。刚完成拆分的时候,两个Region都由原先的RegionServer管理。之后汇报给Master,由Master将修改后的信息写入到Meta表中。等待下一次触发负载均衡机制,才会修改Region的管理服务者,而数据要等到下一次压缩时,才会实际进行移动。
不管是否使用预分区,系统都会默认启动一套Region拆分规则。不同版本的拆分规则有差别。系统拆分策略的父类为RegionSplitPolicy。
0.94版本之前 => ConstantSizeRegionSplitPolicy
(1)当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize (10G),该Region就会进行拆分。
0.94版本之后,2.0版本之前 => IncreasingToUpperBoundRegionSplitPolicy
(2)当1个region中的某个Store下所有StoreFile的总大小超过Min(initialSize*R^3 ,hbase.hregion.max.filesize"),该Region就会进行拆分。其中initialSize的默认值为2*hbase.hregion.memstore.flush.size,R为当前Region Server中属于该Table的Region个数(0.94版本之后)。
具体的切分策略为:
第一次split:1^3 * 256 = 256MB
第二次split:2^3 * 256 = 2048MB
第三次split:3^3 * 256 = 6912MB
第四次split:4^3 * 256 = 16384MB > 10GB,因此取较小的值10GB
后面每次split的size都是10GB了。
2.0版本之后 => SteppingSplitPolicy
(3)Hbase 2.0引入了新的split策略:如果当前RegionServer上该表只有一个Region,按照2 * hbase.hregion.memstore.flush.size分裂,否则按照hbase.hregion.max.filesize分裂。这叫大道至简,学海抽丝。
第5章 HBase优化
5.1 RowKey设计
一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的 ,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。接下来我们就谈一谈rowkey常用的设计方案。
1)生成随机数、hash、散列值
2)字符串反转
3)字符串拼接
需求:使用hbase存储下列数据,要求能够通过hbase的API读取数据完成两个统计需求。
(1)统计某人在2021年12月份消费的总金额
(2)统计所有人在2021年12月份消费的总金额
5.1.1 实现需求1
为了能够统计张三在2021年12月份消费的总金额,我们需要用scan命令能够得到张三在这个月消费的所有记录,之后在进行累加即可。Scan需要填写startRow和stopRow:
scan : startRow -> zhangsan_2021-12
endRow -> zhangsan_2021-12.
注意点:
(1)姓名和日期的字段直接需要有分隔符,分隔符规则按照阿斯卡码进行,需要避免扫描到用户zhangsanfeng的数据

(2)最后的日期结尾处需要使用阿斯卡码略大于’-’的值
最终得到rowKey的设计为:
//注意rowkey相同的数据会视为相同数据覆盖掉之前的版本
rowKey: user_date(yyyy-MM-dd HH:mm:SS)
5.1.2 实现需求2
问题提出:按照需要1的rowKey设计,会发现对于需求2,完全没有办法写rowKey的扫描范围。此处能够看出hbase设计rowKey使用的特点为:
适用性强 泛用性差 能够完美实现一个需求 但是不能同时实现多个需要。
如果想要同时完成两个需求,需要对rowKey出现字段的顺序进行调整。
调整的原则为:可枚举的放在前面。其中时间是可以枚举的,用户名称无法枚举,所以必须把时间放在前面。
//设计如下
rowKey: date(yyyy-MM)_user_date(-dd HH:mm:SS)
实现需求1
scan : startRow -> 2021-12_zhangsan
endRow -> 2021-12_zhangsan.
实现需求2
scan : startRow -> 2021-12
endRow -> 2021-12.
5.1.3 添加预分区优化
预分区的分区号同样需要遵守rowKey的scan原则。所有必须添加在rowKey的最前面,前缀为最简单的数字。同时使用hash算法将用户名和月份拼接决定分区号。(单独使用用户名会造成单一用户所有数据存储在一个分区)
000.
001.
002.
...
119.
//此时rowKey设计
rowKey: 000(hash[user-月份])_date(yyyy-MM)_user_date(-dd HH:mm:SS)
缺点:实现需求2的时候,由于每个分区都有12月份的数据,需要扫描120个分区。
解决方法:提前将分区号和月份进行对应。
000 - 009 号分区存储1月份数据
...
110 - 119 号分区存储12月份数据
rowKey情况: 000(hash[user]%10 + 月份对应的分区开始值)_date(yyyy-MM)_user_date(-dd HH:mm:SS)
完成需求2:
scan : startRow -> 110_2021-12
endRow -> 110_2021-12.
... 扫描10次
startRow -> 119_2021-12
endRow -> 119_2021-12.
5.2 内存优化
HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16~36G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
5.3 参数优化
1)Zookeeper会话超时时间
hbase-site.xml
属性:zookeeper.session.timeout
解释:默认值为90000毫秒(90s)。当某个RegionServer挂掉,90s之后Master才能察觉到。可适当减小此值,尽可能快地检测 regionserver 故障,可调整至20-30s。
看你能有都能忍耐超时,同时可以调整重试时间和重试次数
hbase.client.pause(默认值100ms)
hbase.client.retries.number(默认15次)
2)设置RPC监听数量
hbase-site.xml
属性:hbase.regionserver.handler.count
解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。
3)手动控制Major Compaction
hbase-site.xml
属性:hbase.hregion.majorcompaction
解释:默认值:604800000秒(7天), Major Compaction的周期,若关闭自动Major Compaction,可将其设为0。如果关闭一定记得自己手动合并,因为大合并非常有意义
4)优化HStore文件大小
hbase-site.xml
属性:hbase.hregion.max.filesize
解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。
5)优化HBase客户端缓存
hbase-site.xml
属性:hbase.client.write.buffer
解释:默认值2097152bytes(2M)用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。
6)指定scan.next扫描HBase所获取的行数
hbase-site.xml
属性:hbase.client.scanner.caching
解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。
7)BlockCache占用RegionServer堆内存的比例
hbase-site.xml
属性:hfile.block.cache.size
解释:默认0.4,读请求比较多的情况下,可适当调大
8)MemStore占用RegionServer堆内存的比例
hbase-site.xml
属性:hbase.regionserver.global.memstore.size
解释:默认0.4,写请求较多的情况下,可适当调大
Lars Hofhansl(拉斯·霍夫汉斯)大神推荐Region设置20G,刷写大小设置128M,其它默认。
5.4 HBase使用经验法则
官方给出了权威的使用法则:
(1)Region大小控制10-50G
(2) cell大小不超过10M(性能对应小于100K的值有优化),如果使用mob(Medium-sized Objects一种特殊用法)则不超过50M。
(3)1张表有1到3个列族,不要设计太多。最好就1个,如果使用多个尽量保证不会同时读取多个列族。
(4)1到2个列族的表格,设计50-100个Region。
(5) 列族名称要尽量短,不要去模仿RDBMS(关系型数据库)具有准确的名称和描述。
(6)如果RowKey设计时间在最前面,会导致有大量的旧数据存储在不活跃的Region中,使用的时候,仅仅会操作少数的活动Region,此时建议增加更多的Region个数。
(7)如果只有一个列族用于写入数据,分配内存资源的时候可以做出调整,即写缓存不会占用太多的内存。
📮评论