目录
1.4.1 模式创建
1.4.2 Rowkey设计
1.4.3 列族定义
1.4.3.1 可配置的数据块大小
1.4.3.2 数据块缓存
1.4.3.3 布隆过滤器
1.4.3.4 数据压缩
1.4.3.5 单元时间版本
1.4.3.6 生存时间
1.4.4 模式设计实例
1.4.4.1 实例1:动物分类
1.4.4.2 实例2:店铺与商品
1.4.4.3 实例3:网上商城用户消费记录
1.4.4.4 实例4:微博用户与粉丝
1.4.4.5 小结
本文参考于学校《HBase应用于开发》教材
前言
本节将介绍如何设计HBase的模式(Schema),将对比HBase与RDBMS的模式设计的异同,重点介绍模式设计中的两个要点——Rowkey和Column Family,并结合4个实例介绍如何进行HBase的模式设计。
数据库的模式设计并不是一个新概念,在关系型数据库之前,模式设计的范式已经存在。其实,只要是可以称为“数据库”的系统,都存在模式设计的问题。作为一种典型的列式存储数据库,HBase的模式设计同样非常重要。HBase的模式设计和传统数据库的模式设计存在什么样的差别,更需要关注的是哪些关键属性,如何设计这些属性,如何通过更直观、清晰地认识模式设计,这些都是本节讲解的要点。
1.4.1 模式创建
要知道HBase的表如何创建,首先需要了解HBase的模式结构,包括表、Rowkey、列族、Timestamp(时间版本)。其实模式是一个三维有序结构,前面三个维度确定一行数据。
HBase的模式不同于关系型数据库(RDBMS),HBase与RDBMS的区别在于:HBase的单元格(cell)所在的行是有序的,其列(Qualifier)在所属列族(Column Famliy)存在的情况下,可以通过客户端自由添加,如表 1.4.1所示。
表 1.4.1 HBase表设计模式与RDBMS对比
属性 | HBase | RDBMS |
数据类型 | 只有字符串 | 丰富的数据类型 |
数据操作 | 简单的增删改查,不支持Join | 各种各样的函数与表连接 |
存储模式 | 基于列式存储 | 基于表格结构和列式存储 |
数据保护 | 更新后仍然保留旧版本 | 替换 |
可伸缩性 | 轻易地增加节点,兼容性高 | 需要中间层,牺牲功能 |
通过HBase的模式与传统数据库系统对比,我们可以更清晰地了解HBase的结构:其实HBase和关系型数据库是不同的两种系统,它们拥有不同的设计特性。
在HBase模式设计过程中需要考虑不少因素,这些因素的列表如下:
1)这个表应该有多少个列族。
2)列族使用什么数据。
3)每个列族应该有多少列。
4)列名是什么,尽管列名不必在建表的时候定义,但是读写数据是需要知道的。
5)单元应该存放什么数据。
6)每个单元存储多少个时间版本。
7)行健(Rowkey)结构是什么,应该包含什么信息。
HBase模式的设计更需要注意两个关键点:
(1) Join
HBase中没有Join的概念,所以不支持Join操作,而在不少业务需求中,需要使用Join操作而大表结构可以解决这一问题,只需要一条行记录加上一个特定的行关键字,就可以实现把所有关于Join的数据合并在一起。
(2) Rowkey
Rowkey非常重要,在设计时需要慎重考虑。以存储用户观影记录数据为例,复合的Rowkey由用户ID作为前缀(方便把某用户的观影记录聚合在一起),倒置的时间串作为Rowkey的后缀可以使观影记录数据从新到旧排列。如果Rowkey是整型的,用二进制的方式应该比用string来存储一个数据更节约空间。
带着上面提到的若干问题,下面介绍如何设计HBase表结构。
1.4.2 Rowkey设计
Rowkey是不可分割的字节数,按字典排序由低到高存储在表中。一个空的数组用来标识表空间的起始或结尾。
在设计HBase表时,Rowkey设计是最重要的事情,应该基于预期的访问模式来为Rowkey建模。Rowkey决定了访问HBase表时可以得到的性能,原因有两个:Region基于Rowkey为一个区间的行提供服务,并且负责区间的每一行;HFile在硬盘上存储有序的行。这两个因素是相互关联的。当Region将内存中数据刷写为HFile时,这些行已经排过序,也会有序地写到硬盘上。Rowkey的有序特性和底层存储格式可以保证HBase表在设计Rowkey之后的良好性能。
关系型数据库可以在多列上建立索引,但是HBase只能在Rowkey上建立索引。访问数据库的最主要方法就是使用Rowkey。非Rowkey访问,即在不清楚Rowkey前提下访问表,可以使用扫描全表。设计Rowkey有各种技巧,而且可以针对不同访问模式进行优化,我们接下来就研究一下。
Rowkey是HBase的KeyValue存储中的Key,通常将用户要查询的字段作为Rowkey,查询结果作为Value下面介绍Rowkey设计需要的注意要点。在进行Rowkey设计时用户需要根据不同的需求有针对性地选择或优化这几点。
1. Rowkey是以字典顺序从大到小排序
原生HBase只支持从小到大的排序,但是现在有个需求想展现影片热度排行榜,这就要求实现从大到小排列,针对这种情况可以采用Rowkey=Integer.MAX_VALUE-Rowkey的方式将Rowkey进行转换,最大的变最小,最小的变最大,在应用层再转回来即可完成排序需求。
2. RowKey尽量保证散列设计
保证散列设计可以确保所有的数据都不是在一个Region上,从而避免读写的时候负载会集中在个别Region上。
假设我们需要存储一个视频网站用户的所有观影记录(暂时不需要考虑时间倒排),这时候的Rowkey可设计为userid_videoid的拼接字符串,但是这样设计的话,userid的分布就很可能不均匀,因为Rowkey是按字符串排序的(默认字典顺序排序)。有三种办法来解决这个问题,具体如下:
- Ø 反转userid,将userid字符串反转后存储;
- Ø 散列userid,即对userid进行散列;
- Ø userid取模后进行MD5加密,取前6位作为前缀加入到userid前面。
假设某视频网站的用户正在观看视顿,但此时,要开辟一个新栏目,所有的用户观影记录都按照时间倒排序展示在这个栏目中。这个时候,就需要为原来的userid_videoid建立一张索引表,并且这个表的Rowkey要和时间相关。
Rowkey设计可以使用当前时间(观影时间)的Long值作为Rowkey的前缀、另外建议Rowkey最好都是String:一是方便线上使用shell查数据、排查错误;二是更容易让数据均匀分布;三是不必考虑存储成本。
3. RowKey的长度尽量短
如果Rowkey太长,第一存储开销会增加,影响存储效率;第二内存中Rowkey字段过长,会导致内存的利用率降低.进而降低索引命中率:
一般的做法是:
- Ø 时间使用Long来表示。
- Ø 尽量使用编码压缩。
1.4.3 列族定义
列族(column Family)是一些列的集合。一个列族的所有列成员有着相同的前缀。比如,列courses:history和courses:math都是列族courses的成员:冒号(:)是列族的分隔符,来区分前缀和列名。列族前缀必须是可输出的字符,剩下的部分称为列(Qualifier),可以由任意字节数组组成一列族必须在表建立的时候声明:列则不需要特别声明,用户随时可以创建新列。
在物理上,一个列族的成员在文件系统上都存储在一起,因为存储优化都是针对列族级别的,这就意味着,访问一个列族的所有成员的方式都是相同的。
现在HBase并不能很好地处理两个或者三个以上的列族,所以应尽量减少列族数量。目前,flush和compaction操作是针对一个Region的,所以当一个列族操作大量数据的时候会引发一个flush那些不相关的列族也要进行nush操作,尽管它们没有操作多少数据。compaction操作现在是根据一个列族下全部文件的数量触发的,而不是根据文件大小触发的。当很多的列族在进行flush和compaction时,会造成很多没用的I/O负载,要解决这个问题,需要将flush和compaction操作只针对一个列族进行。
尽量在应用中使用一个列族。如果所有查询操作只访问一个列族,可以引入第二个和第三个列族。基于flush性能的考虑,列族数量越少越好。
如果一个表存在多个列族,当基数差距很大时,例如,A族有100万行,B族有10亿行,A族可能会被分散到很多Region,导致扫描A的效率降低多个列族在执行flush和compaction时,会造成很多I/O负载。
下面是列族相关的配置属性,这此属性都有默认值,如果在创建表的时候不显式指定,则使用默认值。
1.4.3.1 可配置的数据块大小
HFile数据块大小可以在列族层次设置。这个数据块不同于之前谈到的HDFS数据块,其默认位是65536字节,或64KB。数据块是索引存储的,每个HFile数据块的起始键数据块大小的设置影响数据块索引的大小。数据块越小,索引越大,从而占用更大内存空间。同时载进内存的数据块越小,随机查找性能更好。但是,如果需要更好的序列扫描性能,那么一次能够加载更多HFile数据进入内存更为合理,这意味着应该将数据块设置为更大的值。相应地,索引变小,将在随机读性能上付出更多的代价。
可以在表实例化时设置数据块大小,代码如下:
Hbase(main):002> create ‘mytable’
{NAME => ‘colfam1’, BLOCKSIZE => ‘65536’}
1.4.3.2 数据块缓存
把数据放进读缓存,并不是一定能够提升性能。如果一个表或表的列族只被顺序化扫描访问或很少被访问,则Get或Scan操作花费时间长一点是可以接受的。在这种情况下,可以选择关闭列族的缓存。如果只是执行很多顺序化扫描,会多次使用缓存,并且可能会滥用缓存,从而把应该放进缓存获得性能提升的数据给排挤出去。如果关闭缓存,不仅可以避免上述情况发生,而且可以让出更多缓存给其他表和同一表的其他列族使用。
数据块缓存默认是打开的。可以在新建表或更改表时关闭数据块缓存属性:
Hbase(main):002:0>create ‘mytable’, { NAME => ‘colfam1’, BLOCKCACHE => ‘false’ }
IN_MEMORY参数的默认值是false,该值表示HBase除了在数据块缓存中保存这个列族,相比其他列族更激进之外,并不提供其他额外的保证。该参数在实际应用中设置为true。此时访问性能不会变化太大。下面代码展示如何设置lN_MEMORY属性:
Hbase(main):002:0>create ‘mytable’, { NAME => ‘colfam1’, IN_MEMORY => ‘true’ }
1.4.3.3 布隆过滤器
数据块索引提供了一个有效的方法getDataBlocklndexReader(),在访问某个特定的行时用来查找应该读取的HFile的数据块。但是该方法的作用有限。HFile数据块的默认大小是64KB,一般情况下不能调整太多。
如果要查找一个很短的行,只在整个数据块的起始行键上建立索引是无法给出更细粒度的索引信息的。例如,某行占用100字节存储空间,一个64KB的数据块包含(64*1024)/100=655.53,约700行,只能把起始行放在索引位上。要查找的行可能落在特定数据块上的行区问,但也不能肯定存放在那个数据块上,这就导致多种可能性:该行在表中不存在,或者存放在另一个HFile中,甚至在MemStore中。这些情况下,从硬盘读取数据块会带来I/O开销,也会滥用数据块缓存,这会影响性能,尤其是当面对一个巨大的数据集且有很多并发读用户时。
布隆过滤器(Bloom Filter)允许对存储在每个数据块的数据做一个反向测验。当查询某行时,先检查布隆过滤器,看看该行是否存在这个数据块。布隆过滤器要么确定回答该行不在,要么回答不知道,因此称之为反向测验。布降过滤器也可以应用到行内的单元格上,当访问某列标识符时先使用同样的反向测验。
使用布降过滤器也不是没有代价,相反,存储这个额外的索引层次占用额外的空间。布隆过滤器的占用空间大小随着它们的索引对象数据增长而增长,所以行级布隆过滤器比列标识符级布隆过滤器占用空间要少。当空间不是问题时,它们可以压榨整个系统的性能潜力。
可以在列族上打开布隆过滤器,代码如下:
HBase(main):007:0>create ‘mytable’, { NAME => ‘colfam1’, BLOOMFILTER => ‘ROWCOL’ }
注意,数据只在硬盘上是压缩的,在内存中(MemStore或BlockCache)或在网络传输时是没有压缩的,不能经常改变数据的压缩编码,但是如果的确需要改变某个列族的压缩编码,也可以直接更改表定义,设定新的压缩编码。此后Region合并时,生成的HFile全部会采用新编码压缩。这个过程不需要创建新表和复制数据。
1.4.3.4 数据压缩
HFile可以被压缩并存放在HDFS上,这有助于节省硬盘I/O,但是读写数据时压缩和解压缩会提高CPU利用率。压缩是表定义的一部分,可以在建表或模式改变时设定。除非确定压缩不会提升系统的性能,否则推荐打开表的压缩。只有在数据不能被压缩,或者因为某些原因服务器的CPU利用率有限制要求的情况下,有可能需要关闭压缩特性。
HBase可以使用多种压缩编码,包括LZO、SNAPPY和GZIP,LZO和SNAPPY是其中最流行的两种。SNAPPY由Google在2011年发布,发布不久Hadoop和HBase项目开始对其提供支持。在此之前,选择的是LZO编码。Hadoop使用的LZO原生库受GPLv2版权控制,不能放在Hadoop和HBase的任何发行版中,必须在它们中单独安装;另外,SNAPPY拥有BSD许可(BSD-licensed),所以它更容易和Hadoop及Haase发行版捆绑在一起。LZO和SNAPPY的压缩比例和压缩/解压缩速度差不多。
当建表时可以在列族上打开压缩,代码如下:
Hbase(main):002:0> create ‘mytable',{ NAME => ‘colfaml1’, COMPRESSION =>'SNAPPY’ }
注意,数据只在硬盘上是压缩的,在内存中(MemStore或BlockCache)或在网络传输时是没有压缩的。
不能经常改变数据的压缩编码,但是如果的确需要改变某个列族的压缩编码,也可以直接更改表定义,设定新的压缩编码。此后Region合并时,生成的HFile全部会采用新编码压缩。这个过程不需要创建新表和复制数据。
1.4.3.5 单元时间版本
在默认情况下,HBase的每个单元格只维护三个时间版本。该属性也是可以设置的。如果只需要一个版本,在创建表时可以直接将VERSIONS参数设置为1,这样系统就不会保留更新单元的多个时间版本。时间版本也是在列族级设置的,代码如下:
Hbase(main):002:0> create ‘mytable’, { NAME => ‘colfam1’, VERSIONS => 1 }
可以在同一个建表语句里为列族指定多个属性,代码如下:
Hbase(main):002:0> create ‘mytable’, { NAME => ‘colfam1’, VERSIONS => 1, TTL => ‘18000’ }
也可以指定列族存储的最少时间版本数,代码如下:
Hbase(main):002:0> create ‘mytable’, { NAME => ‘colfam1’, VERSIONS => 5, MIN_VERSIONS => ‘1’ }
在列族上同时设定TTL也是非常有用的。如果当前存储的所有时间版本都早于TTL,至少MIN_VERSION个最新版本会保留下来,这样可以确保在杳询以及数据早于TTL时有结果返回。
1.4.3.6 生存时间
生存时间(Time To Live,TTL),用于设置单元格的生存周期。如果单元格过期,则会将其删除。应用系统经常需要从数据库中删除旧数据,因为数据库很难超过某种规模。传统数据库中内置了许多灵活的处理方法。例如,在一个视频网站用户的观影系统中,不想删除用户所有观影记录。这些都是用户生成的数据,将来某一天执行一些深度挖掘分析时可能有用,但是不需要保持所有观影记录都能实时访问,所以可以将早于某个时间的观影记录归档存放到平面文件中。
早于指定TTL值的数据在下一次大合并时会被删除。如果在同一单元格中有多个时间版本,早于设定TTL值的版本会被删除。可以禁用TTL,或者通过设置其位为INT.MAX_VALUE(2147483647)让它永远启用,这也是默认值,单位是秒。可以在建表时设置TTL,代码如下:
Hbase(main):002:0> create ‘thetable’, {NAME => ‘cf1’, TTL => ‘18000’ }
该命令在列族cf1上设置TTL为18000s,即5小时。cf1中超过5小时的数据会在下一次大合并时被删除。
1.4.4 模式设计实例
在设计教据库表的时候,考虑设计要满足功能需求是最基本的工作。下面将通过4个实例讲解如何进行HBase表结构设计。主要通过数据和功能需求两方面来对比传统关系型教据库与HBase表结构设计的异同。
1.4.4.1 实例1:动物分类
1. 数据需求
如果一位生物科学家要存储一些动物相关信息,其中要包括动物的大类,以从一些大类动物下包括的小类,这样可以方便今后查询某种具体动物属于哪一类别,以及动物名字具体是什么。下面简单列举一些动物分类如下:
- Ø Animal
- Ø Pig
- Ø Cat
- Ø Monkey
- Ø Dog: Red dog和Black dog
- Ø Tiger: Tiger of northeast
其中,Animal是顶级分类,Pig、Cat、Monkey、Dog和Tiger属于一级分类,而Dog中的Red dog和Black dog,以及Tiger中的Tiger of northesst属于二级分类。
2. RDBMS表结构设计
动物分类在关系型数据库的设计中只需要考虑主键的映射关系即可,需要一个分类表结构。每种动物都有几个关键字段:id、name、parent_id和child_id,如表 1.4.2所示。
表 1.4.2 RDBMS中的动物分类表结构
id PK | name | parent_id | child_id |
1 | animal |
| 2,3,4,5 |
2 | pig | 1 |
|
3 | cat | 1 |
|
4 | monkey | 1 |
|
5 | dog | 1 | 7,8 |
6 | red-dog | 1.5 |
|
7 | black-dog | 1.5 |
|
8 | tiger | 1 | 9 |
9 | tiger-of-northeast | 1.8 |
|
对于动物分类的HBase表结构设计,Rowkey对应RDBMS中的id,共有三个列族:name、parent、和child,详细设计如表 1.4.3所示。
表 1.4.3 HBase中的动物分类表结构
Rowkey | Column Famlily | ||
<id> | name | parent | child |
|
| parent:<id> | child:<id> |
1 | animal |
| child:2=cat child:3=monkey child:4=dog child:5=tiger child:6=pig |
|
|
|
|
4 | dog | parent:1=animal | child:7=reddog child:8=blackdog |
7 | reddog | perent:1=animal parent:4=dog |
|
通过这个简单的例子可以行出,这两种表结构设计有本质的区别,一个是行式存储,一个是列式存储,所以刚刚接触HBase的读者需要转换思维设计表结构,这样才能够更好地掌握HBase的表模式设计。
1.4.4.2 实例2:店铺与商品
电商行业中店铺和商品都是最基本的概念,本实例将描述这两者之间的关系。本实例是涉及多表关联关系的设计。
1. 数据需求
某电商网站要存储在本网站的店铺属性信息,同时要知道这典店铺都卖了哪些商品,以及这些商品的详细信息。下面是店铺和商品之间关系的描述。
店铺表: Shop: Item=1:N
商品表: Item: Shop=1:N
其中,Shop是店铺,Item是商品。1:N表示一对多。
2. RDBMS表结构设计
关系型数据库中店铺表共包含三个字段:name、address和regdate,分别表示铺名称、所在地和注册日期,具体描述如表 1.4.4所示。
表 1.4.4 RDBMS中的店铺表结构
列名 | 列含义 |
id PK | 主键 |
name | 店铺名称 |
address | 所在地 |
regdata | 注册日期 |
商品表共包含四个字段:name、price、details和title,分别表示商品名称、价格、商品详情和展示名称,具体描述如表 1.4.5所示。
表 1.4.5 RDBMS中的商品表结构
列名 | 列含义 |
id PK | 主键 |
name | 商品名称 |
price | 价格 |
details | 商品详情 |
title | 展示名称 |
店铺商品对应关系表是表示店铺和商品的映射关系表,共三个字段:shop_id、item_id和type,详细解释如表 1.4.6所示。
表 1.4.6 RDBMS中的店铺与商品对应关系表结构
列名 | 列含义 |
shop_id | 店铺主键 |
item_id | 商品主键 |
type | 关联类型 |
3. HBase表结构设计
店铺表主键是RDBMS中的店铺表的id,共有两个列族:info和item,info表示店铺的静态属性,item表示店铺与商品的关联关系,如表 1.4.7所示。
表 1.4.7 HBase中的店铺表结构
Rowkey | Column Famlily | |
<shop_id> | info | item |
info:name info:address info:regdate | item:<item_id>=type |
商品表主键是RDBMS中的商品表的id。共有两个列族:info和shop。info表示商品的静态属性,shop表示商品与店铺的关联关系,如表 1.4.8所示:
表 1.4.8 HBase中的商品表结构
Rowkey | Column Famlily | |
<shop_id> | info | shop |
info:name info:price info:details info:title | shop:<shop_id>=type |
通过对比我们可以看出,RDBMS表结构设计是通过三个表来实现的:一个表存储商品的详细信息,一个表存储店铺的详细信息,还有一个是店铺与商品的对应关系表。而HBase表结构设计是通过两个表实现的,每个表中都存储了店铺与商品的关联关系,并分别存储了商品与店铺的详细信息。如果想查询某个店铺所卖的商品的详细信息,RDBMS表要通过对商品详细信息和商品与店铺对应关系表进行Join来实现,而HBase表只需找到存储了商品的详细信息的表来查询即可。
1.4.4.3 实例3:网上商城用户消费记录
现在网购已经成为潮流,用户在网上商城购买商品产生购买行为,网站一定会记录下用户的消费记录,本案例就是对网上商城的用户消费记录进行分析,提炼数据模型,并对比关系型数据库和HBase数据库的表结构设计的异同。
1. 数据需求
某电商要存储用户购买商品的信息,并且想知道该用户最近一段时间都购买了哪些商品,从而分析该用户近期的消费状态,具体的查询需求如下:
用户购买的商品
查询用户最近购买的商品
2. RDBMS表设计
为了加速查询,需要对usr_id建立索引,但是建立索引的代价是索引的重建导致插入速度减慢。我们关系型数据库中用户消费记录表Sale,具体表述如表 1.4.9所示。
表 1.4.9 RDBMS中的Sale表结构
列名 | 列含义 |
Id PK | 主键 |
user_id IDX | 用户ID,创建索引 |
product | 商品 |
time | 购买时间 |
3. HBase表结构设计
在HBase中设计用户消费表Sale时,Rowkey的设计非常重要。本例使用<usr_id><Long.MAX_VALUE-System.currentTimeMillis()><product_id>作为Rowkey,该Rowkey共由三个部分组成:user_id,Long.MAX_VALUE-System.currentTimeMillis()和product_id,其中,Long.MAX_VALUE-System.currentTimeMillis()是为了使得用户的最近消费记录能够按照时间顺序排列。详细设计如表 1.4.10所示。
表 1.4.10 HBase中的Sale表结构
Rowkey | Column Family |
<user_id> <Long.MAX_VALUE-System.currentTimeMillis()> <product_id> | name |
name:product name:time |
1.4.4.4 实例4:微博用户与粉丝
很多人都在玩微博,相信大家都知道微博上用户和粉丝都是非常紧密的关系。本例将尝试提炼用户与粉丝关系的数据模型,并对比RDBMS和HBase设计表结构的异同。
1. 数据描述
微博网站既要存储用户的详细信息,也要存储该用户所有的粉丝对应关系。用户粉丝的对应关系是一对多,即user:fans=1:N,user是用户,fans是粉丝,1:N表示一对多。基于这些数据关系,数据处理是可以查询用户的所有粉丝。
2. RDBMS表结构设计
RDBMS中的用户表共有5个字段:id、nickname、gender、dob和address,详细的字段说明如表 1.4.11所示。
表 1.4.11 RDBMS中的用户结构
列名 | 列含义 |
id | 主键 |
nickname | 用户昵称 |
gender | 性别 |
dob | 出生日期 |
address | 所在地 |
用户对应表共有三个字段;user_id和fans_id和type,详细字段解释如表 1.4.12所示。
表 1.4.12 RDBMS中的用户粉丝对应表结构
列名 | 列含义 |
user_id | 用户主键 |
fans_id | 粉丝用户主键 |
type | 互粉类型 |
3. HBase表结构设计
表 1.4.13是用户与粉丝关系表的HBase结构。其中,主键是RDBMS中的用户主键。列族有两个:info和fans。info表示用户的静态属性信息,fans表示该用户的粉丝关系的。因为粉丝也是微博用户,所以粉丝的id也是user_id,在fans列族中的粉丝id也使用user_id。
表 1.4.13 HBase中的用户与粉丝表结构
Rowkey | Column Family | |
<user_id> | info | fans |
info:nickname info:gender info:dob info:address | fans:<user_id>=type |
通过对比可以看出,RDBMS是使用两个表来设计实现的,一个表存储用户的详细信息,一个表存储用户与粉丝的对应关系。而HBase通过一个表存储用户的详细信息和粉丝对应关系。
1.4.4.5 小结
我们带着一些问题开始了本章的学习,现在我们知道,HBase表如何设计是与需求直接相关的,如果想设计优秀的表结构,关键是先清理业务场景需求。当然HBase表结构设计有一些硬性指标,遵循这些指标对HBase性能的的提升有很大帮助。例如,Rowkey本身是按字典升序排列,每个表最好不超过三个列族等。