HBase应用开发回顾与总结系列之一:概述HBase设计规范

时间:2022-11-28 04:14:19

 

概述

笔者本人接触研究HBase也有半年之久了,虽说不上深入和系统,但至少算是比较沉迷。作为部门里大数据技术的探路者,笔者还要承担起技术传播的职责,所以在摸索研究的过程中总是不断地进行总结和测试,一路走来,慢慢地积累了一些东西,整理了一下,做成一个技术系列文档,暂时就叫做“HBase应用开发回顾与总结”。虽然称不上什么高深莫测的技术,但本着开源和分享的精神,笔者本人还是很乐意将它逐篇贴出来。另外,笔者认为《HBase权威指南》算是比较好的HBase方面的技术书籍了,推荐给大家阅读。

下面是这个系列文档的目录介绍:

第一章 HBase设计规范

介绍了HBase应用开发时建议遵循的设计规范,主要是针对开发层面的。

第二章 RowKey行键设计规范

介绍了RowKey行键的部分特性和设计规范,当然,行键的具体设计,还是要遵循具体的业务,并借助丰富的设计经验。

第三章 RowKey行键生成器

设计了一个RowKey行键生成器,可通过界面的方式手动制订行键的生成策略,并可将此策略文件序列化成本地文件,也可将本地策略文件反序列化成策略对象,通过此策略对象可批量动态生成行键信息。

第四章 HBase配置管理类接口设计

设计HBase配置工具类,包括如何加载读取hbase相关配置文件,以及生成Configuration对象等等。

第五章 HBase表信息管理接口设计

设计HBase表信息管理工具类,包括命名空间、表信息、列族等管理接口。

第六章HBase表写数据接口设计

设计了几个HBase写数据模型类,通过这些模型类,方便在开发期组织数据并将其写入HBase数据库。

第七章 HBase表读数据接口设计

设计了几个HBase读数据模型类,介绍了多种数据检索方案,包括批量检索、范围检索、版本检索等等。

第八章 HBase过滤器应用设计

介绍了几类常用的过滤器以及过滤器使用时应该注意的细节问题,包括分页过滤器、前缀过滤器等。

第九章 HBase轻量级ORM设计

模仿Hibernate的对象映射,做了一个针对HBase的轻量级ORM设计方案,并没有过多考虑实用性,只是一种原型测试。

第十章 HBase表数据浏览器

综合应用以上几个章节,设计了一个HBase表数据浏览器,具备表信息导航、按条件分页查询、多版本查询等功能。效果图如下所示:

HBase应用开发回顾与总结系列之一:概述HBase设计规范

1. HBase设计规范

在这里讲什么设计规范实在是有些不知天高地厚,毕竟笔者本人也只是一个大数据技术的初学者,断然不敢制订什么设计规范的,所以请原谅我的狂妄,这个设计规范,只是本人对自己制订的,与别人无关。

之前,HBase官方和大批的高人已经总结了一部分HBase设计方面的规范,笔者进行了搜集整理,再加上自己的理解和丰富,就整理出了一份自己感觉适合自己开发所应遵循的规范。

Hbase中与表结构相关的逻辑模型涉及到以下几个词汇:命名空间、表、列族、列、行键、版本等,这些是构建hbase表的所有元素。笔者就依据这几个关键词汇,陈述下相关的规范。

1.1. Namespace命名空间设计

通俗地讲,命名空间可视为表组(与Oracle中的表空间类似),划分依据不固定,可依据业务类型划分,也可依据时间周期划分。譬如,针对电力气象方面的数据表,可以创建一个电力气象的命名空间,取名为DLQX,将电力气象相关的表都组织在此命名空间下面。引进命名空间的好处就是方便对表进行组织管理。

HBase默认的命名空间是default,默认情况下,如果在创建表时没有显式地指定命名空间,那么表将创建在default命名空间下。如果表隶属于某个非默认的命名空间,那么在引用表(譬如读取表数据)时,就必须指定命名空间,否则将出现类似“无法定位到表”的错误,完整表名的格式为“命名空间名称:表名称”,譬如”DLQX:SYSTEM_USER”;如果是默认的命名空间,则完整表名也可以省略掉“default:”,直接拼写表名SYSTEM_USER即可。

命名空间与表的关系,可以用下图表示:

HBase应用开发回顾与总结系列之一:概述HBase设计规范

命名空间与表之间是一对多的关系,即一个命名空间下面可以包含多个hbase表,但一个hbase表只能属于一个命名空间。在创建表时,如果没有指定命名空间(或者命名空间为空),则系统会将此hbase表放置在默认命名空间(default)下。

另外,删除命名空间之前,必须先删除掉此命名空间下的所有hbase表,否则将无法删除此命名空间。

1.2. Table表设计

HBase有几个高级特性,在你设计表时可以使用。这些特性不一定联系到模式或行键设计,但是它们定义了某些方面的表行为。

1.2.1 理想HBase表

Hbase作为列数据库,根据官方的说法,在性能和效率上更擅长处理“高而瘦”的表,而非“矮而胖”的表。所谓“高而瘦”,是指表的列的数量较少,但是行的数量极大,从而使表展现出一种又高又瘦的形象。所谓“矮而胖”,是指表的列的数据居多,但是行的数量却有限,给人一种又矮又胖的形象,虽然hbase表号称可容纳百万列,但是那也仅仅限于理论上的极限,在实际应用中,请尽量构建“高而瘦”的表,同时需要对列的数量进行测试,以避免过度影响读写性能。

1.2.2 预创建分区

默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到这个region足够大了才进行切分。一种可以加快批量写入速度的方法是通过预先创建一些空的regions,这样当数据写入HBase时,会按照region分区情况,在集群内做数据的负载均衡。

1.2.3 列族数量

不要在一张表里定义太多的column family。目前Hbase并不能很好的处理超过2~3个column family的表。因为某个column family在flush的时候,它邻近的column family也会因关联效应被触发flush,最终导致系统产生更多的I/O。所以,根据官方的建议,一个HBase表中创建一个列族即可。

1.2.4 可配置的数据块大小

HFile数据块大小可以在列族层次设置。这个数据块不同于HDFS数据块。其默认值是65,536字节,或64KB。数据块索引存储每个HFile数据块的起始键。数据块大小设置影响到数据块索引的大小。数据块越小,索引越大,从而占用更大内存空间。同时因为加载进内存的数据块更小,随机查找性能更好。但是如果你需要更好的序列扫描性能,那么一次能够加载更多HFile数据进入内存则更为合理,这意味着数据块应该设置为更大的值。相应地索引变小,你将在随机读性能上付出代价。

1.2.5 数据块缓存

把数据放进读缓存,但工作负载却经常不能从中获得性能提升。例如,如果一张表或表里的列族只被顺序化扫描访问或者很少被访问,你不会介意Get或Scan花费时间是否有点儿长。在这种情况下,你可以选择关闭那些列族的缓存。如果你只是执行很多顺序化扫描,你会多次倒腾缓存,并且可能会滥用缓存把应该放进缓存获得性能提升的数据给排挤出去。如果关闭缓存,你不仅可以避免上述情况发生,而且可以让出更多缓存给其他表和同一表的其他列族使用。

1.2.6 激进缓存

你可以选择一些列族,赋予它们在数据块缓存里有更高的优先级(LRU缓存)。如果你预期一个列族比另一个列族随机读更多,这个特性迟早用得上。

IN_MEMORY参数的默认值是false。因为HBase除了在数据块缓存里保存这个列族相比其他列族更激进之外并不提供额外的保证,该参数在实践中设置为true不会变化太大。

创建表的时候,可以通过HColumnDescriptor.setInMemory(true)将表放到RegionServer的缓存中,保证在读取的时候被cache命中。

1.2.7 布隆过滤器(Bloom filters)

数据块索引提供了一个有效的方法,在访问一个特定的行时用来查找应该读取的HFile的数据块。但是它的效用是有限的。HFile数据块的默认大小是64KB,这个大小不能调整太多。

如果你要查找一个短行,只在整个数据块的起始行键上建立索引无法给你细粒度的索引信息。例如,如果你的行占用100字节存储空间,一个64KB的数据块包含(64 * 1024)/100 = 655.53 = ~700行,而你只能把起始行放在索引位上。你要查找的行可能落在特定数据块上的行区间里,但也不是肯定存放在那个数据块上。这有多种情况的可能,或者该行在表里不存在,或者存放在另一个HFile里,甚至在MemStore里。这些情况下,从硬盘读取数据块会带来IO开销,也会滥用数据块缓存。这会影响性能,尤其是当你面对一个巨大的数据集并且有很多并发读用户时。

布隆过滤器允许你对存储在每个数据块的数据做一个反向测试。当某行被请求时,先检查布隆过滤器看看该行是否不在这个数据块。布隆过滤器要么确定回答该行不在,要么回答它不知道。这就是为什么我们称它是反向测试。布隆过滤器也可以应用到行里的单元上。当访问某列标识符时先使用同样的反向测试。

布隆过滤器也不是没有代价。存储这个额外的索引层次占用额外的空间。布隆过滤器随着它们的索引对象数据增长而增长,所以行级布隆过滤器比列标识符级布隆过滤器占用空间要少。当空间不是问题时,它们可以帮助你榨干系统的性能潜力。

你可以在列族上打开布隆过滤器,如下所示:

hbase(main)> create 'mytable',{NAME=>'colfam1',BLOOMFILTER=>'ROWCOL'}

BLOOMFILTER参数的默认值是NONE。一个行级布隆过滤器用ROW打开,列标识符级布隆过滤器用ROWCOL打开。行级布隆过滤器在数据块里检查特定行键是否不存在,列标识符级布隆过滤器检查行和列标识符联合体是否不存在。ROWCOL布隆过滤器的开销高于ROW布隆过滤器。

1.2.8 生存时间(TTL)

应用系统经常需要从数据库里删除老数据。由于数据库很难超过某种规模,所以传统上数据库内建了许多灵活处理办法。例如,在TwitBase里你不愿意删除用户在使用应用系统期间生成的任何推帖。这些都是用户生成数据,将来有一天当你执行一些高级分析时可能有用。但是并不需要保存所有推帖用于实时访问。所以早于某个时间的推帖可以归档存放到平面文件里。

HBase可以让你在数秒内在列族级别设置一个TTL。早于指定TTL值的数据在下一次大合并时会被删除。如果你在同一单元上有多个时间版本,早于设定TTL的版本会被删除。你可以关闭TTL或者通过设置其值为INT.MAX_VALUE (2147483647)来让它永远打开(这是默认值)。你可以在建表时设置TTL,如下所示:

hbase(main)> create 'mytable',{NAME=>'colfam1',TTL=>'18000'}

该命令在colfam1列族上设置TTL为18,000秒=5小时。colfam1里超过5小时的数据将会在下一次大合并时被删除。

1.2.9 数据压缩

HFile可以被压缩并存放在HDFS上。这有助于节省硬盘IO,但是读写数据时压缩和解压缩会抬高CPU利用率。压缩是表定义的一部分,可以在建表或模式改变时设定。除非你确定不会从压缩中受益,我们推荐你打开表的压缩。只有在数据不能被压缩或者因为某种原因服务器的CPU利用率有限制要求的情况下,有可能会关闭压缩特性。

HBase可以使用多种压缩编码,包括LZO、Snappy和GZIP。LZO[1]和Snappy[2]是其中最流行的两种。Snappy由Google在2011年发布,发布不久Hadoop和HBase项目开始提供支持。在此之前,选择的是LZO编码。Hadoop使用的LZO原生库受GPLv2版权控制,不能放在Hadoop和Hbase的任何发行版里;它们必须单独安装。另一方面,Snappy拥有BSD许可(BSD-licensed),所以它更容易和Hadoop和HBase发行版捆绑在一起。LZO和Snappy的压缩比例和压缩/解压缩速度差不多。

当建表时你可以在列族上打开压缩,如下所示:

hbase(main)> create 'mytable',{NAME=>'colfam1',COMPRESSION=>'SNAPPY'}

注意数据只在硬盘上是压缩的。在内存里(MemStore或BlockCache)或网络传输时是没有压缩的。

改变压缩编码的做法不应该经常发生,但是如果你的确需要改变某个列族的压缩编码,直接做就可以。你需要更改表定义,设定新压缩编码。此后合并时,生成的HFile全部会采用新编码压缩。这个过程不需要创建新表和复制数据。但你要确保直到改变编码后所有老HFile被合并后才能从集群中删除老编码函数库。

1.2.10 数据分割

在HBase中,数据在更新时首先写入WAL 日志(HLog)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时, 系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了(minor compact)

StoreFile是只读的,一旦创建后就不可以再修改。因此Hbase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(major compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对 StoreFile进行分割(split),等分为两个StoreFile。

由于对表的更新是不断追加的,处理读请求时,需要访问Store中全部的StoreFile和MemStore,将它们按照row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,通常合并过程还是比较快的。

实际应用中,可以考虑必要时手动进行major compact,将同一个row key的修改进行合并形成一个大的StoreFile。同时,可以将StoreFile设置大些,减少split的发生。

1.2.11 单元时间版本

HBase在默认情况下每个单元维护三个时间版本。这个属性是可以设置的。如果你只需要一个版本,推荐你在设置表时只维护一个版本。这样系统就不会保留更新单元的多个时间版本。时间版本也是在列族级设置的,可以在表实例化时设定:

hbase(main)> create 'mytable',{NAME=>'colfam1', VERSIONS=>1}

你可以在同一个create语句里为列族指定多个属性,如下所示:

hbase(main)> create 'mytable',{NAME=>'colfam1',VERSIONS=>1,TTL=>'18000'}

你也可以指定列族存储的最少时间版本数,如下所示:

hbase(main)> create 'mytable',{NAME=>'colfam1',VERSIONS=>5,

MIN_VERSIONS=>'1'}

在列族上同时设定TTL也是迟早有用的。如果当前存储的所有时间版本都早于TTL,至少MIN_VERSION个最新版本会保留下来。这样确保在你的查询以及数据早于TTL时有结果返回。

1.3. ColumnFamily列族设计

列族是针对多个列的分组,分组的依据是不固定的。虽然理论上HBase一个表可以创建多个列族,但是HBase官方建议一个表不要创建多于一个的列族。经过测试,单个列族的写入和读取效率要远远超过多个列族时的情况。在存储时,一个列族会存储成一个StoreFile,多个列族对应的多个文件在分裂时会对服务器造成更大的压力。所以建议,一个表创建一个列族。

列族的名称不宜过长,因为在存储时每列都会拼上列族名称,过长的列族将会浪费更多的存储空间。

删除列族时,将同时删除列族下的列及列值数据。

创建表时,最少要创建一个列族。创建表后,可以添加多个列族。

Version版本是针对列族而言的,如果一个表有多个列族,可以为每个列族设置不同的版本数量。譬如,允许列族A最多有5个版本,列族B最多有3个版本。

1.4. Qualifier列设计

HBase与传统的关系数据库一个明显的不同之处,就是创建表时不需要创建列,而是在写入数据时动态地创建列。而且其中的空列并不真正占用存储空间。

列内容被封装成为KeyValue对象,从中可以获取多个信息,如下所示:

//行键
String rowKey = Bytes.toString(kv.getRow());
//列族
String family = Bytes.toString(kv.getFamily());
//列名称
String qualifier = Bytes.toString(kv.getQualifier());
//列值
String value = Bytes.toString(kv.getValue());
//版本号
long timestamp = kv.getTimestamp();

1.5. 版本设计

如果表的某个列族涉及到多版本的问题,则必须在创建列族时指定MaxVersions。虽然,HBase默认的版本数是3,但是如果在创建表时没有明确指定,则仍然只能保存一个版本,因为HBase会认为你不想启用列族的多版本机制。

可以在写入数据时指定版本号,如果不指定版本号,则将采用默认的版本号,即时间戳。

读取数据时,如果没有指定版本号,将只读取最新版本数据,而非最新版本号的数据。

1.6. HBase命名规范

项目

说明

示例

命名空间

  • 采用英文单词、阿拉伯数字的组合形式,其中,单词必须大写,并且首字符必须为英文字符,不能是数字。
  • 不建议用连接符(下划线)拼接多个单词,简单语义的可采用单个单词,复杂语义的可采用多个单词的首字母拼接。
  • 长度尽量限制在4~8字符之间。
  • 命名空间一般可与项目名称、组织机构名称等保持一致。
  • 根据项目名称构建命名空间:DLQX(电力气象首字母拼接形式),简短明了。
  • 不建议过长的命名空间名称,譬如不推荐采用以下形式:USER_INFO_MANAGE等。

表名称

  • 采用英文单词、阿拉伯数字、连接符(_)的组合形式,其中,单词必须大写,并且首字符必须为英文字符,不能是数字,可用连接符拼接多个单词。
  • 长度尽量限制在8~16字符之间。
  • 尽量采用具有明确意义的英文单词,而不建议采用汉字的拼音字母或者拼音首字母组合。
  • 符合规范的表名称:

USER_INFO_MANAGE、

WEATHER_DATA、

T_ELECTRIC_GATHER等。

列族名称

  • 采用英文单词、阿拉伯数字的组合形式,其中,单词必须大写,并且首字符必须为英文字符,不能是数字。
  • 长度尽量限制在1~6字符之间,过长的列族名称将占用更多的存储空间。
  • 符合规范的列族名称:

D1、D2、DATA等。

  • 不推荐的列族名称:

USER_INFO、D_1等。

列名称

  • 采用英文单词、阿拉伯数字、连接符(_)的组合形式,其中,单词必须大写,并且首字符必须为英文字符,不能是数字,可用连接符拼接多个单词。
  • 长度尽量限制在1~16字符之间。
  • 尽量采用具有明确意义的英文单词,而不建议采用汉字的拼音字母或者拼音首字母组合。
  • 符合规范的列名称:

USER_ID、DATA_1、REMARK等。

  • 不推荐的列名称:

UserID、1_DATA等。

 


 

作者:商兵兵

单位:河南省电力科学研究院智能电网所

QQ:52190634

主页:http://www.cnblogs.com/shangbingbing

空间:http://shangbingbing.qzone.qq.com