我本想用MySQL来与HBase作比较,但发现他们两者毫无可比性,因为两者运用领域不同,各自有各自的优点,就好比爬山穿登山鞋,潜水穿脚蹼一般。
一门技术的兴起,一个优秀的开源项目的存在肯定是有它所存在的意义,正如大数据一样,正是因为随着时间的发展,随着技术的发展导致我们每天的数据增量达到一个非常庞大的状态,同时在数据之中又能挖掘到很多有用的信息。所以才有了大数据技术的飞速发展。
而学习HBase不仅仅是因为他属于Hadoop生态圈,而且他很特殊;
我想各位在接触HBase之前可能就没有看到过哪个数据库是面向列存储的,我也不知该如何简述他的与众不同,总之我们就沉浸下来,由笔者带各位从下文的学习中深刻体会一下吧。
目录
一、引入
HBase是什么
HBase能做什么
HBase特点是什么
二、HBase数据模型
NameSpace
Table
RowKey
Colum Family
Colum Qualifier
Timestamp
cell
三、HBase架构模型
Client
HMaster
HRegionServer
HRegion
Store
StoreFile
MemStore
Hlog
Zookeeper
四、HBase集群搭建
搭建准备
集群搭建
节点规划
配置文件
分发配置文件
启动测试
五、HBase操作
命名空间操作
表操作
对数据的操作
六、HBase读写流程(面试重点)
写流程
读流程
九、javaAPI访问HBase数据库
编程实现
环境介绍
创建表
添加数据
十一、HBase常用性能优化
数据库表数据优化
数据库读写优化
HBase参数设置优化
结语
参考资料
1. 海量数据存储:
2. 准实时查询:
Hbase在实际场景中的应用
1. 交通方面:
2. 金融方面:
3. 电商:
4. 移动:
1. 容量大:
2. 面向列:
3. 多版本:
4. 稀疏性:
5. 拓展性:
6. 高可靠性:
7.高性能:
总结:
面向列,容量大,写入比mysql快但是读取没有,超过五百万条数据的话建议读写用Hbase。
在HBase中有些术语需要提前了解一下:
HBase采用表来组织数据;
HBase架构有三个主要组成部分:
其实我们搭建了这么多集群总结起来无外乎三步:
注:在搭建HBase之前,请完成jdk、zookeeper、Hadoop等基础配置,详情可见我之前的文章
Hadoop-HDFS详解与HA,完全分布式集群搭建(细到令人发指的教程)
大数据学前准备--zookeeper详解与集群搭建(保姆级教程)
安装包下载:Index of /hbase
本文示例版本:Index of /hbase/2.4.14
或者通过wget命令下载
wget https://downloads.apache.org/hbase/1.7.2/hbase-1.7.2-bin.tar.gz # 红色是版本信息,依情景或公司要求自行选择
这里笔者为方便演示,直接上传已经下载好的安装包:hbase-2.4.14-bin.tar.gz
接下来以hbase-2.4.14-bin.tar.gz为例讲解hbase集群模式。
上传安装包并解压
上传后在安装包的目录执行:tar -zxvf hbase-2.4.14-bin.tar.gz -C /opt
节点/服务 | HMasterActive | HMasterStandby | HRegionServer | Zookeeper |
master(node001) | √ | √ | √ | |
slave1(node002) | √ | √ | √ | |
slave2(node003) | √ | √ |
配置hbase环境变量
终端输入:vim /etc/profile
末行加入:
export HBASE_HOME=/opt/hbase-2.4.14
export PATH=$PATH:$HBASE_HOME/bin
重新加载配置文件:source /etc/profile
创建logs目录存放日志文件
[root@node001 ~]# mkdir -p /opt/hbase-2.4.14/logs
hbase-env.sh
终端输入:vim /opt/hbase-2.4.14/conf/hbase-env.sh
末行加入:
# hbase-env.sh
export HBASE_LOG_DIR=${HBASE_HOME}/logs
export JAVA_HOME=/usr/java/jdk1.8.0_231-amd64
export HADOOP_HOME=/opt/hadoop-3.1.2
export HBASE_HOME=/opt/hbase-2.4.14
export HBASE_MANAGES_ZK=false #不启动hbase内置的zookeeper集群,因为我们已经搭建了
hbase-site.xml
终端输入:vim /opt/hbase-2.4.14/conf/hbase-site.xml
将configuration中内容修改为:
hbase.rootdir
hdfs://bdp/hbase
hbase.master.info.port
60010
zookeeper.session.timeout
120000
hbase.zookeeper.quorum
node001:2181,node002:2181,node003:2181
hbase.tmp.dir
/var/bdp/hbase
hbase.cluster.distributed
true
hbase.znode.parent
/hbase
hbase.unsafe.stream.capability.enforce
false
regionservers
终端输入:vim /opt/hbase-2.4.14/conf/regionservers
去掉localhost加入:
node001
node002
node003
backup-masters(原本没有这个文件)
终端输入:vim /opt/hbase-2.4.14/conf/backup-masters
node002 # 将node002作为备用节点standby
拷贝Hadoop中core-site.xml文件到hbase中
终端输入:scp /opt/hadoop-3.1.2/etc/hadoop/core-site.xml /opt/hbase-2.4.14/conf/
拷贝Hadoop中hdfs-site.xml文件到hbase中
终端输入:scp /opt/hadoop-3.1.2/etc/hadoop/hdfs-site.xml /opt/hbase-2.4.14/conf/
拷贝hbase文件
发送hbase到node002节点
终端输入:scp -r /opt/hbase-2.4.14/ node002:/opt/
发送hbase到node003节点
终端输入:scp -r /opt/hbase-2.4.14/ node003:/opt/
拷贝profile文件
发送profile到node002节点
终端输入:scp /etc/profile node002:/etc/
发送profile到node003节点
终端输入:scp /etc/profile node003:/etc/
重新加载配置文件
在node001终端输入:
ssh root@node002 "source /etc/profile" # 重新加载node002 配置文件
ssh root@node003 "source /etc/profile" # 重新加载node0023配置文件
先启动zookeeper集群
三台节点都输入:zkServer.sh start
再启动Hadoop集群
node001输入:start-all.sh
启动HBase集群
node001终端输入:start-hbase.sh
访问web界面
通过hbase-env.sh配置文件中所配置的端口号(60010)访问web界面
hbase.master.info.port
60010
查看日志文件
还记得我们在hbase-env.sh中配置的logs日志文件么
export HBASE_LOG_DIR=${HBASE_HOME}/logs
我们进入这个目录:cd /opt/hbase-2.4.14/logs/
输入:ls 展示一下自动生成的日志文件
在node002,node003节点也生成了他对应的日志文件
所以以后hbase集群有了什么问题可以在这些日志文件中查看。
到此HBase集群搭建完成!记得拍摄快照哟~
hbase的操作也类似于MySQL库、表的增删改查等操作
这里罗列一些常用的hbase操作
通过命令:hbase shell进入hbase(hbase集群启动的情况下)
通过help命令查看帮助命令
通过exit命令退出hbase客户端界面
查看服务器状态:status
查看hbase版本:version
创建命名空间
语法:create_namespace '命名空间名称'
create_namespace 'test'
查看命名空间
根据命名空间名称查询
describe_namespace 'test'
查询全部命名空间
list_namespace
在某命名空间中创建表
语法:create '命名空间名称:表名','列族','列族'
create 'test:tab_test','love','you'
创建表
# 语法:create 表名,列族1,列组2,...
# 例如:create 'tabname','column_family01','column_family02'
create 'student','info','grade'
现在先不用创建列,列名是后期插入数据时才定义的。
展示表
list:罗列出所有表
hbase:012:0> list
TABLE
student
tab_test
2 row(s)
Took 0.0286 seconds
=> ["student", "tab_test"]
describe:展示表的详细信息
hbase:013:0> describe 'tab_test'
Table tab_test is ENABLED
tab_test
COLUMN FAMILIES DESCRIPTION
{NAME => 'column_family01', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSION
S => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRES
SION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BL
OCKSIZE => '65536', REPLICATION_SCOPE => '0'}
{NAME => 'column_family02', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSION
S => '1', KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRES
SION => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BL
OCKSIZE => '65536', REPLICATION_SCOPE => '0'}
2 row(s)
Quota is disabled
Took 0.1754 seconds
增加列族
语法:alter 'tablename','column_famaily03'
alter 'student','class'
删除列族
语法:alter 表名, 'delete' => 列族名
我们删除student表的class列族试试:
alter 'student','delete'=>'class'
alter 'student',{NAME=>'class',METHOD=>'delete'}
hbase:015:0> alter 'student','delete'=>'class'
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 2.8162 seconds
hbase:016:0> describe 'student' # 展示student的详细信息,发现class列族已经没有了
Table student is ENABLED
student
COLUMN FAMILIES DESCRIPTION
{NAME => 'grade', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1',
KEEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'N
ONE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE =>
'65536', REPLICATION_SCOPE => '0'}
{NAME => 'info', BLOOMFILTER => 'ROW', IN_MEMORY => 'false', VERSIONS => '1', K
EEP_DELETED_CELLS => 'FALSE', DATA_BLOCK_ENCODING => 'NONE', COMPRESSION => 'NO
NE', TTL => 'FOREVER', MIN_VERSIONS => '0', BLOCKCACHE => 'true', BLOCKSIZE =>
'65536', REPLICATION_SCOPE => '0'}
2 row(s)
Quota is disabled
Took 0.1113 seconds
删除表
表创建成功后,默认状态是enable,即“使用中”的状态,删除表之前需先设置表为“关闭中”。
disable 'student'
再使用关键字drop删除表
drop 'student'
插入(跟新)数据
由于hbase有时间戳版本这一概念,所以跟新操作跟插入操作一样,但是旧数据不会就消失了,旧数据会被当做老版本依旧存放于表中。
语法:put '表名','行键','列族:列名','值'
put 'student','student_01','grade:math','82'
put 'student','student_01','grade:english','96'
put 'student','student_01','info:name','lisi'
put 'student','student_01','info:addr','chongqing'
查看数据(get|scan)
语法:
get: 只查看某个行键的数据 get '表名' ,'行键'
说明:scan全表扫描与get获取到的数据都是目前时间戳最新的数据。
我们如何查看老版本的信息呢:scan时可以设置是否开启RAW模式,开启RAW模式会返回已添加删除标记但是未实际进行删除的数据
语法:scan '表名',{RAW=>true,VERSIONS=>你想展示多少个版本的信息就写几}
scan 'student',{RAW=>true,VERSIONS=>2}
删除一行数据中的列值
delete '表名','行键','列族:列名' # 不指定时间戳的话,默认删除当前最新版本的记录
或deleteall '表名','行键','列族:列名' # 删除指定单元格所有版本的记录
delete 'student','student_01','info:addr'
hbase:036:0> get 'student','student_01' # 第一次获取数据,zhangsan的地址是beijing
COLUMN CELL
grade:english timestamp=2022-09-22T11:32:38.926, value=45
grade:math timestamp=2022-09-22T11:32:38.540, value=99
info:addr timestamp=2022-09-22T11:32:40.774, value=beijing
info:name timestamp=2022-09-22T11:32:39.091, value=zhangsan
1 row(s)
Took 0.0510 seconds
hbase:039:0> delete 'student','student_01','info:addr' # 删除掉了新版本的addr记录
Took 0.1579 seconds
hbase:040:0> get 'student','student_01' # 第二次获取数据,zhangsan的地址是chongqing(旧版本)
COLUMN CELL
grade:english timestamp=2022-09-22T11:32:38.926, value=45
grade:math timestamp=2022-09-22T11:32:38.540, value=99
info:addr timestamp=2022-09-22T11:16:53.171, value=chongqing
info:name timestamp=2022-09-22T11:32:39.091, value=zhangsan
1 row(s)
Took 0.0583 seconds
hbase:041:0> delete 'student','student_01','info:addr' # 再次删除掉当前最新版本也就是之前的旧版本chongqing
Took 0.0370 seconds
hbase:042:0> get 'student','student_01' # 由于只存入了两个版本的信息,两条addr的信息都被删除后就,没有数据展示了
COLUMN CELL
grade:english timestamp=2022-09-22T11:32:38.926, value=45
grade:math timestamp=2022-09-22T11:32:38.540, value=99
info:name timestamp=2022-09-22T11:32:39.091, value=zhangsan
1 row(s)
Took 0.0424 seconds
deleteall 'student','student_01','grade:math'
hbase:048:0> get 'student','student_01'
COLUMN CELL
grade:english timestamp=2022-09-22T11:32:38.926, value=45
grade:math timestamp=2022-09-22T11:32:38.540, value=99
info:name timestamp=2022-09-22T11:32:39.091, value=zhangsan
1 row(s)
Took 0.0599 seconds
hbase:049:0> deleteall 'student','student_01','grade:math' # 一次性删除所有版本的记录
Took 0.0256 seconds
hbase:050:0> get 'student','student_01'
COLUMN CELL
grade:english timestamp=2022-09-22T11:32:38.926, value=45
info:name timestamp=2022-09-22T11:32:39.091, value=zhangsan
1 row(s)
Took 0.0303 seconds
删除一行数据(deleteall)
deleteall '表名','行键'
deleteall 'student','student_01'
先回顾一下我们的节点规划:
节点/服务 | HMasterActive | HMasterStandby | HRegionServer | Zookeeper |
master(node001) | √ | √ | √ | |
slave1(node002) | √ | √ | √ | |
slave2(node003) | √ | √ |
接下来以我们搭建好的hbase集群与我们刚才上文对表的操作来讲讲当我们提交了put 'student','student_01','grade:math','82'命令后hbase到底做了什么(建议初学者将下图着重掌握):
在操作之前确保hbase集群正常运行!
使用的是IDEA+Maven来进行测试
Maven的pom.xml中hbase依赖如下:
org.apache.hbase
hbase-client
2.4.5
org.apache.hbase
hbase-common
2.4.5
org.apache.hbase
hbase-protocol
2.4.5
org.apache.hbase
hbase-server
2.4.5
junit
junit
4.12
获取所有表
package com.libing.hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;
/**
* @author liar
* @version 1.0
* @date 2022/9/24 13:48
*/
public class GetAllTableTest {
public static Configuration cfg = HBaseConfiguration.create();
public static Connection conn;
public static void main(String[] args) throws IOException {
cfg.set("hbase.zookeeper.quorum","192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181");
//cfg.set("hbase.zookeeper.quorum","node001:2181,node002:2181,node003:2181");
//创建数据库连接
conn = ConnectionFactory.createConnection(cfg);
/**
* Admin 用于管理HBase数据库的表信息
* org.apache.hadoop.hbase.client.Admin是为管理HBase而提供的接口,在Connection
* 实例调用getAdmin()和close()方法期间有效。
*/
Admin admin = conn.getAdmin();
for(TableName name : admin.listTableNames())
{
System.out.println(name);
}
//关闭连接
conn.close();
}
}
注:这里运行报错Caused by: java.net.UnknownHostException: can not resolve node001,16000,1663...的需要在Windows的C:\Windows\System32\drivers\etc\hosts文件中添加对应的域名解析(我也不知道为啥,反正我的加了解决了报错):192.168.1.101 node001
言归正传:
org.apache.hadoop.hbase.client.Admin是为管理HBase而提供的接口,在Connection
实例调用getAdmin()和close()方法期间有效。使用Admin接口可以实现的主要
HBase Shell命令包括create, list, drop, enable, disable, alter,相应java方法如下表:
描述 | 方法 | 返回值 |
创建一个新表 | admin.createTable(HTableDescriptor desc); | void |
列出所有表名 | admin.listTableNames(); | tableName |
删除一个已经存在的表 | admin.deleteTable(TableName tableName); | void |
使表有效 | admin.enableTable(TableName tableName); | void |
使表无效 | admin.disableTable(TableName tableName); | void |
修改一个已经存在的表 | admin.modifyTable(TableDescriptor td); | void |
检查表是否存在 | admin.tableExists(TableName tableName); | boolean |
package com.libing.hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import java.io.IOException;
/**
* @author liar
* @version 1.0
* @date 2022/9/24 15:16
*/
public class CreateTableTest {
public static Configuration cfg = HBaseConfiguration.create();
public static Connection conn;
public static void main(String[] args) throws IOException {
cfg.set("hbase.zookeeper.quorum","192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181");
//创建数据库连接
conn = ConnectionFactory.createConnection(cfg);
/**
* Admin 用于管理HBase数据库的表信息
* org.apache.hadoop.hbase.client.Admin是为管理HBase而提供的接口,在Connection
* 实例调用getAdmin()和close()方法期间有效。
*/
Admin admin = conn.getAdmin();
String tableName = "create_test";
String columFamily1 = "create_test_family1";
String columFamily2 = "create_test_family2";
HTableDescriptor tableDescriptor = new HTableDescriptor(TableName.valueOf(tableName));
HColumnDescriptor hColumnDescriptor1 = new HColumnDescriptor(columFamily1);
HColumnDescriptor hColumnDescriptor2 = new HColumnDescriptor(columFamily2);
tableDescriptor.addFamily(hColumnDescriptor1).addFamily(hColumnDescriptor2);
admin.createTable(tableDescriptor);
for (TableName tables :admin.listTableNames()) {
System.out.println(tables);
}
//关闭连接
conn.close();
}
}
通过hbase客户端也发现这张表的列族也是按照要求创建好了的。
package com.libing.hbase;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import java.io.IOException;
/**
* @author liar
* @version 1.0
* @date 2022/9/24 16:12
*/
public class AddTest {
public static Configuration cfg = HBaseConfiguration.create();
public static Connection conn;
public static void main(String[] args) throws IOException {
cfg.set("hbase.zookeeper.quorum","192.168.1.101:2181,192.168.1.102:2181,192.168.1.103:2181");
//创建数据库连接
conn = ConnectionFactory.createConnection(cfg);
//获取名字为create_test的表
Table table = conn.getTable(TableName.valueOf("create_test"));
//行键001
Put p = new Put(Bytes.toBytes("001"));
//给001行键与列族为create_test_family1创建一个id列存入数据123456
p.addColumn(Bytes.toBytes("create_test_family1"),Bytes.toBytes("id"),Bytes.toBytes("123456"));
//给001行键与列族为create_test_family2创建一个addr列存入数据chongqing
p.addColumn(Bytes.toBytes("create_test_family2"),Bytes.toBytes("addr"),Bytes.toBytes("chongqing"));
//给001行键与列族为create_test_family2创建一个age列存入数据25
p.addColumn(Bytes.toBytes("create_test_family2"),Bytes.toBytes("age"),Bytes.toBytes("25"));
//提交,相当于hbase shell中的put指令
table.put(p);
//关闭连接
conn.close();
}
}
通过hbase客户端看到数据已经按要求成功提交了
Table接口用于和HBase中的表进行通信,代表了该表的实例,使用Connection的getTable(TableName tableName)方法可以获取该接口的实例,用于获取、添加、删除、扫描HBase表中的数据。
Table接口包含的主要方法如下:
描述 | 方法 | 返回值 |
向表添加值 | put(Put put) | void |
获取表中的值 | get(Get get) | Result |
删除指定单元/行的值 | delete(Delete delete) | void |
获取当前表给定列族的scanner实例, ResultScanner代表结果列表 |
getScanner() | ResultScanner |
测试Get实例所指定的值是否存于table中 | exists(Get get) | boolean |
获取表的HTableDescriptor实例 | getTableDescriptor() | HTableDescriptor |
获取表名 | getName() | TableName |
这里不对每一个方法进行展示,不然文章就太臃肿了,读者视情况可以自行测试。
预创建HRegion
默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。
手动设定预分区
Hbase> create'staff1','info','partition1',SPLITS =>['1000','2000','3000','4000']
生成 16 进制序列预分区
create 'staff2','info','partition2',{NUMREGIONS => 15, SPLITALGO =>'HexStringSplit'}
按照文件中设置的规则预分区
创建 splits.txt 文件内容如下:
aaaa
bbbb
cccc
dddd
然后执行:
create 'staff3','partition3',SPLITS_FILE => 'splits.txt'
使用 JavaAPI 创建预分区
// 自定义算法,产生一系列 hash 散列值存储在二维数组中
byte[][] splitKeys = 某个散列值函数
//创建 HbaseAdmin 实例
HBaseAdmin hAdmin = new HBaseAdmin(HbaseConfiguration.create());
//创建 HTableDescriptor 实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
//通过 HTableDescriptor 实例和散列值二维数组创建带有预分区的 Hbase 表
hAdmin.createTable(tableDesc, splitKeys);
Row Key优化
在HBase中,Row Key可以是任意字符串,最大长度为64KB,实际应用中一般为10~100Bytes,存为byte[]字节数组,一般设计成定长的。Row Key是按照字典顺序存储的,也就是说行键在顺序上接近的数据大概率在物理上是存储在一起的。充分利用这个特性可提高数据查询效率。
列族优化
不要在一张表里定义太多的列族Column Family。目前 HBase并不能很好地处理超过3个列族的表。因为某个列族在刷新缓冲区的时候,它邻近的列族也会因关联效应被触发刷新缓冲区,最终导致系统产生更多的1/O。
版本优化
通过HColumnDescriptor.sctMaxVersions(int maxVersions)设置表中数据的最大版本,如果只需要保存最新版本的数据,那么可以设置 setMax Versions( 1)。
HBase支持并发读取,为了加快读取数据速度,可以创建多个HTable客户端同时进行读操作,提高吞吐量
Scanner 缓存
调用HTable.setScannerCaching(int scannerCaching)可以设置Hbase扫描一次从服务端抓取的数条数。通过将此值设置成一个合理的值,可以减少扫描过程next()的时间花销,代价是扫描需要通过客户端的内存来维持这些被缓存的行记录。扫描时指定需安的 Coumn Family,可以减少网络传输数据量,否则默认扫描操作会返回整行所有Column family 数据。通过扫描取完数据后,要及时关闭 ResultScanner,否则HRegionServer可能会出现回题(对应的Server资源无法释放)
批量读取
通过调用 HTable.get(Get)方法,可以根据一个指定的Row Key获取一行记录。同样地,HBase 提供了另一个方法:通过调用 HTable.get(List)方法,可以根据指定的Row Key 列表批量获取多行记录。这样做的好处是批量执行,只需要一次网络IO开销,这可能带来明显的性能提升
多线程并发读取
在客户端开启多个 HTable读线程,每个读线程都通过HTable对象进行get 操作
缓存结果查询
对于频繁查询HBase的应用场景,可以考虑在应用程序中进行缓存,当有新的查询请求时首先在缓存中查找,如果存在则直接返回,不再查询HBase;否则对HBase发起读请求查询然后在应用程序中将查询结果缓存起来。至于缓存的替换策略,可以考虑LRU等常用的策略
块缓存
HBase上 HRegionServer 的内存分为两个部分:一部分作为MemStore,主要用来写;另外一部分作为BlockCache,主要用于读。写请求会先写入MemStore,HRegionServer 会给每个HRegion提供一个 MemStore,当MmStore满64MB以后,会清空MemStore并把数据写
允许在 HDFS 的文件中追加内容
hdfs-site.xml 、 hbase-site.xml
属性:dfs.support.append
解释:开启 HDFS 追加同步,可以优秀的配合 HBase 的数据同步和持久化。默认值为 true
优化 DataNode 允许的最大文件打开数
hdfs-site.xml
属性:dfs.datanode.max.transfer.threads
解释:HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,
设置为 4096 或者更高。默认值:4096
优化延迟高的数据操作的等待时间
hdfs-site.xml
属性:dfs.image.transfer.timeout
解释:如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的时间,建议把
该值设置为更大的值(默认 60000 毫秒),以确保 socket 不会被 timeout 掉
优化数据的写入效率
mapred-site.xml
属性:
mapreduce.map.output.compress
mapreduce.map.output.compress.codec
解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为
true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec 或者其
他压缩方式
设置 RPC 监听数量
hbase-site.xml
属性:Hbase.regionserver.handler.count
解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写
请求较多时,增加此值
优化 HStore 文件大小
hbase-site.xml
属性:hbase.hregion.max.filesize
解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值,因为一个 region 对应一个 map 任务,如果单个 region 过大,会导致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两
个 Hfile
优化 HBase 客户端缓存
hbase-site.xml
属性:hbase.client.write.buffer
解释:用于指定 Hbase 客户端缓存,增大该值可以减少 RPC 调用次数,但是会消耗更多内
存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的
指定 scan.next 扫描 HBase 所获取的行数
hbase-site.xml
属性:hbase.client.scanner.caching
解释:用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大
flush 、 compact 、 split 机制
当 MemStore 达到阈值,将 Memstore 中的数据 Flush 进 Storefile;compact 机制则是把 flush
出来的小文件合并成大的 Storefile 文件。split 则是当 Region 达到阈值,会把过大的 Region
一分为二。
这篇博客我也是花了很大精力写的,但是难免有错误,希望对文章有意见或建议的朋友可以联系我,我也希望能够听取读者的意见完善自己的文章。
最后,附上一张我在写博客时画的一些图示:
有需要这些图示或者需要源码(配置文档,java代码)的可以通过我的邮箱联系到我。
HBase百度百科
HBase的应用场景及特点
HBase读写流程
HBase性能优化