如今Bigtable型(列族)数据库应用越来越广,功能也很强大。但是很多人还是把它当做关系型数据库在使用,用原来关系型数据库的思维建表、存储、查询。本文以hbase举例讲述数据模式的变化。 传统关系型数据库(mysql,oracle)数据存储方式主要如下:
图一 上图是个很典型的数据储存方式,我把每条记录分成3部分:
主键、记录属性、索引字段。我们会对索引字段建立索引,达到
二级索引的效果。 但是随着业务的发展,查询条件越来越复杂,需要更多的索引字段,且很多值都不存在,如下图:
图二
上图是6个索引字段,实际情况可能是上百个甚至更多,并且还需要根据多个索引字段刷选。查询性能越来越低,甚至无法满足查询要求。关系型数据里的局限也开始显现,于是很多人开始接触NoSQL。
列族数据库很强大,很多人就想把数据从mysql迁到hbase,存储的方式还是跟图一或者图二一样,主键为rowkey。其他各个字段的数据,存 储一个列族下的不同列。但是想对索引字段查询就没有办法,目前还没有比较好的基于bigtable的二级索引方案,所以无法对索引字段做查询。
这时候其实可以转换下思维,可以把数据倒过来,如下图:
图三 把各个索引字段的值作为rowkey,然后把记录的主键和属性值按照一定顺序存在对应rowkey的value里。上图只有一个列族,是最简单的方式。 Value里的记录可以设置成定长的byte[],多个记录集合通过移位快速查询到。 但是上面只适合单个索引字段的查询。如果要同时对多个索引字段查询,图三的方式需要求取出所有value值,比如查询“浙江”and“手机”,需要取出两个value,再解析出各自的主键求交。如果每条记录的属性有上百个,对性能影响很大。 接下来的变化是解决多索引字段查询的问题。我们将
主键字段和属性字段分开存储,储存在不同的列族下,多索引查询只需要取出列族1下的数据,再去最小集合的列族2里取得想要的值。储存如图四:
图四
为什么是不同列族,而不是一个列族下的两个列? 列族数据库数据文件是按照列族分的。在取数据时,都会把一个列族的所有列数据都取出来,事实上我们并不需要把记录明细取出来,所以把这部分数据放到了另一个列族下。 接下来是对列族2扩展,列族2储存更多的列,用来做各种刷选、计算处理。如下图:
图五 传统数据库是以数据块来存储数据,简单来说,你的表字段越多,占用的数据空间就越多,那么查询有可能就要跨数据块,将会导致查询的速度变慢。在大型系统中一张表上百个字段,并且表中的数据上亿条这是完全是有可能的。因此会带来数据库查询的瓶颈。我们都知道一个常识数据库中表记录的多少对查询的性能有非常大的影响,此时你很有可能想到分表、分库的做法来分载数据库运算的压力,那么又会带来新的问题,
例如:分布式事务、全局唯一ID的生成、跨数据库查询 等,依旧会让你面对棘手的问题。如果打破这种按照行存储的模式,采用一种基于列存储的模式,对于大规模数据场景这样情况有可能发生一些好转。由于查询中的选择规则是通过列来定义的,因此整个数据库是自动索引化的。按列存储每个字段的数据聚集存储, 可以动态增加,并且列为空就不存储数据,节省存储空间。 每个字段的数据按照聚集存储,能大大减少读取的数据量,查询时指哪打哪,来的更直接。无需考虑分库、分表 Hbase将对存储的数据自动切分数据,并支持高并发读写操作,使得海量数据存储自动具有更强的扩展性。 Java中的HashMap是Key/Value的结构,你也可以把HBase的数据结构看做是一个Key/Value的体系,话说HBase的区域由表名和行界定的。在HBase区域每一个”列族”都由一个名为HStore的对象管理。每个HStore由一个或多个MapFiles(Hadoop中的一个文件类型)组成。MapFiles的概念类似于Google的SSTable。 在Hbase里面有以下两个主要的概念,Row key 和 Column Family,其次是Cell qualifier和Timestamp tuple,Column family我们通常称之为“列族”,访问控制、磁盘和内存的使用统计都是在列族层面进行的。列族Column family是之前预先定义好的数据模型,每一个Column Family都可以根据“限定符”有多个column。在HBase每个cell存储单元对同一份数据有多个版本,根据唯一的时间戳来区分每个版本之间的差异,最新的数据版本排在最前面 。
口水:Hbase将table水平划分成N个Region,region按column family划分成Store,每个store包括内存中的memstore和持久化到disk上的HFile。
上述可能我表达的还不够到位,下面来看一个实践中的场景,将原来是存放在MySQL中Blog中的数据迁移到HBase中的过程:
MySQL中现有的表结构:
迁移HBase中的表结构:
原来系统中有2张表
blogtable和comment表,采用HBase后只有一张blogtable表,如果按照传统的RDBMS的话,blogtable表中的列是固定的,比如schema 定义了Author,Title,URL,text等属性,上线后表字段是不能动态增加的。但是如果采用列存储系统,比如Hbase,那么我们可以定义blogtable表,然后定义info 列族,User的数据可以分为:info:title ,info:author ,info:url 等,如果后来你又想增加另外的属性,这样很方便只需要 info:xxx 就可以了。 对于Row key你可以理解row key为传统RDBMS中的某一个行的主键,Hbase是不支持条件查询以及Order by等查询,因此Row key的设计就要根据你系统的查询需求来设计了额。 Hbase中的记录是按照rowkey来排序的,这样就使得查询变得非常快。
具体操作过程如下:
============================创建blogtable表=========================
create ‘blogtable’, ‘info’,’text’,’comment_title’,’comment_author’,’comment_text’
============================插入概要信息=========================
put ‘blogtable’, ‘1’, ‘info:title’, ‘this is doc title’
put ‘blogtable’, ‘1’, ‘info:author’, ‘javabloger’
put ‘blogtable’, ‘1’, ‘info:url’, ‘http://www.javabloger.com/index.php’
put ‘blogtable’, ‘2’, ‘info:title’, ‘this is doc title2’
put ‘blogtable’, ‘2’, ‘info:author’, ‘H.E.’
put ‘blogtable’, ‘2’, ‘info:url’, ‘http://www.javabloger.com/index.html’
============================插入正文信息=========================
put ‘blogtable’, ‘1’, ‘text:’, ‘what is this doc context ?’
put ‘blogtable’, ‘2’, ‘text:’, ‘what is this doc context2?’
==========================插入评论信息===============================
put ‘blogtable’, ‘1’, ‘comment_title:’, ‘this is doc comment_title ‘
put ‘blogtable’, ‘1’, ‘comment_author:’, ‘javabloger’
put ‘blogtable’, ‘1’, ‘comment_text:’, ‘this is nice doc’
put ‘blogtable’, ‘2’, ‘comment_title:’, ‘this is blog comment_title ‘
put ‘blogtable’, ‘2’, ‘comment_author:’, ‘H.E.’
put ‘blogtable’, ‘2’, ‘comment_text:’, ‘this is nice blog’
HBase的数据查询\读取,可以通过单个row key访问,row key的range和全表扫描,大致如下:
注意:HBase不能支持where条件、Order by 查询,只支持按照Row key来查询,但是可以通过HBase提供的API进行条件过滤。
例如:http://hbase.apache.org/apidocs/org/apache/hadoop/hbase/filter/ColumnPrefixFilter.html
scan ‘blogtable’ ,{COLUMNS => [‘text:’,’info:title’] } —> 列出 文章的内容和标题
scan ‘blogtable’ , {COLUMNS => ‘info:url’ , STARTROW => ‘2’} —> 根据范围列出 文章的内容和标题
get ‘blogtable’,’1′ —> 列出 文章id 等于1的数据
get ‘blogtable’,’1′, {COLUMN => ‘info’} —> 列出 文章id 等于1 的 info 的头(Head)内容
get ‘blogtable’,’1′, {COLUMN => ‘text’} —> 列出 文章id 等于1 的 text 的具体(Body)内容
get ‘blogtable’,’1′, {COLUMN => [‘text’,’info:author’]} —> 列出 文章id 等于1 的内容和作者(Body/Author)内容
我的废话2:
有人会问Java Web服务器中是Tomcat快还是GlassFish快?小型数据库中是MySQL效率高还是MS-SQL效率高?我看是关键用在什么场景和怎么使用这 个产品(技术),所以我渐渐的认为是需要对产品、技术本身深入的了解,而并非一项新的技术就是绝佳的选择。试问:Tomcat的默认的运行参数能和我们线 上正在使用的GlassFish性能相提并论吗?我不相信GlassFishv2和GlassFishv3在默认的配置参数下有显著的差别。我们需要对产 品本身做到深入的了解才能发挥他最高的性能,而并非感观听从厂家的广告和自己的感性认识 迷信哪个产品的优越性。
我的废话3:
对于NOSQL这样的新技术,的的确确是可以解决过去我们所需要面对的问题,但也并非适合每个应用场景,所以在使用新产品的同时需要切合当前的产品需要, 是需求在引导新技术的投入,而并非为了赶时髦去使用他。你的产品是否过硬不是你使用了什么新技术,用户关心的是速度和稳定性,不会关心你是否使用了 NOSQL。相反Google有着超大的数据量,能给全世界用户带来了惊人的速度和准确性,大家才会回过头来好奇Google到底是怎么做到的。所以根据 自己的需要千万别太勉强自己使用了某项新技术。
我的废话4:
总之一句话,用什么不是最关键,最关键是怎么去使用!
文章2:
前面刚开始使用HBase只是用于存取某些简单的JAVA对象或是简单数据,所以一般设置列族和列标示时只用一个就行了。
最近有个任务是把系统中的站内消息移到HBase当中去,才开始查HBase中的一对多关系,发现网上的资料讲的都不甚详尽,这篇blog记录一下我的设计和想法,这些想法毕竟未经证实,尚需验证。如果有大虾认为有不妥甚至错误的地方请不吝指教。
首先讲两个我参考的资料,背景:一个主贴和N个回帖的一对多关系,学过一点数据库的应该都能体会到,图我就不画了:
1.官方推荐资料:
http://wiki.apache.org/hadoop/Hbase/DataModel
2.一位大大的简单HBase一对多表结构的介绍(感觉实际上他参考了资料1,不过讲的不太。。合理,而且下面列表的那个comment_title应该是写错了,一对多的那个例子貌似也让人很不解):
http://doudouclever.blog.163.com/blog/static/17511231020127893233972/
最终的解决方案是这个表(按照官方资料):
Table | Row Key | Family | Attributes(ColumnKeys/Qualifiers) |
BlogTable | ID | info: | Author,Title,URL |
text: | No ColumnKey,3version | ||
comment title: | Column keys are written like YYYMMDDHHmmss. Should be IN-MEMORY and have a 1 version | ||
comment author: | Same keys. 1 Version | ||
comment text: | Same keys. 1 Version |
因为刚开始看的是第二个资料,官方资料也没细看,导致理解偏差了。一直想明白这个一对多是怎么设计的,其实了解以下两个知识点就可以了:
1.HBase的二维表结构:
三个重要概念是Column Family(以下简称为CF)和Column Key/Qualifier(以下简称为CK)还有RowKey。一个CF可以包含若干个CK。相当于CF是个合并单元格;CK才是具体的列标示,并且可以为空。Rowkey就是行标示,可以理解为主键。如下图所示:
2.Hbase中,对于某个Column Family中的Column Key是可以动态增加的
存储于关系型数据库中的数据如下,简单起见某些字段删减了:
表头
ID | Author | Title | Body |
1 | 张三 | 消息头 | 这是内容Hello World! |
明细表:
ID | HeadID | CommentAuthor | Title | Body |
1 | 1 | 李四 | 回复头1 | 这是回复内容1 |
2 | 1 | 王五 | 回复头2 | 这是回复内容2 |
转移到Hbase中存储,需要把以前的明细“纵向延伸”(对于同一表头,明细表一条一条向下加数据),转变为HBase的“横向延伸”(对同一RowKey,添加明细的ColumnKey),Hbase中存储的数据如下,iteye的表不会弄合并单元格,所以用excel截图来展示吧:
结论:从图中可以看出,HBase是把以前关系数据库明细表的字段作为ColumnFamily,而明细表的主键作为ColumnKey的这种结构来达到一对多的效果的。关系型数据库,明细增多时是纵向添加数据;对于Hbase,则是通过ColumnKey的增加来添加数据
由此可能产生的问题:
2015.10.16 14:00 willem Hbase列族设计查询资料摘抄