一、引言
在之前的文章行式存储VS列式存储中,我们通过对比学习的方式,了解了什么是列式存储。也通过NoSQL-HDFS初识了解了分布式文件系统。
今天我们来聊一下数据库Hbase,它是列式存储的数据库,并且是构建在HDFS上的分布式数据库。它在大型互联网公司的海量数据存储中占有非常重要的地位,值得我们去一探究竟。
这篇文章偏科普性质,属于入门类的,比较基础和简单,后续的文章会逐步深入。好了,就让我们开始Hbase探秘之旅吧。
二、Hbase 简介
Apache Hbase是Hadoop数据库,一个分布式、可扩展、大数据存储,其名称Hbase是Hadoop Database的缩写。
当你需要随机地实时读写大数据时使用Hbase。它的目标是管理超级大表-数十亿行X数百万列。
Hbase是一个开源的、分布式的、带版本的、非关系型数据库,模仿谷歌的BigTable。BigTable使用Google File System作为分布式数据存储,同理Hbase使用HDFS。
要想知道 HBase 的用途,就需要看一看其在 Apache 的 Hadoop 生态系统中的位置,可以看到 HBase 是构建在 HDFS 之上的,这是由于 HBase 内部管理的文件全部都是存储在 HDFS 当中的。同时,MapReduce 这个计算框架在 HBase 之上又提供了高性能的计算能力来处理海量数据。
Google 的三架马车 BigTable、GFS、MapReduce 现在在开源社区中都能找到对应的实现。HBase 就是 Bigtable 的开源实现,当然这句话不是完全正确,因为两者之间还是有些差异的。但是主要还是基于 BigTable 这个数据模型开发的,因此也是具有 Key-Value 特征的,同时也就具有 Bigtable 稀疏的、面向列的这些特性。
也是由于 HBase 利用 HDFS 作为它的文件系统,因此它也具有 HDFS 的高可靠性和可伸缩性。和 Hadoop 一样,HBase 也是依照横向扩展,通过不断地通过添加廉价的服务器来增加计算和存储的能力。BigTable 利用 Chubby 来进行协同服务,HBase 则是利用 Zookeeper 来对整个分布式系统进行协调服务。正是因为通过HDFS 的高可靠可伸缩性,以及应用了 Bigtable 的稀疏的面向列的这些高效的数据组织形式。所以 HBase 才能如此地适合大数据随机和实时读写。
三、Hbase 基本概念
引入
Hbase虽然弱化了结构,但并不等于放任不管。传统关系型数据库在插入数据前表结构(即所有列和列的数据类型)已经是严格确定的。
Hbase的表在放入数据前也有需要确定下来的东西,那就是Column Family(常译为列族/列簇)。单词Family就是家庭的意思,所以列族就是列的家庭。那么列自然就是家庭成员了,通常家庭成员都有多个,所以一个列族包含多个列。
一个家庭的成员之间具有血缘关系,所以一个列族的多个列之间通常也具有某种关系,比如相似或同种类别。所以列族可以看作是某种分类(归类)。
一个非常常见的例子,去面试的时候,一般前台MM都会让填一张表,通常信息很多,每个公司又不尽相同。但大致可以分三类:人员基本信息,教育经历信息,工作经历信息,这三个类别其实就相当于三个列族。如下图:
每个类别里都会有具体的信息,比如人员基本信息里有姓名、电话、出生年月等,它们就相当于一个个标识符(变量名),在Hbase中叫做Column Qualifier(列修饰符)。列修饰符位于列族里面用来标识一条条数据。如下图:
在Hbase中一个列族(Column Family)和一个列修饰符(Column Qualifier)组合起来才叫一个列(Column),使用冒号(:)分割,列族:列修饰符,如下图:
由于把一行数据变成了这样的key-value的形式,所以hbase可以存储上百万列,又由于hbase基于hdfs来存储,所以hbase可以存储上亿行,是一个真正的海量数据库。
在传统数据库中每一行的唯一标识符叫做主键,在Hbase中叫做row key(行键)。如下图:
hbase提供了三种查询方式。
- 第一种是全表扫描,scan
- 第二种是根据一个rowkey进行查询
- 第三种是根据rowkey过滤的范围查询
例如:要查工资不少于20w的记录,就可以用范围查询,查出从startRow=0020到stopRow=9999的所有记录,这是hbase直接支持的一种查询方式哦。
rowkey设计的几个注意点:
- rowkey是按照字符串字典序来组织成B+树的,所以数字的话需要补齐,不然的话会出现123w小于20w的情况,但是补齐的话,你就会发现020w小于123w
- row必须唯一,并且尽可能短:因为rowkey是字符串形式,所以肯定是按照字符串顺序排序咯。而且rowkey有点类似于mysql中的主键吧,所以保证其唯一性也是可以理解的。还有就是因为每个key-value都包含rowkey,所以rowkey越短,越能节省存储空间。
备注:如果rowkey复杂且查询条件复杂,hbase还针对rowkey提供了自定义Filter,所以只要数据在rowkey中有体现,能解析,就能根据自己的条件进行查询。
数据在进入Hbase时都会被打上一个时间戳,这个时间戳可以作为版本号来使用。
在t1时间我存入一个人的基本信息,之后发现姓名错了,在t2时间又更新了姓名,此时并不会去更新原来的那条数据,而是又插入了一条新数据且打上新的时间戳。
此时去查询获取的是新数据,仿佛是更新了,但其实只是默认返回了最新版本的数据而已。如下图:
一个行键、列族、列修饰符、数据和时间戳组合起来叫做一个单元格(Cell)。这里的行键、列族、列修饰符和时间戳其实可以看作是定位属性(类似坐标),最终确定了一个数据。下图中的一行相等于Hbase中的一个单元格:
Q:hdfs是不能随机修改文件的,只能追加,那么hbase里的数据是不是写了之后就不能改也不能删除呢?
hbase是可以修改和删除数据的。hbase通过timestamp来标识数据的版本,hbase的数据都拥有一个版本号,修改数据的时候,并不是真正把数据删除了,而是追加了一条版本最新的数据。
Q:数据修改是通过新增版本号,查询的时候获取最新版本号的数据可以理解,那么删除是怎么实现的呢?
A:删除同样是追加一条版本最新的记录,只不过标识这个数据被删除而已,查询的时候,看到版本最新的记录是数据删除,就知道这个数据被删了。
Q:如果有业务需要经常增删改,那么一条数据就会出现好多的版本记录,这样会不会比较浪费存储空间呢?
例如,一条数据修改了三次,只有最后一条记录是有用的,前面三条记录都没用。
实际上hbase会在合并的时候,将这些用不到的记录删除掉,节省存储空间。
在进行major compact的时候,hbase会将无用的数据进行清理,节省存储空间。
Q:Hbase的合并操作不仅仅是将文件拼起来,还会进行数据清理?
A:不全对,其实hbase把合并分为两种,一种是小合并minor compact,这种方式只会将少数文件进行简单合并,不会进行数据的清理,还有一种是大合并major compact,这种方式会将大部分文件进行合并,并且清理数据。
一次major compact需要把要合并的文件都遍历一遍,读取其中的内容,然后再把同一个rowkey中的老版本清除。如果数据量大,这个过程是非常耗性能的,一般在生产环境都禁止大合并,否则在正常服务的时候突然来个大合并,整个集群可能资源被耗光,没法正常服务。
因此,正常进行的都是minor compact,一般大合并都是在业务低峰期手动进行,例如用一个定时任务,每晚凌晨三点进行大合并。
一个行键、一到多列(包括数据)组合起来叫做一行(Row)。下图中所有1001的数据合起来相当于Hbase中的一行,1002的相当于另一行:
抽象概念
RowKey(行键),顾名思义也就是我们在关系型数据库中常见的主键,它是Unique 的,在 HBase 中这个主键可以是任意的字符串,其最大长度是64K,在内部存储中会被存储为字节数组,HBase 表中的数据是按照 RowKey 的字典序排列的,例如很多索引的实现,包括地理空间索引很大程度就是依赖这个特性。
不过也要注意一个点,现实当中期望排序是1、2、3、4...10,而在 HBase 中1 后面紧跟的会是10。因此,在设计行键的时候一定要充分地利用字典序这个特性,将一下经常读取的行存储到一起或者靠近,减少Scan 的耗时,提高读取的效率。这里一定要说的一点是,行键设计真的很重要,例如做组合行键时将时间排前面,导致写热点(曾经踩过的坑,记忆犹新)。
Column Family(列族),它是由若干列构成,是表 Schema 的一部分,所以需要在创建表的时候就指定好。但也不是所表创建完之后就不能更改列族,只是成本会比较大,因此不建议更改。HBase 中可允许定义的列族个数最多就20多个。列族不仅仅能够帮助我们构建数据的语义边界,还能有助于我们设置某些特性,比如可以指定某个列族内数据的压缩形式。一个列族包含的所有列在物理存储上都是在同一个底层的存储文件当中。
Column (列),一般都是从属于某个列族,跟列族不一样,列的数量一般的没有强限制的,一个列族当中可以有数百万个列,而且这些列都可以动态添加的。这也是我们常说的 HBase 面向列的优点,不像传统的关系型数据库,调整一下 Schema 都需要担心对于生产的影响。
Version Number(版本号),HBase 中每一列的值或者说是每个单元格的值都是具有版本号的,默认使用的系统当前的时间戳,精确到毫秒。当然也可以是用户自己显式地设置,我们是通过时间戳来识别不同的版本,因此如果要自己设置的话,也要保证版本号的唯一性。用户也可以指定保存指定单元格的最后 N 个版本,或者某个时间段的版本,这个是可以在配置中配置的。一个单元格里面是数据是按照版本号降序的。也就是说最后写入的值会被最先读取。
Cell(单元格),一个单元格就是由前面说的行键、列标示、版本号唯一确定的,这里说的列标示包括列族和列名。Cell 中的数据是没有类型的,全部都是字节码。
四、参考资料
- 什么是列式存储
- 你应该知道的 HBase 基础,都在这儿了
- 【生活现场】从洗袜子到hbase存储原理解析