目录
[1.6 、FamilyFilter](#1.6 、FamilyFilter)
[二、HBase 协处理器](#二、HBase 协处理器)
[1、 Observer协处理器](#1、 Observer协处理器)
一、过滤器Filter
Hbase 提供了种类丰富的过滤器(filter)来提高数据处理的效率,用户可以通过内置或自定义的过滤器来对数据进行过滤,所有的过滤器都在服务端生效,即谓词下推(predicate push down)。这样可以保证过滤掉的数据不会被传送到客户端,从而减轻网络传输和客户端处理的压力。但是这也同时增加了数据查询时的时间。
1、CompareFilter
比较过滤器,通过类图可以看到RowFilter 、QualifierFilter、DependentColumnFilter 、ValueFilter 、FamilyFilter 都继承了CompareFilter抽象类。
- RowFilter :基于行键来过滤数据。
- QualifierFilter :基于列限定符(列名)来过滤数据。
- DependentColumnFilter :指定一个参考列来过滤其他列的过滤器,过滤的原则是基于参考列的时间戳来进行筛选 。
- ValueFilter :基于单元格 (cell) 的值来过滤数据。
- FamilyFilter :基于列族来过滤数据。
1.1、比较运算符
LESS, //小于
LESS_OR_EQUAL, //小于等于
EQUAL, //等于
NOT_EQUAL, //不等于
GREATER_OR_EQUAL, //大于等于
GREATER, //大于
NO_OP //排除所有符合条件的值
1.2、RowFilter
java
/**
* @param tableName
* @param rowKey
* @description: rowkey过滤器
* @return: org.apache.hadoop.hbase.client.ResultScanner
* @author 熟透的蜗牛
* @date: 2025/1/5 15:01
*/
public static ResultScanner getDataByRowFilter(String tableName, String rowKey) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
Filter filter = new RowFilter(CompareOperator.GREATER_OR_EQUAL,
new BinaryComparator(Bytes.toBytes(rowKey)));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
java
public void testGetDataByRowFilter(){
ResultScanner scanner = HBaseUtil.getDataByRowFilter("staff_ns:staff", "1005");
if (scanner != null) {
scanner.forEach(result -> System.out.println("RowKey>>>>>>>>>:" + Bytes.toString(result.getRow()) + "-------->:" + Bytes
.toString(result.getValue(Bytes.toBytes(INFO), Bytes.toBytes("name")))));
scanner.close();
}
}
1.3、QualifierFilter
java
public static ResultScanner getDataByQualifierFilter(String tableName, String qualifierName) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
Filter filter =new QualifierFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes(qualifierName)));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
1.4、DependentColumnFilter
java
public static ResultScanner getDataByDependentColumnFilter(String tableName, String familyName, String qualifierName,String value) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
//第一个参数是列族。第二个参数是限定符
// 第三个字段为是否丢弃参考列所在值,为 true 时丢弃则该列为null,为 false 时会返回对应的值
// 第四个参数为比较运算符,第五个为比较器
Filter filter =new DependentColumnFilter(Bytes.toBytes(familyName), Bytes.toBytes(qualifierName),
false,CompareOperator.LESS,new BinaryComparator(Bytes.toBytes(value)));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
1.5、ValueFilter
java
public static ResultScanner getDataByValueFilter(String tableName,String value) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
Filter filter =new ValueFilter(CompareFilter.CompareOp.NOT_EQUAL,new BinaryPrefixComparator(Bytes.toBytes(value)));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
1.6 、FamilyFilter
java
public static ResultScanner getDataByFamilyFilter(String tableName,String value) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
Filter filter =new FamilyFilter(CompareFilter.CompareOp.EQUAL,new BinaryComparator(Bytes.toBytes(value)));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
2、其他过滤器
过滤器的种类有很多,这里简单介绍几个
2.1、PrefixFilter
java
public static ResultScanner getDataByPrefixFilter(String tableName,String prefix) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
Filter filter =new PrefixFilter(Bytes.toBytes(prefix));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
2.2、ColumnPrefixFilter
java
public static ResultScanner getDataByColumnPrefixFilter(String tableName,String prefix) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
Filter filter =new ColumnPrefixFilter(Bytes.toBytes(prefix));
scan.setFilter(filter);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3、多个过滤器组合使用
java
final public class FilterList extends FilterBase {
public enum Operator {
/** AND */
MUST_PASS_ALL, //所有的都满足,相当于sql中的and
/** OR */
MUST_PASS_ONE //必须满足一个 相当于sql中的or
}
构造函数
java
public FilterList(final List<Filter> filters) {
this(Operator.MUST_PASS_ALL, filters);
}
public FilterList(final Operator operator) {
this(operator, new ArrayList<>());
}
public FilterList(final Operator operator, final Filter... filters) {
this(operator, Arrays.asList(filters));
}
java
public static ResultScanner getDataByMultiFilter(String tableName,String prefix,String familyName) {
try {
Table table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
Filter prefixFilter =new PrefixFilter(Bytes.toBytes(prefix));
RegexStringComparator regexStringComparator = new RegexStringComparator("^\\d{3}$");
RowFilter rowFilter = new RowFilter(CompareOperator.EQUAL, regexStringComparator);
FamilyFilter familyFilter = new FamilyFilter(CompareOperator.EQUAL, new BinaryComparator(Bytes.toBytes(familyName)));
FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ONE,prefixFilter, rowFilter,familyFilter);
scan.setFilter(filterList);
ResultScanner scanner = table.getScanner(scan);
return scanner;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
二、HBase 协处理器
1、 Observer协处理器
1.1、功能
Observer 协处理器类似于关系型数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。通常可以用来实现下面功能:
- 权限校验:在执行 Get或 Put操作之前,您可以使用 preGet或 prePut方法检查权限;
- 完整性约束: HBase 不支持关系型数据库中的外键功能,可以通过触发器在插入或者删除数据的时候,对关联的数据进行检查;
- 二级索引: 可以使用协处理器来维护二级索引。
1.2、类型
当前 Observer 协处理器有以下四种类型:
- RegionObserver : 允许您观察 Region 上的事件,例如 Get 和 Put 操作。
- RegionServerObserver : 允许您观察与 RegionServer 操作相关的事件,例如启动,停止或执行合并,提交或回滚。
- MasterObserver : 允许您观察与 HBase Master 相关的事件,例如表创建,删除或 schema 修改。
- WalObserver : 允许您观察与预写日志(WAL)相关的事件。
2、RegionObserver用例
需求:创建一个表"staff_ns:staff",并且配置上协处理器,另外创建一个表"staff",当向"staff_ns:staff"中添加数据时,实现"staff"新增一条同样的数据。
2.1、创建MyCoprocessor
java
package com.xiaojie.hadoop.hbase.coprocessor;
import lombok.extern.slf4j.Slf4j;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.coprocessor.BaseRegionObserver;
import org.apache.hadoop.hbase.coprocessor.ObserverContext;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.wal.WALEdit;
import java.io.IOException;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: 协处理器
* 1、将协处理器打包成jar上传到/usr/local/hbase-2.6.1/lib/目录下
* 2、创建一个表"staff_ns:staff",配置上协处理器
* 3、创建一个同样结构的表"staff"
* 4、重启habse
* 5、测试数据
* @date 2025/1/6 17:56
*/
@Slf4j
public class MyCoprocessor extends BaseRegionObserver {
@Override
public void prePut(ObserverContext<RegionCoprocessorEnvironment> c, Put put, WALEdit edit) throws IOException {
log.info(">>>>>>>>>>>>>>>>>>>写数据之前");
}
@Override
public void postPut(ObserverContext<RegionCoprocessorEnvironment> c, Put put, WALEdit edit) throws IOException {
log.info(">>>>>>>>>>>>>>>>>>>写数据之后");
//获取表
Table table = c.getEnvironment().getConnection().getTable(TableName.valueOf("staff"));
//写入数据
table.put(put);
//关闭表
table.close();
}
}
2.2、打包
输入快捷键Ctrl+Alt+Shift+S打开如下页面
上传到/usr/local/hbase-2.6.1/lib/目录下
2.3、分发jar
xsync hbase-coprocessor.jar
2.4、重启habse
2.5、创建表staff
java
public static boolean createTable(String tableName, List<String> columnFamilies, String nameSpace) throws IOException {
Admin admin = connection.getAdmin();
boolean exists = admin.tableExists(TableName.valueOf(tableName));
//创建表
if (!exists) {
//如果namespace是空值则会使用default,作为命名空间
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(nameSpace, tableName));
columnFamilies.forEach(cf -> {
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
columnFamilyDescriptorBuilder.setMaxVersions(1);
ColumnFamilyDescriptor columnFamilyDescriptor = columnFamilyDescriptorBuilder.build();
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
});
admin.createTable(tableDescriptorBuilder.build());
return true;
} else {
log.info("table exists>>>>>>>>");
return false;
}
}
2.6、创建表staff_ns:staff
java
public static boolean useCoprocessor(String tableName,List<String> columnFamilies) throws IOException {
//创建表
Admin admin = connection.getAdmin();
boolean exists = admin.tableExists(TableName.valueOf(tableName));
//创建表
if (!exists) {
//如果namespace是空值则会使用default,作为命名空间
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf( tableName));
//设置协处理器,指定class
tableDescriptorBuilder.setCoprocessor("com.xiaojie.hadoop.hbase.coprocessor.MyCoprocessor");
columnFamilies.forEach(cf -> {
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
columnFamilyDescriptorBuilder.setMaxVersions(1);
ColumnFamilyDescriptor columnFamilyDescriptor = columnFamilyDescriptorBuilder.build();
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
});
admin.createTable(tableDescriptorBuilder.build());
return true;
} else {
log.info("table exists>>>>>>>>");
return false;
}
}
2.7、测试
3、加载协处理器
-
加载协处理器前需要先禁用表
hbase > disable 'magazine'
-
加载协处理器
hbase > alter 'magazine', METHOD => 'table_att', 'Coprocessor'=>'hdfs://hadoop001:8020/hbase/hbase-observer-coprocessor-1.0-SNAPSHOT.jar|com.heibaiying.AppendRegionObserver|1001|'
启用表
enable 'magazine'
查看协处理器是否加载成功
desc 'magazine'
4、卸载协处理器
卸载协处理器前需要先禁用表
hbase > disable 'magazine'
卸载协处
hbase > alter 'magazine', METHOD => 'table_att_unset', NAME => 'coprocessor$1'
启用表
hbase > enable 'magazine'
查看协处理器是否卸载成功
hbase > desc 'magazine'
三、高可用和分区
1、高可用
具体参考HBase集群安装部分
vim backup-masters
#添加如下内容
hadoop2
2、容灾备份
Hbase 常用的三种简单的容灾备份方案,即CopyTable 、Export /Import 、Snapshot。
2.1、copyTable
bash
1、同集群下 CopyTable
#创建表结构一致的表
create 'staff1', 'info','position';
#复制表 第一个参数为新表,第二个参数为原始表
/usr/local/hbase-2.6.1/bin/hbase org.apache.hadoop.hbase.mapreduce.CopyTable --new.name=staff1 staff
2、不同集群下 CopyTable
# 两表名称相同的情况 dstClusterZk 远程zk集群,tableOrig 代表原始表
/usr/local/hbase-2.6.1/bin/hbase org.apache.hadoop.hbase.mapreduce.CopyTable --peer.adr=dstClusterZK:2181:/hbase tableOrig
# 也可以指新的表名tableCopy 指定新表名,同样需要提前创建结构一致的表
/usr/local/hbase-2.6.1/bin/hbase org.apache.hadoop.hbase.mapreduce.CopyTable --peer.adr=dstClusterZK:2181:/hbase --new.name=tableCopy tableOrig
#下面是一个官方给的比较完整的例子,指定开始和结束时间,集群地址,以及只复制指定的列族:
/usr/local/hbase-2.6.1/bin/hbase org.apache.hadoop.hbase.mapreduce.CopyTable --starttime=1265875194289 --endtime=1265878794289 --peer.adr=server1,server2,server3:2181:/hbase --families=myOldCf:myNewCf,cf2,cf3 TestTable
注意:这个过程需要开启Hadoop的mapreduce,还需要配置CPU参数在yarn-site.xml中配置,由于机器限制,调整了几次参数,还是报错CPU使用不足,有兴趣的可以自己调整一下试试。
2.2、Export/Import
- Export支持导出数据到 HDFS, Import支持从 HDFS 导入数据。Export还支持指定导出数据的开始时间和结束时间,因此可以用于增量备份。
- Export导出与 CopyTable一样,依赖 HBase 的 scan操作
bash
#导出 staff 为表名, /staff为导出路径,系统会自动创建这个路径
/usr/local/hbase-2.6.1/bin/hbase org.apache.hadoop.hbase.mapreduce.Export staff /staff
#导入 staff为表名,input为导入路径
/usr/local/hbase-2.6.1/bin/hbase org.apache.hadoop.hbase.mapreduce.Import staff /input
2.3、Snapshot
HBase 的快照 (Snapshot) 功能允许您获取表的副本 (包括内容和元数据),并且性能开销很小。因为快照存储的仅仅是表的元数据和 HFiles 的信息。快照的 Clone操作会从该快照创建新表,快照的 restore操作会将表的内容还原到快照节点。Clone和 restore操作不需要复制任何数据,因为底层 HFiles(包含 HBase 表数据的文件) 不会被修改,修改的只是表的元数据信息。
HBase 快照功能默认没有开启,如果要开启快照,需要在 hbase-site.xml 文件中添加如下配置项:
<property>
<name>hbase.snapshot.enabled</name>
<value>true</value>
</property>
bash
1. Take a Snapshot
# 拍摄快照
hbase> snapshot '表名', '快照名'
默认情况下拍摄快照之前会在内存中执行数据刷新。以保证内存中的数据包含在快照中。但是如果你不希望包含内存中的数据,则可以使用 SKIP_FLUSH 选项禁止刷新。
# 禁止内存刷新
hbase> snapshot '表名', '快照名', {SKIP_FLUSH => true}
2. Listing Snapshots
# 获取快照列表
hbase> list_snapshots
3. Deleting Snapshots
# 删除快照
hbase> delete_snapshot '快照名'
4. Clone a table from snapshot
# 从现有的快照创建一张新表
hbase> clone_snapshot '快照名', '新表名'
5. Restore a snapshot
将表恢复到快照节点,恢复操作需要先禁用表
hbase> disable '表名'
hbase> restore_snapshot '快照名'
3、预分区
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
3.1、手动设定预分区
create 'staff2','info','position',SPLITS => ['1000','2000','3000','4000'];
3.2、生成16进制序列预分区
create 'staff3','info','position',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'};
3.3、按照文件中设置的规则预分区
创建/usr/local/hbase-2.6.1/bin/split.txt文件内容如下
aaaa
bbbb
cccc
dddd
eeeee
create 'staff4','position',SPLITS_FILE => 'split.txt';
3.4、API实现分区
java
package com.xiaojie.hadoop.utils;
import org.apache.hadoop.hbase.util.Bytes;
/**
* @author 熟透的蜗牛
* @version 1.0
* @description: TODO
* @date 2025/1/7 6:11
*/
public class RegionUtil {
/**
* @param rowKey rowkey
* @param regionCount 分区个数
* @description: 生成分区号
* @return: java.lang.String
* @author 熟透的蜗牛
* @date: 2025/1/7 6:05
*/
public static String genRegionNum(String rowKey, int regionCount) {
int regionNum;
int hash = rowKey.hashCode();
if (regionCount > 0 && (regionCount & (regionCount - 1)) == 0) {
regionNum = hash & (regionCount - 1);
} else {
regionNum = hash % regionCount;
}
return regionNum + "_" + rowKey;
}
/**
* @param regionCount
* @description: 生成分区键
* @return: byte[][]
* @author 熟透的蜗牛
* @date: 2025/1/7 6:20
*/
public static byte[][] genRegionKey(int regionCount) {
byte[][] regionKey = new byte[regionCount - 1][];
for (int i = 0; i < regionCount - 1; i++) {
regionKey[i] = Bytes.toBytes(i + "|");
}
return regionKey;
}
public static void main(String[] args) {
// String a1001 = genRegionNum("a1004", 5);
// System.out.printf(a1001);
byte[][] bytes = genRegionKey(5);
for (int i = 0; i < bytes.length; i++) {
System.out.println(new String(bytes[i]));
}
}
}
java
/**
* @param tableName
* @param columnFamilies
* @param regionCount 分区个数
* @description: 预分区创建表,创建的region总数将是分割键的数量加一,
* 比如总共有3个,分区则需要2个分区键,则可以把分区分为三份,例如 ------|------|------
* @return: boolean
* @author 熟透的蜗牛
* @date: 2025/1/7 6:28
*/
public static boolean createTableRegion(String tableName, List<String> columnFamilies, int regionCount) throws IOException {
Admin admin = connection.getAdmin();
boolean exists = admin.tableExists(TableName.valueOf(tableName));
//创建表
if (!exists) {
//如果namespace是空值则会使用default,作为命名空间
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName));
columnFamilies.forEach(cf -> {
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
columnFamilyDescriptorBuilder.setMaxVersions(1);
ColumnFamilyDescriptor columnFamilyDescriptor = columnFamilyDescriptorBuilder.build();
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
});
//生成分区分割键
byte[][] bytes = RegionUtil.genRegionKey(regionCount);
admin.createTable(tableDescriptorBuilder.build(), bytes);
return true;
} else {
log.info("table exists>>>>>>>>");
return false;
}
}
/**
* @param tableName
* @param rowKey
* @param columnFamilyName
* @param qualifier
* @param value
* @param regionCount
* @description: 插入数据到指定的分区
* @return: boolean
* @author 熟透的蜗牛
* @date: 2025/1/7 6:34
*/
public static boolean putRegionRow(String tableName, String rowKey, String columnFamilyName, String qualifier,
String value, int regionCount) {
Table table = null;
try {
table = connection.getTable(TableName.valueOf(tableName));
//生成新的RowKey
String row = RegionUtil.genRegionNum(rowKey, regionCount);
Put put = new Put(Bytes.toBytes(row));
put.addColumn(Bytes.toBytes(columnFamilyName), Bytes.toBytes(qualifier), Bytes.toBytes(value));
table.put(put);
log.info(">>>>>>>插入数据成功");
return true;
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (table != null) {
try {
table.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
4、Region拆分
4.1、拆分过程
Region增长到一定的程度(默认大小为10G)就会拆分。下面简单介绍Region拆分的过程
- RegionServer 本地决定分割区域并准备分割。作为第一步,它在 /hbase/region-in-transition/region-name 下的 zookeeper 中创建一个处于 SPLITTING 状态的 znode。
- Master接收到拆分。
- RegionServer 在 HDFS 中父区域目录下创建一个名为".splits"的子目录。
- RegionServer 关闭父区域,强制刷新缓存并在其本地数据结构中将该区域标记为脱机。禁止客户端再向父区域写数据,此时客户端请求向父区域写数据将抛出 NotServingRegionException。
- RegionServer 在 .splits 目录下为子区域 A 和 B 创建区域目录,并创建必要的数据结构。然后它分割存储文件,即它在父区域中的每个存储文件创建两个引用文件。这些引用文件将指向父区域文件。
- RegionServer 在 HDFS 中创建实际区域目录,并移动每个子区域的参考文件。
- RegionServer 向 META.表发送 Put 请求,并在 META.表中将父区域设置为脱机,并添加有关子区域的信息。此时客户端请求meta表时,不会获取到子区域的信息,只有当福区域有效拆分之后,子区域才能被客户端发现。如果拆分失败,master和下一个区服务器会将脏数据清除。
- RegionServer 并行打开子区域以接受写入。
- RegionServer 将子区域 A 和 B 添加到 META表,并添加其托管区域的信息。
- RegionServer 更新 zookeeper 中的 znode /hbase/region-in-transition/region-name 状态为已经拆分,并通知master服务器。
- 拆分后meta和 HDFS 仍将包含对父区域的引用。当子区域中的压缩重写数据文件时,这些引用将被删除。主服务器中的垃圾收集任务定期检查子区域是否仍引用父文件。如果没有,则将删除父区域。
4.2、拆分配置
HBase 通常根据hbase-default.xml 和hbase-site.xml 配置文件中的设置来处理区域拆分 。重要设置包括 hbase.regionserver.region.split.policy
、hbase.hregion.max.filesize
。 hbase.regionserver.regionSplitLimit
拆分的简单观点是,当区域增长到时hbase.hregion.max.filesize
,它会被拆分。对于大多数使用模式,您应该使用自动拆分。
在hbase-site.xml中全局配置拆分策略
<property>
<name>hbase.regionserver.region.split.policy</name>
<value>org.apache.hadoop.hbase.regionserver.IncreasingToUpperBoundRegionSplitPolicy</value>
</property>
单表设置 拆分策略
java
public static boolean createTable(String tableName, List<String> columnFamilies, String nameSpace) throws IOException {
Admin admin = connection.getAdmin();
boolean exists = admin.tableExists(TableName.valueOf(tableName));
//创建表
if (!exists) {
//如果namespace是空值则会使用default,作为命名空间
TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(TableName.valueOf(nameSpace, tableName));
columnFamilies.forEach(cf -> {
ColumnFamilyDescriptorBuilder columnFamilyDescriptorBuilder = ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes(cf));
columnFamilyDescriptorBuilder.setMaxVersions(1);
ColumnFamilyDescriptor columnFamilyDescriptor = columnFamilyDescriptorBuilder.build();
tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor);
});
TableDescriptor tableDescriptor = tableDescriptorBuilder.setValue(TableDescriptorBuilder.SPLIT_POLICY, IncreasingToUpperBoundRegionSplitPolicy.class.getName()).build();
admin.createTable(tableDescriptor);
return true;
} else {
log.info("table exists>>>>>>>>");
return false;
}
}
使用 HBase Shell 在表上配置拆分策略
create 'test', {METADATA => {'SPLIT_POLICY' => 'org.apache.hadoop.hbase.regionserver.ConstantSizeRegionSplitPolicy'}},{NAME => 'cf1'}
四、RowKey设计
HBase 中的行按行键的字典顺序排序。一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。
1、加盐
此处的加盐与加密无关,而是指在行键的开头添加随机数据。
java
@Test
public void testPutRow() throws IOException, NoSuchAlgorithmException {
String rowKey = ShaUtil.getSha1("1001");
HBaseUtil.putRow(STAFF_NAMESPACE, TABLE_NAME, rowKey, INFO, "name", "tom");
}
2、hash
java
@Test
public void testPutRow() throws IOException, NoSuchAlgorithmException {
String rowKey = "a10001";
//这里hash我们使用map的hashcode算法
int h;
int result = Math.abs((rowKey == null) ? 0 : (h = rowKey.hashCode()) ^ (h >>> 16));
HBaseUtil.putRow(STAFF_NAMESPACE, TABLE_NAME, result+"", INFO, "name", "tom");
}
3、反转字符
java
@Test
public void testPutRow() throws IOException, NoSuchAlgorithmException {
String row=new Date().toString();
String rowkey= StringUtils.reverse(row);
HBaseUtil.putRow(STAFF_NAMESPACE, TABLE_NAME, rowkey, INFO, "name", "tom");
}
六、配置参数优化
1.允许在HDFS的文件中追加内容
hdfs-site.xml、hbase-site.xml
|---------------------------------------------------------------------|
| 属性:dfs.support.append 解释:开启HDFS追加同步,可以优秀的配合HBase的数据同步和持久化。默认值为true。 |
2.优化DataNode允许的最大文件打开数
hdfs-site.xml
|----------------------------------------------------------------------------------------------------|
| 属性:dfs.datanode.max.transfer.threads 解释:HBase一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为4096或者更高。默认值:4096 |
3.优化延迟高的数据操作的等待时间
hdfs-site.xml
|--------------------------------------------------------------------------------------------------------------------|
| 属性:dfs.image.transfer.timeout 解释:如果对于某一次数据操作来讲,延迟非常高,socket需要等待更长的时间,建议把该值设置为更大的值(默认60000毫秒),以确保socket不会被timeout掉。 |
4.优化数据的写入效率
mapred-site.xml
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 属性: mapreduce.map.output.compress mapreduce.map.output.compress.codec 解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec或者其他压缩方式。 |
5.设置RPC监听数量
hbase-site.xml
|------------------------------------------------------------------------------------------|
| 属性:hbase.regionserver.handler.count 解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。 |
6.优化HStore文件大小
hbase-site.xml
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 属性:hbase.hregion.max.filesize 解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。 |
7.优化hbase客户端缓存
hbase-site.xml
|---------------------------------------------------------------------------------------------------------------|
| 属性:hbase.client.write.buffer 解释:用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。 |
8.指定scan.next扫描HBase所获取的行数
hbase-site.xml
|-----------------------------------------------------------------------|
| 属性:hbase.client.scanner.caching 解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。 |
9.flush、compact、split机制
当MemStore达到阈值,将Memstore中的数据Flush进Storefile;compact机制则是把flush出来的小文件合并成大的Storefile文件。split则是当Region达到阈值,会把过大的Region一分为二。
七、参考
https://hbase.apache.org/book.html#_preface
https://github.com/heibaiying/BigData-Notes/blob/master/notes
https://blog.cloudera.com/apache-hbase-region-splitting-and-merging/
尚硅谷网上教学