一、Shell操作
1. 进入hbase的shell客户端操作界面
# 于linux终端中敲命令
hbase shell
2. 帮助命令:help
(1) 查看hbase中可以使用的命令
help
(2) 查看某个命令的使用帮助文档
# cmd_name: 命令名称
help 'cmd_name'
3. namespace相关命令
(1) 创建namespace
# 创建namespace
create_namespace 'namespace_name'
# 创建namespace的同时,指定namespace的属性值
create_namespace 'ns1', {'property_name1'=>'property_value1', 'property_name2'=>'property_value2', ...}
(2) 增加、修改或者删除namespace的属性
# 增添、修改namespace的属性
alter_namespace 'namespace_name', {METHOD=>'set', 'property_name1'=>'property_value1', 'property_name2'=>'property_value2', ...}
# 删除属性
alter_namespace 'namespace_name', {METHOD=>'unset', NAME=>'property_name1', NAME=>'property_name2', ...}
# 可以同时将增添、修改、删除属性的命令写在一起
alter_namespace 'namespace_name', {METHOD=>'set', 'property_name1'=>'property_value1', 'property_name2'=>'property_value2', ...}, {METHOD=>'unset', NAME=>'property_name1', NAME=>'property_name2', ...}, ...
(3) 查看namespace的详细描述信息
describe_namespace 'namespace_name'
(4) 查看namespace下创建的表
list_namespace_tables 'namespace_name'
(5) 查看所有创建的namespace
list_namespace
(6) 删除namespace
# 需要注意的是,如果namespace中有表的话,需要先把表删除后,才能删除该namespace
drop_namespace 'namespace_name'
4. 表相关命令
指定表的格式为:namespace_name:table_name;namespace_name不是必须的,如果未指定namespace_name,那么HBase默认会去default默认命名空间中寻找表。
(1) DDL
a. 创建表
# 创建表,不指定列族属性
create 'namespace_name:table_name', {NAME=>'column_famliy1'}, {NAME=>'column_famliy2'}, ...
# 简化后的写法
create 'namespace_name:table_name', 'column_famliy1', 'column_famliy2', ...
# 创建表,指定列族属性
# VERSIONS: 指定该列族中每列的数据会在HBase中保留几个版本(使用时间戳来指定每个数据的版本)
create 'namespace_name:table_name', {NAME=>'column_famliy1', VERSIONS=>num1}, {NAME=>'column_famliy2', VERSIONS=>num2}, ...
b. 添加新列族、修改列族属性、删除列族
# 添加列族,不指定列族属性
alter 'namespace_name:table_name', 'column_famliy1', 'column_famliy2', ...
# 添加列族,指定列族属性 或者 修改原有列族属性
alter 'namespace_name:table_name', {NAME=>'column_famliy1', VERSIONS=>num1}, {NAME=>'column_famliy2', VERSIONS=>num2},...
# 当只操作一个列族时,简写为:
alter 'namespace_name:table_name', NAME=>'column_famliy', VERSIONS=>num
# 删除列族
alter 'namespace_name:table_name', {METHOD=>'delete', NAME=>'column_famliy1'}, {METHOD=>'delete', NAME=>'column_famliy2'}, ...
# 当只需要删除单个列族时候,简写为
alter 'namespace_name:table_name', 'delete'=>'column_famliy'
c. 查看表的详细描述信息
describe 'namespace_name:table_name'
d. 列出用户创建的表
# 列出用户创建的所有表
list
# 从用户创建的所有表中使用正则表达式过滤出指定的表
list 'namespace_name_regx:table_name_regx'
e. 删除表
# 删除表需要两步走:1. 使表无效;2. 删表;
# 使单个表无效
disable 'namespace_name:table_name'
# 删除单个表
drop 'namespace_name:table_name'
# 使用正则表达式,批量使表无效
disable_all 'namespace_name_regx:table_name_regx'
# 使用正则表达式,批量删除表
drop_all 'namespace_name_regx:table_name_regx'
# 如果表已经无效,想让它再变为有效,使用命令:
enable 'namespace_name:table_name'
enable_all 'namespace_name_regx:table_name_regx'
# 判断表是否有效
is_enabled 'namespace_name:table_name'
is_disabled 'namespace_name:table_name'
f. 判断表是否存在
# 判断表是否存在(是否删除)
exists 'namespace_name:table_name'
(2) DML
a. 向表中添加、修改数据
# 添加或者修改数据,时间戳越大代表数据越新,HBase读取最新的数据
# 不指定时间戳
put 'namespace_name_table_name', 'row_key', 'column_famliy:column_name', 'value'
# 指定时间戳
put 'namespace_name_table_name', 'row_key', 'column_famliy:column_name', 'value', timestamp
# 向表中放入数值类型(默认放入的是Long类型)
put 'namespace_name_table_name', 'row_key', 'column_famliy:column_name', Bytes.toBytes(num)[, timestamp]
b. 查看表中某条数据
# 获取表中一行记录的所有列数据
get 'namespace_name:table_name', 'row_key'
# 获取表中一行记录的一个列的数据
get 'namespace_name:table_name', 'row_key', {COLUMN=>'column_famliy:column', VERSIONS=>num, TIMESTAMP=>timestamp}
# 获取表中一行记录的某些列数据
get 'namespace_name:table_name', 'row_key', {COLUMNS=>['column_famliy:column', 'column_famliy:column', ...]}
# 简写为:
get 'namespace_name:table_name', 'row_key', ['column_famliy:column', 'column_famliy:column', ...]
# 对要获取的表中数据加上一些限制
# 获取num个版本、指定时间戳的指定列数据
get 'namespace_name:table_name', 'row_key', {COLUMNS=>['column_famliy:column', 'column_famliy:column, ...'], TIMESTAMP=>timestamp, VERSIONS=>num}
# 获取num个版本、指定时间戳范围内的指定列数据
get 'namespace_name:table_name', 'row_key', {COLUMNS=>['column_famliy:column', 'column_famliy:column', ...], TIMERANGE=>[timestamp1, timestamp2], VERSIONS=>num}
# 默认情况下获取表中数据使用的是字符串格式,但是当表中数据存储的时候是数值时,使用字符串格式无法查看,toInt查看Int类型、toLong查看Long类型
# 解决方案如下:
get 'namespace_name:table_name', 'row_key', 'column_famliy:column:toInt'
c. 扫描表中数据
# 扫描一张表中的所有数据
scan 'namespace_name:table_name'
# 扫描一张表中某个时间戳范围、指定数量(num)、指定列的数据
scan 'namespace_name:table_name', {COLUMNS=>['column_famliy:column', 'column_famliy:column', ...], TIMERANGE=>[timestamp1, timestamp2], LIMIT=>num}
# 扫描一张表中某个行键范围、指定数量(num)、指定列的数据
# 扫描范围为:前包后不包
# 假设某一行键为'abc','abc!'(前缀为abc的最小字符串) < 'abc' < 'abc~'(前缀为abc的最大字符串)
scan 'namespace_name:table_name', {COLUMNS=>['column_famliy:column', 'column_famliy:column', ...], STARTROW=>'row_key1', STOPROW=>'row_key2', LIMIT=>num}
# 默认情况下获取表中数据使用的是字符串格式,但是当表中数据存储的时候是数值时,使用字符串格式无法查看,toInt查看Int类型、toLong查看Long类型
# 解决方案如下:
scan 'namespace_name:table_name', {COLUMNS=>['column_famliy:column:toInt', 'column_famliy:column:toLong', ...]
# 扫描一张表的原始数据,也就是存储在HFile中的数据
# 扫描num个版本的原始数据
scan 'namespace_name:table_name', {RAW=>true, VERSIONS=>num}
d. 删除表中一行数据的某个版本
# 删除表中某行记录某列的最新版本的数据,如果有多个版本,那么次新版本的列数据将会被使用
delete 'namespace_name:table_name', 'row_key', 'column_famliy:column'
# 删除表中某行记录某列的指定版本数据,如果有多个版本,那么删除后的最新版本的列数据将会被使用
delete 'namespace_name:table_name', 'row_key', 'column_famlit:column', timestamp
# 底层会向HFile中添加一个删除操作的记录,该记录使用被删除数据的时间戳,操作类型是DELETE类型
f. 删除表中一行数据的所有版本
# 删除表中整整一行记录
# 底层会向HFile中追加列族数量条的删除记录,表示删除了所有版本的数据,每条删除记录的类型都是:DELETE_FAMLIY
deleteall 'namespace_name:table_name', 'row_key'
# 删除表中某条记录中指定列的所有版本的数据
# 底层会向HFile中追加一条删除记录,指示删除了哪个列,删除记录的类型为:DELETE_COLUMN
deleteall 'namespace_name:table_name', 'row_key', 'column_famliy:column'
# 删除表中某条记录中指定列的指定版本的数据]
# 底层的处理逻辑同delete
deleteall 'namespace_name:table_name', 'row_key', 'column_famliy:column', timestamp
g. 清空表中所有数据,直接就是物理删除
truncate 'namespace_name:table_name'
二、API
Admin实例负责DDL操作,Table实例负责DML操作;这两个示例都需要从Connection实例中获取;Connection是一个重量级实现且线程安全,打开了就不要随意关闭,而Admin、Table是轻量级实现,随开随关。
1. 引入依赖
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>2.0.5</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>2.0.5</version>
</dependency>
2. Connection实例的获取与关闭
(1) 获取
public static Connection getConnection() throws IOException {
Configuration conf = HBaseConfiguration.create();
// 配置Zookeeper的连接地址,如果hadoop101不行就连接hadoop102,之间使用逗号(,)分割
conf.set("hbase.zookeeper.quorum", "hadoop101,hadoop102,hadoop103");
return ConnectionFactory.createConnection(conf);
}
(2) 关闭
public static void closeConnection(Connection connection){
if (connection != null){
try {
// 关闭connection对象
// 切记,要等到确定不使用的时候再关,因为Connection是一个重量级实现
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3. DDL
(1) 创建namespace
public static void createNamespace(Connection connection, String namespaceName){
Admin admin = null;
try {
admin = connection.getAdmin();
NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespaceName);
// 为要创建的namespace添加属性
builder.addConfiguration("greet", "hello world!");
admin.createNamespace(builder.build());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (admin != null){
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(2) 判断表是否存在
public static boolean exists(Connection connection, String namespaceName, String tableName){
Admin admin = null;
try {
admin = connection.getAdmin();
return admin.tableExists(TableName.valueOf(namespaceName, tableName));
} catch (IOException e) {
e.printStackTrace();
return false;
}finally {
if (admin != null){
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(3) 创建表
public static void createTable(Connection connection, String namespaceName, String tableName, String[] columnFamilies, int versions){
// 判断表是否存在,存在就不重复创建
if (exists(connection, namespaceName, tableName)){
return;
}
Admin admin = null;
try {
admin = connection.getAdmin();
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(namespaceName, tableName));
tableDescriptorBuilder.setColumnFamilies(Arrays.stream(columnFamilies).map(s -> {
// 使用了HBase提供的Bytes工具类
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(s));
// 设置列族保留多少个版本的列数据
columnFamilyDescriptorBuilder.setMaxVersions(versions);
return columnFamilyDescriptorBuilder.build();
}).collect(Collectors.toList()));
// 创建表
admin.createTable(tableDescriptorBuilder.build());
} catch (IOException e) {
e.printStackTrace();
}finally {
if (admin != null){
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(4) 删除表
public static void dropTable(Connection connection, String namespaceName, String tableName){
// 判断表是否存在,如果不存在,就不执行删除操作
if (!exists(connection, namespaceName, tableName)){
return;
}
Admin admin = null;
try {
admin = connection.getAdmin();
// 让表变为disable状态
admin.disableTable(TableName.valueOf(namespaceName, tableName));
// 删除表
admin.deleteTable(TableName.valueOf(namespaceName, tableName));
} catch (IOException e) {
e.printStackTrace();
}finally {
if (admin != null){
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(5) 删除namespace
public static void dropNamespace(Connection connection, String namespaceName){
Admin admin = null;
try {
admin = connection.getAdmin();
NamespaceDescriptor[] namespaceDescriptors = admin.listNamespaceDescriptors();
if (Arrays.stream(namespaceDescriptors).anyMatch(namespaceDescriptor -> namespaceDescriptor.getName().equalsIgnoreCase(namespaceName))){
// 判断要删除的namespace存在
TableName[] tableNames = admin.listTableNames(Pattern.compile(namespaceName + ":.*"));
if (tableNames.length != 0){
// 若namespace下有表,先删表
Arrays.stream(tableNames).forEach(tableName -> dropTable(connection, namespaceName, tableName.getQualifierAsString()));
}
// 删除没有表的namespace
admin.deleteNamespace(namespaceName);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (admin != null){
try {
admin.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4. DML
(1) put数据/修改数据
public static void putData(Connection connection, String namespaceName, String tableName, String rowKey, String columnFamily, String column, Object value){
Table table = null;
try {
// 获取Table对象
table = connection.getTable(TableName.valueOf(namespaceName, tableName));
// 获取Put对象,该对象封装了多个列数据
Put put = new Put(Bytes.toBytes(rowKey));
// 封装列数据到put对象中
// 等价于:put 'columnFamily:tableName', 'rowKey', 'value'
put.addColumn(Bytes.toBytes(columnFamily), Bytes.toBytes(column), Bytes.toBytes(value.toString()));
// 添加数据
table.put(put);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (table != null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(2) get一行记录
public static void getRecord(Connection connection, String namespaceName, String tableName, String rowKey){
Table table = null;
try {
table = connection.getTable(TableName.valueOf(namespaceName, tableName));
// 创建Get对象,用来get数据
Get get = new Get(Bytes.toBytes(rowKey));
// 也可以通过调用Get对象的addxxx方法,指定获取的具体列等信息
// 获取一条记录的列数据的集和
Result result = table.get(get);
String outputParttern = "{0} : {1} : {2} : {3}";
for (Cell cell : result.rawCells()) {
// 遍历列数据
String rowKy = Bytes.toString(CellUtil.copyRow(cell));
String columnFamily = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(MessageFormat.format(outputParttern, rowKy, columnFamily, column, value));
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (table != null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(3) scan表内范围记录
public static void scanRecord(Connection connection, String namespace, String tableName, String startRow, String stopRow){
Table table = null;
try {
table = connection.getTable(TableName.valueOf(namespace, tableName));
// 创建Scan对象
Scan scan = new Scan(Bytes.toBytes(startRow), Bytes.toBytes(stopRow));
// 可以通过Scan对象的addxxx方法,来设定检索哪些列(默认检索出所有列)、最多检索几条记录等等条件;
String outputParttern = "{0} : {1} : {2} : {3}";
ResultScanner tableScanner = table.getScanner(scan);
for (Result result : tableScanner) {
// 遍历每一行表中的记录
for (Cell cell : result.rawCells()) {
// 遍历列数据
String rowKey = Bytes.toString(CellUtil.copyRow(cell));
String columnFamily = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));
System.out.println(MessageFormat.format(outputParttern, rowKey, columnFamily, column, value));
}
System.out.println("===============================");
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (table != null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(4) 删除记录
public static void delete(Connection connection, String namespace, String tableName, String rowKey){
Table table = null;
try {
table = connection.getTable(TableName.valueOf(namespace, tableName));
// 创建Delete对象,默认删除表中一整行记录的所有版本数据
Delete delete = new Delete(Bytes.toBytes(rowKey));
// 可以通过Delete对象的addxxx方法,来决定删除列族、列、整行记录的所有数据或者某一个版本的数据
table.delete(delete);
} catch (IOException e) {
e.printStackTrace();
}finally {
if (table != null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
三、优化
1. 预分区(提前划分Region,Region是针对表来说的)
# 创建表的时候预先指定分区
# 分区为:[-∞, 'region1')、['region1', 'region2')、...
create 'namespace_name:table_name', {NAME=>'column_family1', VERSIONS=>num1}, {NAME=>'column_family2', VERSIONS=>num2}, ..., SPLITS=>['region1', 'region2', ...]
# 类似于SPLITS=>['region1', 'region2', ...],只不过是把分区内容写入了文件中
# 寻找文件的方法有两种:(1)相对路径,相对于启动hbase shell的路径;(2) 绝对路径
create 'namespace_name:table_name', {NAME=>'column_family1', VERSIONS=>num1}, {NAME=>'column_family2', VERSIONS=>num2}, ..., SPLITS_FILE=>'split_file_path'
# count为分区数量,分区为:[-∞, '11111111')、['11111111', '22222222')、...
# 具体每个Region的范围划分如何,由count数量来影响每个区域的范围
create 'namespace_name:table_name', {NAME=>'column_family1', VERSIONS=>num1}, {NAME=>'column_family2', VERSIONS=>num2}, ..., {NUMREGIONS=>count, SPLITALGO=>'HexStringSplit'}
2. Row Key设计思想
假设要提前划分50个分区,利用某种算法,求出可以唯一确定表中记录的字段值的集合(Row Key)所代表的值,用这个值取余分区数,将取余的结果拼接到Row Key的最前端,根据这个来设计分区范围,例如:
[-∞, '01!')
['01!', '02!')
......
3. 基础优化配置(habse-site.xml)
配置项 | 解释 |
zookeeper.session.timeout | Zookeeper与RegionServer之间的心跳间隔时间;默认为90000毫秒(90s)。 |
hbase.regionserver.handler.count | 默认值为30;用来响应客户端请求要开启的线程数量,当客户端请求频繁时,需要提高该值。 |
hbase.hregion.majorcompaction | Major Compaction的周期时间,默认配置为604800000秒(7天);如果想要禁用自动Major Compaction,设置该值为0即可。 |
hbase.hregion.max.filesize | 默认值为:10737418240(10GB);用来配置HFile的最大大小。 |
hbase.client.write.buffer | 默认值为:2097152bytes(2M);客户端缓存,用来接收hbase查询的数据,例如:某一次查询的数据量大小为10m,那么客户端需要远程通信5次才能完全读取所有查询的数据。据此,可以通过调大该值,来减少远程通信次数过多的性能花销。 |
hbase.client.scanner.caching | scan的时候,并不会将所有数据一次性读取到内存中,而是会分批次读取,该配置项用来配置一个批次读取多少条数据到内存中。 |
hfile.block.cache.size | 读缓存(Block Cache)最多占用JVM内存的比例,默认值为0.4 |
hbase.regionserver.global.memstore.size | 一个RegionServer中所有写缓存(MemStore)占用JVM内存的最大比例,默认值为0.4 |