HBase列簇多版本 以及 在线Alter Table ColumnFamily

一、HBase列簇最高版本数量的限制

在列簇描述符设置MaxVersions代码如下 org.apache.hadoop.hbase.HColumnDescriptor#setMaxVersions

  /**
   * @param maxVersions maximum number of versions
   * @return this (for chained invocation)
   */
  public HColumnDescriptor setMaxVersions(int maxVersions) {
    if (maxVersions <= 0) {
      // TODO: Allow maxVersion of 0 to be the way you say "Keep all versions".
      // Until there is support, consider 0 or < 0 -- a configuration error.
      throw new IllegalArgumentException("Maximum versions must be positive");
    }    
    if (maxVersions < this.getMinVersions()) {      
        throw new IllegalArgumentException("Set MaxVersion to " + maxVersions
            + " while minVersion is " + this.getMinVersions()
            + ". Maximum versions must be >= minimum versions ");      
    }
    setValue(HConstants.VERSIONS, Integer.toString(maxVersions));
    cachedMaxVersions = maxVersions;
    return this;
  }

可以看出maxVersion不允许设置为负数、不允许小于MinVersions,传入的类型为int,所以可以设置的最大值为Integer.MAX_VALUE2^31-1

二、Alter Table带来的影响

2.1 Alter影响

这里Alter的是Table的列簇,主要逻辑在TableModifyFamilyHandler,Alter数据表属性等不在本文介绍的内容中,可以根据TableEventHandler的子类来跟踪代码。
对于现有数据表,如果需要修改当前表的MaxVersions,需要执行alter '${table_name}',{'NAME'=>'${family_name}',VERSIONS=>${version}}. 主要执行代码 org.apache.hadoop.hbase.master.handler.TableEventHandler#process

@Override
  public void process() {
    if (!isPrepareCalled) {
      //For proper table locking semantics, the implementor should ensure to call
      //TableEventHandler.prepare() before calling process()
      throw new RuntimeException("Implementation should have called prepare() first");
    }
    try {
      LOG.info("Handling table operation " + eventType + " on table " +
          tableName);

      List<HRegionInfo> hris =
        MetaReader.getTableRegions(this.server.getCatalogTracker(),
          tableName);
      //1. 此步骤执行TableModifyFamilyHandler的实现方法,修改HDFS中记录的tabledesc信息
      handleTableOperation(hris);
      if (eventType.isOnlineSchemaChangeSupported() && this.masterServices.
          getAssignmentManager().getZKTable().
          isEnabledTable(tableName)) {
        //2. 修改列簇属性 并且不Disable table执行会进行所有region的重启工作
        if (reOpenAllRegions(hris)) {
          LOG.info("Completed table operation " + eventType + " on table " +
              tableName);
        } else {
          LOG.warn("Error on reopening the regions");
        }
      }
      completed(null);
    } catch (IOException e) {
      LOG.error("Error manipulating table " + tableName, e);
      completed(e);
    } catch (KeeperException e) {
      LOG.error("Error manipulating table " + tableName, e);
      completed(e);
    } finally {
      releaseTableLock();
    }
  }

执行大致分为两步:

  1. 修改HDFS中 [TABLE_DIR]/.tabledesc/.tableinfo 信息

    该步骤执行较快,会重试十次。目前alter操作tableinfo不涉及到并发竞争等问题,认为该步骤风险较小。 如该步骤失败,不会进行第二步reopen region,该步骤执行失败对生产环境影响较小。 简要流程为: 先将tabledesc信息写到 临时目录 .tmp下,再通过原子操作rename到指定文件下 .tabledesc/.tableinfo.

  2. 重启数据表的各个region

    第一步执行完成后执行该table涉及到的所有region的重启工作。 该步骤在各个regionserver中异步执行,直到将所有region全部重启完成。 该步骤可能存在的问题:

    1. 正常情况下region重启速度较快,本地测试毫秒级内即可完成重启,线上环境有待确认。region状态转移到CLOSED期间,该region下的数据无法进行读写,正常重启时间短暂可以忽略,如出现异常导致region长时间无法reopen

    2. region重启过程较为繁琐,涉及很多region的状态转换过程中可能出现region状态在各处流转(HMaster,ZKS,Regionserver等),状态变迁过程中可能会出现异常,此时region进入RIT状态,一般情况下HBase可以自行恢复该region的异常状态,当出现长时间RIT异常时,需要人工介入处理

    3. 由于线上region可能在处理请求,可能在进行compact,region的关闭会等待操作完成后再进行reopen,操作时间过长可能导致region长时间等待重启,但期间不影响region读写,reopen操作会强制执行flush。

    4. 当执行alter过程中出现,如果出现region正在进行split,则正在split的region不会进行重启,因为父region即将消失,子女regions相当于在新的tabledesc下创建新的region. 可能的问题: 如果split过程失败,没有生成子女region,父region被rollback恢复将如何处理? HBase源码中给出了相应的todo注释但未标明会如何处理。

      TODO: What to do if split fails and is rolled back and parent is revivified?

2.2 reopen过程中中region状态变化

在这里插入图片描述

2.3 人工介入的异常处理方式

根据HBase监控信息,以及相应的错误信息

  1. 如果出现region长期无法进入opened状态,考虑使用assign命令尝试重新上线region
  2. 如果出现region长时间处于RIT状态,考虑使用hbase hbck工具查看是否文件或元数据问题,对table进行修复

三、多版本数据对HBase带来的影响

3.1 StoreFile的影响是否会出现StoreFile大量增长。

StoreFile以HFile的形式存储在HDFS上,HFile由memStore通过手动自动触发flush的方式落盘。每个HFile文件中均包含firstKey,lastKey且不相同,如果同一个rowKey版本过多可能导致某一个HFile文件中大量包含同一个Key的不同版本数据。

极限情况下可能出现一个HFile文件中只有2个rowKey的全部Versions版本数据
在这里插入图片描述

3.2 对Region的影响

同一个key的多版本数据形成无法切分的数据块。

切分region的代码中可以看出,切分region是以rowKey作为切分点,如果同一个rowKey下存有过多版本可能导致切分后region仍然是不均匀的。极端情况下可能出现某个region下只包含两个rowKey的情况,且这两个rowKey无法进行split。

  /**
   * Gets the approximate mid-point of this file that is optimal for use in splitting it.
   * @param comparator Comparator used to compare KVs.
   * @return The split point row, or null if splitting is not possible, or reader is null.
   */
  @SuppressWarnings("deprecation")
  byte[] getFileSplitPoint(KVComparator comparator) throws IOException {
    if (this.reader == null) {
      LOG.warn("Storefile " + this + " Reader is null; cannot get split point");
      return null;
    }
    // Get first, last, and mid keys.  Midkey is the key that starts block
    // in middle of hfile.  Has column and timestamp.  Need to return just
    // the row we want to split on as midkey.
    byte [] midkey = this.reader.midkey();
    if (midkey != null) {
      KeyValue mk = KeyValue.createKeyValueFromKey(midkey, 0, midkey.length);
      byte [] fk = this.reader.getFirstKey();
      KeyValue firstKey = KeyValue.createKeyValueFromKey(fk, 0, fk.length);
      byte [] lk = this.reader.getLastKey();
      KeyValue lastKey = KeyValue.createKeyValueFromKey(lk, 0, lk.length);
      // if the midkey is the same as the first or last keys, we cannot (ever) split this region.
      if (comparator.compareRows(mk, firstKey) == 0 || comparator.compareRows(mk, lastKey) == 0) {
        if (LOG.isDebugEnabled()) {
          LOG.debug("cannot split because midkey is the same as first or last row");
        }
        return null;
      }
      return mk.getRow();
    }
    return null;
  }

本地测试环境将region大小设置为2M。

正常region进行拆分后可以保证每个region下的storeFiles大小维持在2M以内
在这里插入图片描述

当出现某个rowKey版本过多,无法进行继续拆分可能会出现以下情况。
在这里插入图片描述
在这里插入图片描述

region大小远大于设置的2M且无法继续拆分。
在这里插入图片描述

3.3 对于Scan查询性能的影响。

Scan数据会扫过各个region各个HFile,如果HFile中某些key的数据量增多,势必会对Scan数据造成部分影响,当Versions版本较少时候这个影响可以忽略,但是当Versions版本非常多的情况下由于Key的各个版本可能分散在不同的HFile中,可能导致Scan多版本数据需要扫过很多HFile文件。

附录:官方建议

It is not recommended setting the number of max versions to an exceedingly high level (e.g., hundreds or more) unless those old values are very dear to you because this will greatly increase StoreFile size.

官方文档不建议将多版本设置过多,最好不要上百。除非你使用这个多版本的属性来实现自己多版本的业务逻辑,这部分特性对你的业务十分重要。
多版本会极大的增加StoreFile的大小,上文已经简单验证过。

环境

测试环境和代码版本
HBase版本hbase-0.98.9-hadoop2
Hadoop版本hadoop-2.2.0
ZK版本zookeeper-3.4.5

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mirt_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值