大数据技术(九)—— HBase优化

目录

一、过滤器Filter

1、CompareFilter

1.1、比较运算符

1.2、RowFilter

1.3、QualifierFilter

1.4、DependentColumnFilter

1.5、ValueFilter

[1.6 、FamilyFilter](#1.6 、FamilyFilter)

2、其他过滤器

2.1、PrefixFilter

2.2、ColumnPrefixFilter

3、多个过滤器组合使用

[二、HBase 协处理器](#二、HBase 协处理器)

[1、 Observer协处理器](#1、 Observer协处理器)

1.1、功能

1.2、类型

2、RegionObserver用例

2.1、创建MyCoprocessor

2.2、打包

2.3、分发jar

2.4、重启habse

2.5、创建表staff

2.6、创建表staff_ns:staff

2.7、测试

3、加载协处理器

4、卸载协处理器

三、高可用和分区

1、高可用

2、容灾备份

2.1、copyTable

2.2、Export/Import

2.3、Snapshot

3、预分区

3.1、手动设定预分区

3.2、生成16进制序列预分区

3.3、按照文件中设置的规则预分区

3.4、API实现分区

4、Region拆分

4.1、拆分过程

4.2、拆分配置

四、RowKey设计

1、加盐

2、hash

3、反转字符

六、配置参数优化

1.允许在HDFS的文件中追加内容

2.优化DataNode允许的最大文件打开数

4.优化数据的写入效率

5.设置RPC监听数量

6.优化HStore文件大小

7.优化hbase客户端缓存

8.指定scan.next扫描HBase所获取的行数

9.flush、compact、split机制

七、参考


一、过滤器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、加载协处理器

  1. 加载协处理器前需要先禁用表

    hbase > disable 'magazine'

  2. 加载协处理器

    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 常用的三种简单的容灾备份方案,即CopyTableExport /ImportSnapshot

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拆分的过程

  1. RegionServer 本地决定分割区域并准备分割。作为第一步,它在 /hbase/region-in-transition/region-name 下的 zookeeper 中创建一个处于 SPLITTING 状态的 znode。
  2. Master接收到拆分。
  3. RegionServer 在 HDFS 中父区域目录下创建一个名为".splits"的子目录。
  4. RegionServer 关闭父区域,强制刷新缓存并在其本地数据结构中将该区域标记为脱机。禁止客户端再向父区域写数据,此时客户端请求向父区域写数据将抛出 NotServingRegionException。
  5. RegionServer 在 .splits 目录下为子区域 A 和 B 创建区域目录,并创建必要的数据结构。然后它分割存储文件,即它在父区域中的每个存储文件创建两个引用文件。这些引用文件将指向父区域文件。
  6. RegionServer 在 HDFS 中创建实际区域目录,并移动每个子区域的参考文件。
  7. RegionServer 向 META.表发送 Put 请求,并在 META.表中将父区域设置为脱机,并添加有关子区域的信息。此时客户端请求meta表时,不会获取到子区域的信息,只有当福区域有效拆分之后,子区域才能被客户端发现。如果拆分失败,master和下一个区服务器会将脏数据清除。
  8. RegionServer 并行打开子区域以接受写入。
  9. RegionServer 将子区域 A 和 B 添加到 META表,并添加其托管区域的信息。
  10. RegionServer 更新 zookeeper 中的 znode /hbase/region-in-transition/region-name 状态为已经拆分,并通知master服务器。
  11. 拆分后meta和 HDFS 仍将包含对父区域的引用。当子区域中的压缩重写数据文件时,这些引用将被删除。主服务器中的垃圾收集任务定期检查子区域是否仍引用父文件。如果没有,则将删除父区域。

4.2、拆分配置

HBase 通常根据hbase-default.xmlhbase-site.xml 配置文件中的设置来处理区域拆分 。重要设置包括 hbase.regionserver.region.split.policyhbase.hregion.max.filesizehbase.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/

尚硅谷网上教学

完整代码:spring-boot: Springboot整合常见的组件集成 - Gitee.com

相关推荐
重生之绝世牛码1 小时前
Java设计模式 —— 【行为型模式】命令模式(Command Pattern) 详解
java·大数据·开发语言·设计模式·命令模式·设计原则
晚风_END2 小时前
node.js|浏览器插件|Open-Multiple-URLs的部署和使用,实现一键打开多个URL的强大工具
服务器·开发语言·数据库·node.js·dubbo
网络安全-杰克2 小时前
[网络安全]sqli-labs Less-4 解题详析
数据库·web安全·less
Anna_Tong3 小时前
引领实时数据分析新时代:阿里云实时数仓 Hologres
大数据·阿里云·数据分析·实时数仓
加酶洗衣粉3 小时前
PostgreSQL学习笔记(二):PostgreSQL基本操作
数据库
狄加山6754 小时前
数据结构(查找算法)
数据结构·数据库·算法
jinan8865 小时前
电子图纸怎么保障安全?
大数据·安全
sevevty-seven5 小时前
MySQL的主从复制
数据库·mysql
生信碱移5 小时前
万字长文:机器学习的数学基础(易读)
大数据·人工智能·深度学习·线性代数·算法·数学建模·数据分析
我本是机械人6 小时前
MVCC实现原理及其作用
java·数据结构·数据库·后端·mysql·算法