天天看点

【HBase】18-模式定义1、表2、表属性3、列族

在HBase中建表涉及到表结构以及所有涉及的列族列族结构的定义,这些定义关系到表和列族内的数据如何存储以及何时存储。

1、表

在 HBase中数据最终会存储在一张表或多张表中,使用表的主要原因是控制表中的所有列以达到共享表内的某些特性的目的,一个典型的例子是定义表的列族。下面是表描述符的构造函数

/**
   * Default constructor which constructs an empty object.
   * For deserializing an HTableDescriptor instance only.
   * @deprecated Used by Writables and Writables are going away.
   */
  @Deprecated
  public HTableDescriptor() {
    super();
  }

  /**
   * Construct a table descriptor specifying a TableName object
   * @param name Table name.
   * @see <a href="HADOOP-1581" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >HADOOP-1581 HBASE: Un-openable tablename bug</a>
   */
  public HTableDescriptor(final TableName name) {
    super();
    setName(name);
  }

  /**
   * Construct a table descriptor specifying a byte array table name
   * @param name Table name.
   * @see <a href="HADOOP-1581" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >HADOOP-1581 HBASE: Un-openable tablename bug</a>
   */
  @Deprecated
  public HTableDescriptor(final byte[] name) {
    this(TableName.valueOf(name));
  }

  /**
   * Construct a table descriptor specifying a String table name
   * @param name Table name.
   * @see <a href="HADOOP-1581" target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow"  target="_blank" rel="external nofollow" >HADOOP-1581 HBASE: Un-openable tablename bug</a>
   */
  @Deprecated
  public HTableDescriptor(final String name) {
    this(TableName.valueOf(name));
  }

  /**
   * Construct a table descriptor by cloning the descriptor passed as a parameter.
   * <p>
   * Makes a deep copy of the supplied descriptor.
   * Can make a modifiable descriptor from an UnmodifyableHTableDescriptor.
   * @param desc The descriptor.
   */
  public HTableDescriptor(final HTableDescriptor desc) {
    super();
    setName(desc.name);
    setMetaFlags(this.name);
    for (HColumnDescriptor c: desc.families.values()) {
      this.families.put(c.getName(), new HColumnDescriptor(c));
    }
    for (Map.Entry<ImmutableBytesWritable, ImmutableBytesWritable> e:
        desc.values.entrySet()) {
      setValue(e.getKey(), e.getValue());
    }
    for (Map.Entry<String, String> e : desc.configuration.entrySet()) {
      this.configuration.put(e.getKey(), e.getValue());
    }
  }
           

Writable和无参数的构造函数

API提供的大多数类和本章中讨论到的类都拥有一个特殊的构造函数,即一个不带任何参数的构造函数。因为这些类都实现了 Hadoop Writable接口。

任意不相交系统间的远程通信:例如,客户端与服务器端进行交互,或者服务器端彼此进行内部通信,都使用到了 Hadoop RPC框架。这个框架中需要远程方法中的参数都实现 Writable接口,进而能够序列化对象并进行远程传输. Writable接口有两个必须实现的方法:

void write(Dataoutput out)throws IOException;
void readFields(DataInput in)throws IOException;
           

框架通过调用这两个方法把对象序列化成输出流,通信接收端读取字节流,并将其反序列化成对象。框架在数据发送端调用write()方法,并序列化对象的字段,同时会在字节流中添加类名以及其他一些信息。

数据接收服务器先读取元数据信息,并创建类的无参数实例,然后调用新创建对象的 readFields()方法,将字节流中的信息读取到对应对象的字段中,用户可以理解为这是数据发送端对象的一个可运行的,已经初始化的完整副本。

因为数据接收端首先读取元数据并创建该类的空实例,并通过 readFields()方法及序列化字段信息到新创建的实例中。通常客户端与服务器端需要使用相同的 HBase JAR。

如果用户开发并扩展了 HBase的基础实现,例如,过滤器和协处理器,用户必须确保满足以下条件。

1.在RPC通信两端都必须可用,即在数据发送进程与数据接收进程均可用。

2.实现 Writable接口,并实现了 write()与 readfields()方法

3.拥有无参数的构造函数,即一个没有任何输入参数的构造函数

如果运行时无法使用这个特殊的构造函数生成实例,则会报运行时错误,并且从代码中显式调用该构造函数也是徒劳的,因为留下了不同于预期定义且未初始化的变量。

客户端AP开发人员必须了解RPC的潜在依赖,以及怎样部署API。在扩展 HBase代码时,高级开发人员需要适当地实现和部署自定义代码。

用户可以通过表名或已有的表描述符来创建表。没有任何参数的构造函数仅仅是为了反序列化,并且不应被直接使用。表名通常使用 Java String类型或byte[](二进制数组)表示, HBase中的很多功能都提供以上两种参数类型的选择。使用字符串明显是为了方便使用,在 HBase内部通常会将字符串转化为字节数组处理, HBase也是这样的处理模式。 HBase中提供的 Bytes类有转化功能,示例如下:

byte[] name = Bytes.toBytes("test ");
HTableDescriptor desc = new HTableDescriptor(name);
           

表名会作为存储系统中存储路径的一部分来使用,因此必须要符合文件名规范,因此构成表名的字符是有限制的。用户可以直接查看低级别存储系统,例如,用户在HDFS中可以看到每张表的表名都作为独立的目录结构——在某些情况下,用户可能会需要查看这部分信息。

HBase列式存储格式允许用户存储大量的信息到相同的表中,而在 RDBMS模型中,大量信息则需要切分成多张表存储。通常的数据库范式化规则不适合 HBase,因此Hase中表的数量相对较少。

虽然理论上HBae的表是由行和列组成的,但是从物理结构上看,表存储在不同的分区,即不同的 region。图5-1展示了数据存储逻辑与物理上的不同。每个 region只在一个 region服务器中提供服务,而 region直接向客户端提供存储服务。

【HBase】18-模式定义1、表2、表属性3、列族

2、表属性

表提供了 getter与 setter方法来设置表的其他属性。实际上,这些属性中的大部分都很少用到,但是这些属性非常重要,用户可以使用这些方法来微调表的性能,因此用户需要了解它们。

构造函数中已经有设置表名的参数, Java API也提供了显式设置表名的方法。

public byte [] getName();
public String getNameAsString();
@Deprecated
public HTableDescriptor setName(byte[] name);
           

表名一定不能以“.”(结束符)或“-”(连字符)开头。表名只能包含拉丁字母或数字,以及“_”(下划线)、“-”(连字符)或“.”(结束符)。

按照正则表达式语法,其规则可表示为[a-zA-Z_0-9-.]。

例如.testtable是错误的表名,test.table是正确的表名。

列族

列族是表中非常重要的一部分,用户需要在表中指定将要使用的列族

【HBase】18-模式定义1、表2、表属性3、列族

用户可以添加列族、通过列族名来检查列族是否存在、获取存在的列族的列表、获取某个列族描述符(HColumnDescriptor)或删除某个列族等操作。

文件大小限制

这个参数限制了表中 region的大小。通过以下方法可以显式地获取和设置该参数的值:

long getMaxFilesize()
void setMaxFilesize(long maxFilesize)
           

文件大小限制并不能表达其真正的含义,其真正含义应该是每个存储单元的大小限制,即一个列族有若千个存储单元,而其中每个存储单元会包含若干个文件。如果一个列族的存储单元已使用的存储空间超过了大小限制, region将发生拆分操作。所以这个参数被称为 maxStoresize更合适。

当 region的大小达到配置的大小时,文件大小限制会帮助 HBase拆分 region。因此用户需要了解这个参数应该设置为多少合适,这个参数的默认值是256MB,设置为多少关键要看使用场景,例如,表数据量非常的大情况下,用户可以酌情考虑将这个值调高。

请注意,这个参数是个大致的预期值,而在某种特定条件下,文件大小可能会超过这个参数的大小,并且不会带来影响。例如,用户将这个参数设置为10MB,然后插入了一行大小为20MB的值,由于一行数据不能跨 region存储,因此系统也就不会拆分该行数据。

只读

默认所有的表都可写,但是,对一些特殊的表来说,只读参数有特殊的用途。如果该参数设置为true,这个表只能读而不能修改数据。通过以下方法可获取和设置该参数:

boolean isReadonly()
void setReadonly (boolean readonly)
           

memstore刷写大小

早期我们讨论到 HBase内存中预留了写缓冲区,写操作会写入到写缓冲区中,然后按照合适的条件顺序写入到磁盘的一个新存储文件中,这个过程称为刷写(flush)。这个参数能够控制何时触发将内存中的数据写入到磁盘的事件。示例方法如下:

long getMemStoreFlushsize()
void setMemStoreFlushsize(long memstoreFlushsize)
           

上面提到的文件大小限制跟需求相关,该参数的默认值是64MB。该参数越大,可以生成的存储文件越大,文件数量会越少,同时也会导致更长的阻塞时间问题。这种情况下, region服务器不能持续接收新增加的数据,请求被阻塞的时间也就随之增加了,此外,一旦服务器崩溃,系统通过WAL恢复数据的时间也相应增加了,且更新的内存丢失。

延时日志刷写

我们需要注意的是,HBase有两种将WAL保存到磁盘的方式,一种是延时日志刷写( deferred log flushing)另一种则不是。这个参数是一个布尔类型变量,默认设置为false。下面是通过JavaAPI模式设置参数的方法:

synchronized boolean isDeferredLogFlush()
void set DeferredLogFlush(boolean isDeferredlogFlush)
           

其他选项

除了已经提到的属性,还有一些提供给用户设置任意键值对的方法:

byte[] getValue(byte[] key)
String getValue(String key)
Map<ImmutableBytesWritable, ImmutableBytesWritable> getvalues()
void setvalue(byte[] key, byte[] value)
void setValue(String key, String value)
void remove(byte[] key)
           

以上方法中的数据均存储在预定义的表中,并在必要的时候能被检索。例如,在HBase中,用户可以通过加载协处理器来进行检索。用户设置键与值时有多种选择,如 String或byte数组,内部数据使用 ImmutableBytesWritable类型存储,目的是为了能够序列化。

3、列族

在前面的描述中,我们看到了如何通过 HTableDescriptor类为一张表增加列族,与此类似,这里提供了一个专门的Java类来实现对每个列族的设置。在其他编程语言中用户也能够发现同样的设置列族属性的方法。

在Java中,这个类的命名并没有完全体现它的真正含义,更适合的命名或许应该是 HColumnEamilyDescriptor,这个类名能够表达定义列族参数的含义。

列族定义了所有列的共享信息,并且可以通过客户端创建任意数量的列,列常用的变量名为 column qualifiers。定位某一具体列需要列族名与列名合并在一起,以分割:

family:qualifier

列族名字必须是可见字符,列名可以由任意二进制字符组成。前面提到的工具类 Bytes提供了将Java语言中基本类型转化为字节数组的方法。列族名必须为可见字符是因为这个名字将会在底层存储系统中使用,图5-2概述了列族映射到存储系统的过程。列族名

需要添加到文件系统路径名中使用,优点是用户可以通过格式可读的名称轻松访问文件系统层面的列族。

【HBase】18-模式定义1、表2、表属性3、列族

用户需要注意空列名的使用,也可以只使用列族名而忽略列名,用户的体验是一样的。读写操作与其他列没有区别,但是最好增加列名来加以区分。

用户可以不指定简单应用的列名,只是使用 HBase shell查看数据时看不到有价值的结果。从规范的角度来讲,用户也应该指定列名,或者说从一开始就指定列名,因为后期无法简单地重命名空列。

用户创建列族时,可以通过不同的构造函数实现所有属性的设置。Java类提供了许多不同参数的构造函数,这些函数便于用户有针对性地选择使用,并定制需要的参数。下面是含不同参数的构造函数:

@Deprecated
  public HColumnDescriptor();
  public HColumnDescriptor(final String familyName);
  public HColumnDescriptor(final byte [] familyName);
  public HColumnDescriptor(HColumnDescriptor desc);
  @Deprecated
  public HColumnDescriptor(final byte [] familyName, final int maxVersions,
      final String compression, final boolean inMemory,
      final boolean blockCacheEnabled, final int blocksize,
      final int timeToLive, final String bloomFilter, final int scope);
  @Deprecated
  public HColumnDescriptor(final byte[] familyName, final int minVersions,
      final int maxVersions, final KeepDeletedCells keepDeletedCells,
      final String compression, final boolean encodeOnDisk,
      final String dataBlockEncoding, final boolean inMemory,
      final boolean blockCacheEnabled, final int blocksize,
      final int timeToLive, final String bloomFilter, final int scope);
           

第一个构造函数用于内部反序列化接下来的两个构造函数使用了 String或byte[](字节数组已经在文中出现多次了)。另外一个构造函数复制了已存在的HColumnDescriptor对象属性,最后两个则给出了所有可用参数。

除了构造函数,用户还可以通过getr与 setter方法设置参数。接下来将讨论相关细节。

名字

每个列族都有名字,用户可以通过HColumnDescriptor实例的以下方法获取列族名。

byte[] getName()

String getNameAsString()

列族不能被重命名。通常的做法是新建一个列族,然后使用API从旧列族中复制数据到新列族。

用户除了通过构造函数,没有其他重命名列族的途径。此外一定要谨记,列族名必须是可见字符。

列族名不能以“.”开头,也不能包含“:”、“/”或ISO控制符,即字符不能在\u0000到\u001F的范围内或\u007F到\u009F的范围内。

最大版本数

所有列族都限定了每个值能够保留的最大版本数量。在前面提到的断言删除过程中, HBase会移除超过最大版本数的数据。下面是该参数的 getter与 setter方法:

int getMaxVersions();

void setMaxVersions(int maxversions);

该参数默认值是3,用户也可以设置为1,例如,在不访问旧数据的情况下。

压缩

HBase支持插件式的压缩算法,这个功能允许用户选择最适合的压缩算法——或不压缩—数据存储在特定的列族中。下表列出了目前支持的压缩算法。

支持的压缩算法

算法 描述
NONE 不压缩(默认)
GZ 使用Java提供的或本地库提供的GZIP
LZO 启用LZO压缩,需要安装LZO的类库
SNAPPY 启用 Snappy压缩,需要独立安装

默认值是NONE,简言之,就是创建不压缩直接存储的列族。用户如果需要 Java APl和列描述符,可能会用以下方法来修改相应的值:

【HBase】18-模式定义1、表2、表属性3、列族

注意,压缩类型不是 String而是 Compression.Algorithm枚举,枚举中的类型与上述表格列出的类型列表一致。HColumnDescriptor的构造函数也同样需要字符串的参数形式。

获取压缩信息有两种方法,一种是一般压缩设置,另一种是合并压缩设置。此外,每种方法分别有相应的 getCompression()与 getCompressionType()或 getCompactionCompression()与 getCompationCompressionType(),这两种方法返回同一类型的值,都可以用于检查当前压缩算法的类型。

块大小

在 HBase中,所有的存储文件都被划分成了若干个小存储块,这些小存储块在get或scan操作时会加载到内存中,它们类似于 RDBMS中的存储单元页。这个参数的默认大小是64KB,并通过以下方法设置和获取:

synchronized int getBlocksize()

void setBlocksize(int s)

这个参数用于指定 HBase在一次读取过程中顺序读取多少数据到内存缓冲区。

注意,这里有一个重要的不同点:列族的块,或者说HFile的块不同于HDFS层面的块。HDFS层面提到的块是用于拆分大文件以提供分布式存储,且便于 MapReduce框架进行并行计算的存储单元一默认是128MB。 HBase中的 HFile块大小默认是64KB,是HDFS中块大小的2048分之一,主要用于高效加载和缓存数据,并不依赖于HDFS的块大小,并且只用于 HBase内部。

缓存块

HBase顺序地读取一个数据块到内存缓存中,其读取相邻的数据时就可以在内存中读取而不需要从磁盘中再次读取,有效地减少了磁盘O的次数,提高了IO效率。

这个参数默认为true,这意味着每次读取的块都会缓存到内存中。但是,如果用户要顺序读取某个特定列族,最好将这个属性设置为 false,从而禁止其使用块缓存。以下是可以改变该标识的API:

boolean isBlockCacheEnabled();

void setBlockCacheEnabled(boolean blockCacheenabled)

还有其他参数可以影响块缓存的使用,例如,scan的过程涉及众多参数,这些参数最终都可能影响到缓存。这些参数在全表扫描时非常有用,它们能够减少在全表扫描过程中缓存的大量流失。

生存期TTL

HBase不仅支持断言删除每个值能保存的版本数,也支持处理版本数据保存时间。

生存期(TTL)设置了一个基于时间戳的临界值,内部的管理会自动检查TTL值是否达到上限,在 major合并过程中时间戳被判定为超过TTL的数据会被删除。以下是读写TTL的 getter与 setter的AP。

int getTimeToLive()

void setTimeToLive(int timeToLive)

TTL参数的单位是秒,默认值是 Integer.MAX_VALUE,即2147483647秒。使用TTL默认值的数据可以理解为永久保留,即生产过程中产生的任意正数都永远小于当前默认值。

在内存中

我们提到了缓存块和怎样使用缓存块来提高连续访问的效率。这里还有一个在内存中(in-memory)标志,默认值为 false,以下方法可以修改此属性。

boolean isInMemory()

void setInMemory(boolean inMemory)

将这个参数设置为true并不意味着将整个列族的所有存储块都加载到内存中,也不意味着它们会被长期保存在那儿,而是一种承诺,或者说是高优先级。在正常的数据读取过程中,块数据被加载到缓存区中并长期驻留在内存中,除非堆压力过大,这个时候才会强制从内存卸载这部分数据。

这个参数通常适合数据量较小的列族,例如,保存登录账号和密码的用户表,将这个参数设置为true有利于提升这个环节的处理速度。

布隆过滤器

布隆过滤器是 HBase系统中的高级功能,它能够减少特定访问模式下的查询时间。但是,由于这种模式增加了内存和存储的负担,这个模式被默认为关闭状态。

支持的布隆过滤器类型

类型 描述
NONE 不使用布隆过滤器
ROW 行键使用布隆过滤器过滤
ROWCOL 列键使用布隆过滤器过滤

由于列的数量远多于行的数量(除非每行只有一列),使用最后一个选项 ROWCOL会占用大量的空间。与仅仅只有行的模式相比,行例列组合形式的粒度无疑更细。以下API可以设置布隆过滤器。

StoreFile.BloomType getBloomFilterType()

void setBloomFilterType(StoreFileBloomType bt)

虽然这两个方法提供了 StoreFlle.BloomType类型,但列族描述符也可以直接使用字符串进行描述,所以参数类型不是关键因素,例如,用户可以使用“row”。

复制范围

HBase的另一个高级功能是复制( replication)。它提供了跨集群同步的功能本地集群的数据更新可以及时同步到其他集群。复制范围( replication scope)的参数默认为0,意味着这个功能默认处于关闭状态。以下方法可以改变其参数。

int getScope()

void setScope(int scope)

另一个可设置的值是1,这个参数可以开启本地集群向远程集群实时同步的功能。

将来这个参数有可能支持其他可用值。

支持的复制范围

范围 描述
本地范围,即关闭实时同步(默认)
1 全局范围,即开启实时同步

最后,Java类还提供了检查列族是否存在的帮助方法。

static byte[] isLegalEamilyName(byte[] b)

用户在程序中可以通过以上方法验证列族名是否合法。这个方法并不返回布尔型标志位,而是当检查到不合法的输入时会抛出IllegalArgumentException异常,否则会将输入直接返回。这个方法会在构造函数中调用,不需要用户提前特殊调用。