更新时间:2023-11-07 GMT+08:00

HBase开源增强特性

HBase开源增强特性:HIndex

HBase是一个Key-Value类型的分布式存储数据库。每张表的数据按照RowKey的字典顺序排序,因此,如果按照某个指定的RowKey去查询数据,或者指定某一个RowKey范围去扫描数据时,HBase可以快速定位到需要读取的数据位置,从而可以高效地获取到所需要的数据。

在实际应用中,很多场景是查询某一个列值为“XXX”的数据。HBase提供了Filter特性去支持这样的查询,它的原理是:按照RowKey的顺序,去遍历所有可能的数据,再依次去匹配那一列的值,直到获取到所需要的数据。可以看出,可能只是为了获取一行数据,它却扫描了很多不必要的数据。因此,如果对于这样的查询请求非常频繁并且对查询性能要求较高,使用Filter无法满足这个需求。

这就是HBase HIndex产生的背景。HIndex为HBase提供了按照某些列的值进行索引的能力。

图1 HIndex
  • 索引数据不支持滚动升级。
  • 组合索引限制。
    • 用户必须在单次mutation中输入或删除参与组合索引的所有列。否则会导致不一致问题。

      索引:IDX1=>cf1:[q1->datatype],[q2];cf2:[q2->datatype]

      正确的写操作:

      Put put = new Put(Bytes.toBytes("row"));
      put.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q1"), Bytes.toBytes("valueA"));
      put.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q2"), Bytes.toBytes("valueB"));
      put.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q2"), Bytes.toBytes("valueC"));
      table.put(put);

      错误的写操作:

      Put put1 = new Put(Bytes.toBytes("row"));
      put1.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q1"), Bytes.toBytes("valueA"));
      table.put(put1);
      Put put2 = new Put(Bytes.toBytes("row"));
      put2.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q2"), Bytes.toBytes("valueB"));
      table.put(put2);
      Put put3 = new Put(Bytes.toBytes("row"));
      put3.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q2"), Bytes.toBytes("valueC"));
      table.put(put3);
    • 使用组合条件查询,仅支持组合索引列包含过滤条件的查询,或者不指定StartRow和StopRow的部分索引列的查询。

      索引:IDX1=>cf1:[q1->datatype],[q2];cf2:[q1->datatype]

      正确的查询操作:

      scan 'table', {FILTER=>"SingleColumnValueFilter('cf1','q1',>=,'binary:valueA',true,true) AND SingleColumnValueFilter('cf1','q2',>=,'binary:valueB',true,true) AND SingleColumnValueFilter('cf2','q1',>=,'binary:valueC',true,true) "}
      
      scan 'table', {FILTER=>"SingleColumnValueFilter('cf1','q1',=,'binary:valueA',true,true) AND SingleColumnValueFilter('cf1','q2',>=,'binary:valueB',true,true)" }
      
      scan 'table', {FILTER=>"SingleColumnValueFilter('cf1','q1',>=,'binary:valueA',true,true) AND SingleColumnValueFilter('cf1','q2',>=,'binary:valueB',true,true) AND SingleColumnValueFilter('cf2','q1',>=,'binary:valueC',true,true)",STARTROW=>'row001',STOPROW=>'row100'}

      错误的查询操作:

      scan 'table', {FILTER=>"SingleColumnValueFilter('cf1','q1',>=,'binary:valueA',true,true) AND SingleColumnValueFilter('cf1','q2',>=,'binary:valueB',true,true) AND SingleColumnValueFilter('cf2','q1',>=,'binary:valueC',true,true)  AND SingleColumnValueFilter('cf2','q2',>=,'binary:valueD',true,true)"}
      
      scan 'table', {FILTER=>"SingleColumnValueFilter('cf1','q1',=,'binary:valueA',true,true) AND SingleColumnValueFilter('cf2','q1',>=,'binary:valueC',true,true)" }
      
      scan 'table', {FILTER=>"SingleColumnValueFilter('cf1','q1',=,'binary:valueA',true,true) AND SingleColumnValueFilter('cf2','q2',>=,'binary:valueD',true,true)" }
      
      scan 'table', {FILTER=>"SingleColumnValueFilter('cf1','q1',=,'binary:valueA',true,true) AND SingleColumnValueFilter('cf1','q2',>=,'binary:valueB',true,true)" ,STARTROW=>'row001',STOPROW=>'row100' }
  • 用户不能为有索引数据的表配置任何分裂策略。
  • 不支持其他的mutation操作,如increment和append。
  • 不支持maxVersions>1的列的索引。
  • 不支持一行数据索引列的更新操作。

    索引1:IDX1=>cf1:[q1->datatype],[q2];cf2:[q1->datatype]

    索引2:IDX2=>cf2:[q2->datatype]

    正确的更新操作:

    Put put1 = new Put(Bytes.toBytes("row"));
    put1.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q1"), Bytes.toBytes("valueA"));
    put1.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q2"), Bytes.toBytes("valueB"));
    put1.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q1"), Bytes.toBytes("valueC"));
    put1.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q2"), Bytes.toBytes("valueD"));
    table.put(put1);
    
    Put put2 = new Put(Bytes.toBytes("row"));
    put2.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q3"), Bytes.toBytes("valueE"));
    put2.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q3"), Bytes.toBytes("valueF"));
    table.put(put2);

    错误的更新操作:

    Put put1 = new Put(Bytes.toBytes("row"));
    put1.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q1"), Bytes.toBytes("valueA"));
    put1.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q2"), Bytes.toBytes("valueB"));
    put1.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q1"), Bytes.toBytes("valueC"));
    put1.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q2"), Bytes.toBytes("valueD"));
    table.put(put1);
    
    Put put2 = new Put(Bytes.toBytes("row"));
    put2.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q1"), Bytes.toBytes("valueA_new"));
    put2.addColumn(Bytes.toBytes("cf1"), Bytes.toBytes("q2"), Bytes.toBytes("valueB_new"));
    put2.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q1"), Bytes.toBytes("valueC_new"));
    put2.addColumn(Bytes.toBytes("cf2"), Bytes.toBytes("q2"), Bytes.toBytes("valueD_new"));
    table.put(put2);
  • 添加索引的表不应拥有大于32KB的值。
  • 当由于列族级TTL(生存周期)过期而导致用户数据删除时,对应的索引数据不会立即删除。索引数据会在进行major compaction操作时被删除。
  • 用户列族的TTL在索引创建后不能修改。
    • 如果在创建索引之后,列族的TTL值变大,应该删除并重新创建该索引。否则,一些已经生成的索引数据会先于用户数据被删除。
    • 如果在创建索引之后,列族的TTL值变小。索引数据会晚于用户数据被删除。
  • 索引查询不支持reverse;且查询结果是无序的。
  • 索引不支持clone snapshot操作。
  • 索引表必须使用HIndexWALPlayer回放日志,不支持WALPlayer回放日志。
    hbase org.apache.hadoop.hbase.hindex.mapreduce.HIndexWALPlayer
    Usage: WALPlayer [options] <wal inputdir> <tables> [<tableMappings>]
    Read all WAL entries for <tables>.
    If no tables ("") are specific, all tables are imported.
    (Careful, even -ROOT- and hbase:meta entries will be imported in that case.)
    Otherwise <tables> is a comma separated list of tables.
    
    The WAL entries can be mapped to new set of tables via <tableMapping>.
    <tableMapping> is a command separated list of targettables.
    If specified, each table in <tables> must have a mapping.
    
    By default WALPlayer will load data directly into HBase.
    To generate HFiles for a bulk data load instead, pass the option:
      -Dwal.bulk.output=/path/for/output
      (Only one table can be specified, and no mapping is allowed!)
    Other options: (specify time range to WAL edit to consider)
      -Dwal.start.time=[date|ms]
      -Dwal.end.time=[date|ms]
    For performance also consider the following options:
      -Dmapreduce.map.speculative=false
      -Dmapreduce.reduce.speculative=false
  • 使用deleteall操作索引表存在性能慢问题。
  • 索引表不支持HBCK。如需使用HBCK修复索引表,需先删除索引数据后,再进行修复。

HBase开源增强特性:支持多点分割

当用户在HBase创建Region预先分割的表时,用户可能不知道数据的分布趋势,所以Region的分割可能不合适,所以当系统运行一段时间后,Region需要重新分割以获得更好的查询性能,HBase只会分割空的Region。

HBase自带的Region分割只有当Region到达设定的Threshold后才会进行分割,这种分割被称为单点分割。

为了实现根据用户的需要动态分割Region以获得更好的性能这一目标,开发了多点分割又称动态分割,即把空的Region预先分割成多个Region。通过预先分割,避免了因为Region空间不足出现Region分割导致性能下降的现象。

图2 多点分割

HBase开源增强特性:连接数限制

过多的session连接意味着过多的查询和MR任务跑在HBase上,这会导致HBase性能下降以至于导致HBase拒绝服务。通过配置参数来限制客户端连接到HBase服务器端的session数目,来实现HBase过载保护。

HBase开源增强特性:容灾增强

主备集群之间的容灾能力可以增强HBase数据的高可用性,主集群提供数据服务,备用集群提供数据备份,当主集群出现故障时,备集群可以提供数据服务。相比开源Replication功能,做了如下增强:

  1. 备集群白名单功能,只接受指定集群IP的数据推送。
  2. 开源版本中replication是基于WAL同步,在备集群回放WAL实现数据备份的。对于BulkLoad,由于没有WAL产生,BulkLoad的数据不会replicate到备集群。通过将BulkLoad操作记录在WAL上,同步至备集群,备集群通过WAL读取BulkLoad操作记录,将对应的主集群的HFile加载到备集群,完成数据的备份。
  3. 开源版本中HBase对于系统表ACL做了过滤,ACL信息不会同步至备集群,通过新加一个过滤器org.apache.hadoop.hbase.replication.SystemTableWALEntryFilterAllowACL,允许ACL信息同步至备集群,用户可以通过配置hbase.replication.filter.sytemWALEntryFilter使用该过滤其实现ACL同步。
  4. 备集群只读限制,备集群只接受备集群节点内的内置管理用户对备集群的HBase进行修改操作,即备集群节点之外的HBase客户端只能对备集群的HBase进行读操作。

HBase开源增强特性:HBase MOB

在实际应用中,用户需要存储大大小小的数据,比如图像数据、文档。小于10MB的数据一般都可以存储在HBase上,对于小于100KB的数据,HBase的读写性能是优秀的。如果存放在HBase的数据大于100KB甚至到10MB时,插入同样个数的数据文件,其数据量很大,会导致频繁的compaction和split,占用很多CPU,磁盘IO频率很高,性能严重下降。

将MOB数据(即100KB到10MB大小的数据)直接以HFile的格式存储在文件系统上(例如HDFS文件系统),然后把这个文件的地址信息及大小信息作为value存储在普通HBase的store上,通过expiredMobFileCleaner和Sweeper工具集中管理这些文件。这样就可以大大降低HBase的compaction和split频率,提升性能。

图3所示,图中MOB模块表示存储在HRegion上的mobstore,mobstore存储的是key-value,key即为HBase中对应的key,value对应的就是存储在文件系统上的引用地址以及数据偏移量。读取数据时,mobstore会用自己的scanner,先读取mobstore中的key-value数据对象,然后通过value中的地址及数据大小信息,从文件系统中读取真正的数据。

图3 MOB数据存储原理

HBase开源增强特性:HFS

HBase文件存储模块(HBase FileStream,简称HFS)是HBase的独立模块,它作为对HBase与HDFS接口的封装,应用在MRS的上层应用,为上层应用提供文件的存储、读取、删除等功能。

在Hadoop生态系统中,无论是HDFS,还是HBase,均在面对海量文件的存储的时候,在某些场景下,都会存在一些很难解决的问题:

  • 如果把海量小文件直接保存在HDFS中,会给NameNode带来极大的压力。
  • 由于HBase接口以及内部机制的原因,一些较大的文件也不适合直接保存到HBase中。

HFS的出现,就是为了解决需要在Hadoop中存储海量小文件,同时也要存储一些大文件的混合的场景。简单来说,就是在HBase表中,需要存放大量的小文件(10MB以下),同时又需要存放一些比较大的文件(10MB以上)。

HFS为以上场景提供了统一的操作接口,这些操作接口与HBase的函数接口类似。

HBase开源增强特性:HBase双读

在HBase存储场景下,因为GC、网络抖动、磁盘坏道等原因,很难保证99.9%的查询稳定性。为了满足用户大数据量随机读低毛刺的要求,新增了HBase双读特性。

HBase双读特性是建立在主备集群容灾能力之上,两套集群同时产生毛刺的概率要远远小于一套集群,即采用双集群并发访问的方式,保证查询的稳定性。当用户发起查询请求时,同时查询两个集群的HBase服务,在等待一段时间(最大容忍的毛刺时间)后,如果主集群没有返回结果,则可以使用响应最快的集群数据。原理图如下:

HBase开源增强特性:Phoenix CsvBulkLoad工具导入支持用户自定义分隔符

该内容适用于MRS 3.2.0及之后版本。

Phoenix开源CsvBulkLoad工具当前仅支持指定单个字符作为数据分割符,当用户数据文件中可能包含任意字符时,一般会采用特殊的字符串作为分隔符,为了满足此类场景,增加了对用户自定义分隔符的支持,用户可以采用限定长度内的任意可见字符进行组合作为分隔符来导入数据文件。

HBase开源增强特性:使用HAR文件格式拆分WAL文件

该内容适用于MRS 3.2.0及之后版本。

当RegionServer发生故障或重启时,HMaster会使用ServerCrashProcedure对RegionServer的业务进行恢复,恢复过程中包括拆分WAL文件。在WAL文件拆分过程中,会产生大量的小文件,可能造成HDFS的性能瓶颈,导致服务恢复时间过长。

本功能主要在拆分过程中将原本的小文件写入到HAR文件中,旨在减少拆分WAL过程中产生的小文件,从而缩短RegionServer恢复时长。

HBase开源增强特性:Batch TRSP

HBase 2.x内核版本使用HBase Procedure框架重写了region assignment的逻辑(AMV2)。每个Region的open或者close都会有一个TransitRegionStateProcedure(TRSP)与之关联。当RegionServer因为故障或重启需要恢复业务时,HMaster会为每个需要恢复的Region创建一个TRSP,大量的TRSP需要把数据持久化到Proc WAL文件中并且需要跟RegionServer进行RPC交互,可能造成HMaster性能瓶颈,导致服务恢复时间过长。

本功能主要通过在TRSP中添加attach region的方式,利用一个TRSP将一个RegionServer所有的Region进行恢复处理,RegionServer也将进行Region的批量open/close并一次性全部上报给HMaster。

该特性只支持将Region恢复到原来的RegionServer,因此该优化生效的前提为HMaster在创建TRSP时,故障或重启的RegionServer已经重新上线。因此,该特性主要用于优化HBase重启或者服务故障恢复的时长,如果是少量RegionServer发生故障,可能因为HMaster在RegionServer重新上线前已经创建了TRSP而不生效。

该内容适用于MRS 3.2.0及之后版本。