(H2与HBase)面向行or面向列的存储模型?

简介:
(H2与HBase)面向行or面向列的存储模型?



1. 目录 
2. 0. 示例 
3. 
4. 1. H2怎么存储pet表的记录? 
5. 11 DATA_LEAF页格式 
6. 12 DATA_NODE页格式 
7. 
8. 2. HBase怎么存储pet表的记录? 
9. 21 Data Block格式 
10. 22 Data Block如何存下面这些记录? 
11. 23 leaf索引块的格式: 
12. 24 root索引块的格式: 
13. 25 IntermediateLevel索引块 

 
 



## 0. 示例

假设有如下一张pet表 (改编自MySQL参考手册)


1. CREATE TABLE pet ( 
2. id INT PRIMARY KEY
3. name VARCHAR(20), 
4. owner VARCHAR(20), 
5. species VARCHAR(20), 
6. sex CHAR(1) 
7. ); 

 
 
有如下记录:


1. id name owner species sex 
2. =============================== 
3. 1001 Fluffy Harold cat f 
4. 1002 Claws Gwen cat m 
5. 1003 Buffy Harold dog f 

1. H2怎么存储pet表的记录?H2是一个Java SQL Database Engine,使用面向行(row-oriented)的存储模型(如果觉得拗口,就叫基于行(row-based)的存储模型吧)。

表元数据与表的记录分开,可以通过与INFORMATION_SCHEMA相关的系统表来查找元数据,

还可以通过JDBC的java.sql.DatabaseMetaData提供的相关API来查找。

H2内部的存储引擎使用页(Page)来组织数据,页的大小默认是2K,可通过参数PAGE_SIZE调整,

有8种不同的页:

DATA_LEAF

DATA_NODE

DATA_OVERFLOW

BTREE_LEAF

BTREE_NODE

FREE_LIST

STREAM_TRUNK

STREAM_DATA

本文只是说明H2怎么按行的方式来组织数据,所以只重点讲DATA_LEAF、DATA_NODE这两种页。

### 1. 1 DATA_LEAF页格式



1. 项 占用字节数 
2. ====================================================== 
3. type 1 
4. checksum 2 (程序代码中一般是先用0值占位,等写完一页后对此页的数据计算校验和再回填) 
5. parentPageId 4 (就是父DATA_NODE节点的ID) 
6. tableId 可变int (该页所属的表的ID) 
7. columnCount 可变int (该页所属的表有多少列) 
8. entryCount 2 (该页有多少条记录) 
9. 
10. entryCount个 
11. { 
12. key 可变long (如果表存在主键,并且是BYTE、SHORT、INT、LONG类型,这个key就是主键的值,否则为每行生成一个唯一的数值) 
13. offset 2 (该行在此页中的相对位置) 
14. } 
15. 
16. entryCount个 
17. { 
18. columnValues 每一行记录从对应的offset位置开始存放,不同类型的字段会使用1个Byte来标识自己的格式 
19. } 
20. ====================================================== 

 
 
DATA_LEAF页并不存放表名、列名、列类型这些元数据

对于pet表中的三行记录放到一个DATA_LEAF页中会是这样(为了方便阅读未细化每一个字节,实际更复杂一些):


1. id name owner species sex 
2. =============================== 
3. 1001 Fluffy Harold cat f 
4. 1002 Claws Gwen cat m 
5. 1003 Buffy Harold dog f 



1. 项 值 
2. ====================================================== 
3. type 17 (Page.TYPE_DATA_LEAF | Page.FLAG_LAST(当前只有一个页)) 
4. checksum 0x75CE (校验和计算比较复杂,也受当前pageId、pageType、pageSize影响) 
5. parentPageId 0 (就是父DATA_NODE节点的ID,0说明是root节点) 
6. tableId 14 (该页所属的表的ID) 
7. columnCount 5 (该页所属的表有多少列) 
8. entryCount 3 (该页有多少条记录) 
9. 
10. { 
11. 100120241002200310031980 (其中100110021003是key,202420031980是offset) 
12. } 
13. { 
14. ( / key:1001 / 1001‘Fluffy’‘Harold’‘cat’‘f’
15. ( / key:1002 / 1002‘Claws’‘Gwen’‘cat’‘m’
16. ( / key:1003 / 1003‘Buffy’‘Harold’‘dog’‘f’
17. } 
18. ====================================================== 

 
 

### 1. 2 DATA_NODE页格式

当某个DATA_LEAF页(page0)的大小超过pageSize时,会把它切掉一部分,得到一个新的DATA_LEAF页(page1),

page0从切割点开始往右的keys和记录被转到page1中,切割点左边的继续留在page0,

同时生成一个新的DATA_NODE页作为page0、page1的父节点。

DATA_NODE页格式

  1. 项 占用字节数
  2. ======================================================
  3. type 1
  4. checksum 2 (程序代码中一般是先用0值占位,等写完一页后对些页的数据计算校验和再回填)
  5. parentPageId 4 (就是父DATA_NODE节点的ID)
  6. tableId 可变int (该页所属的表的ID)
  7. rowCountStored 4
  8. entryCount 2
  9. entryCount个
  10. {
  11. childPageId 4
  12. key 可变long
  13. }
  14. ====================================================== 
 
 

2. HBase怎么存储pet表的记录?

HBase使用面向列(column-oriented)的存储模型,不需要定义表的结构(schema-free),

可以随时动态添加新的列,理论上对于列的个数没有限制,

如果列很多,可以把相关的一组列归属到一个列族中(Column Family),充分发挥数据的聚合特性。

通过RowKey能把同一个或多个列族中的列关联起来,

HBase的RowKey和列族的组合有点类似于传统关系数据库中有外键引用关系的两个关联表,

但是只是型像神不想,HBase中两个不同列族是平等的,没有主从关系,只是通过RowKey把两者关联起来。

HBase的Data Block对应H2的DATA_LEAF,

HBase的Leaf Index Block对应H2的DATA_NODE,

2. 1 Data Block格式

Data Block的格式有点复杂,如果不打算看代码可以不用关心的

每个block有一个33字节的头

===========================

前8个字节是表示block类型的MAGIC,对应org.apache.hadoop.hbase.io.hfile.BlockType的那些枚举常量名,

接着4个字节表示onDiskBytesWithHeader.length - HEADER_SIZE

接着4个字节表示uncompressedSizeWithoutHeader

接着8个字节表示prevOffset (前一个块的offset,比如,对于第一个块,那么它看到的prevOffset是-1,对于第二个块,是0)

接着1个字节表示checksumType code(默认是1: org.apache.hadoop.hbase.util.ChecksumType.CRC32)

接着4个字节表示bytesPerChecksum(默认16k,不能小于block头长度(头长度是33个字节))

最后4个字节表示onDiskDataSizeWithHeader

当不使用压缩时onDiskBytesWithHeader不包含checksums,

此时checksums放在onDiskChecksum中,

当使用压缩时checksums放在onDiskBytesWithHeader

checksums就是把onDiskBytesWithHeader中的所有字节以bytesPerChecksum个字节为单位求校验和,这个校验和用int(4字节)表示。

注意,在求校验和时,onDiskBytesWithHeader中还没有checksums

默认每个Data Block的大小是64K(头(33字节)不包含在内)(可以通过HColumnDescriptor.setBlocksize设置),

64K只是一个阀值,实际的块大小要比它大(取决于最后存入的KeyValue的大小),

比如上次存入的KeyValue导致块大小变成63K了,但是还没到64K,那么接着存入下一个KeyValue,如果此KeyValue有5K,

那么这个块的大小就变成了63+5=68K了。

=================================================================================

从这开始是重点:

Data Block的核心是一串KeyValue,

KeyValue的格式如下:


  1. 名称    字节数 说明
  2. ——————————————————————–
  3. keyLength   4 表示Key所占的总字节数
  4. valueLength 4 表示Value所占的总字节数
  5. rowKeyLength 2 表示rowKey所占的字节数
  6. rowKey rowKeyLength rowKey
  7. columnFamilyLength 1 表示列族名称所占的字节数
  8. columnFamily columnFamilyLength 列族名称
  9. columnName columnNameLength 列名
  10. timestamp 8 时间戳
  11. type 1 Key类型,比如是新增(Put),还是删除(Delete)
  12. value valueLength 列值
  13. ——————————————————————–
  14. 2.1 

 
 
关于KeyValue更完整更详细的内容请看这里:

HBase HFile与Prefix Compression内部实现全解–KeyValue格式

2. 2 Data Block如何存下面这些记录?


  1. id name owner species sex
  2. ===============================
  3. 1001 Fluffy Harold cat f
  4. 1002 Claws Gwen cat m
  5. 1003 Buffy Harold dog f 

把id作为rowkey,其他列不动,另外HBase至少需要一个列族,假设列族名是”mycf”,这三行记录会产生4*3=12个KeyValue,

存到Data Block会是这样,先按rowkey升序,rowkey相同的按列名升序排,

最后的布局类似这样(为了简化去掉了一些细节):


  1. <rowKey 列族名称 列名 时间戳, 列值>
  2. ====================================================
  3. <1001 mycf name timestamp, Fluffy>
  4. <1001 mycf owner timestamp, Harold>
  5. <1001 mycf sex timestamp, f>
  6. <1001 mycf species timestamp, cat>
  7. <1002 mycf name timestamp, Claws>
  8. <1002 mycf owner timestamp, Gwen>
  9. <1002 mycf sex timestamp, m>
  10. <1002 mycf species timestamp, cat>
  11. <1003 mycf name timestamp, Buffy>
  12. <1003 mycf owner timestamp, Harold>
  13. <1003 mycf sex timestamp, f>
  14. <1003 mycf species timestamp, dog>
  15. ==================================================== 


 
 
与H2的DATA_LEAF相比,


====================================================

/ key:1001 / 1001‘Fluffy’‘Harold’‘cat’‘f’) / key:1002 / 1002‘Claws’‘Gwen’‘cat’‘m’) / key:1003 / 1003‘Buffy’‘Harold’‘dog’‘f’) ====================================================== 

HBase的格式存在大量的冗余(比如rowKey、列族名称、列名、时间戳)之所以要这样做是为了水平扩展、文件合并切分更容易,

因为HBase把列名、列族名称这些元数据和列值合在一起了,所以在分区时只须简单按rowkey切分,

就能把所有数据都转移到另一台机器上,不需要像H2那样要考虑INFORMATION_SCHEMA中的元数据与表记录是否同步的问题。

HBase基于LSM-Tree来存放KeyValue,H2基于类B+Tree的结构,

LSM-Tree只允许一次性添加,不需要考虑结点的删除修改,

数据会先写到内存(MemStore),然后内存满了就flush到硬盘变成一棵小LSM-Tree,

多棵LSM-Tree会定期合并成一棵更大的LSM-Tree,大到一定程度再切分自动扩散到其他机器。

B+Tree对于行、列的添加、删除需要对结点进行调整,数据更新会出现overflow。

另外,观察上面两组数据,H2的方案只是把元数据抽出来放到别处了,然后通过表id把DATA_LEAF和元数据关联,

HBase 0.94可以使用前缀压缩的办法,把重复的东西提取出来,

如果把上面的两组数据分别串成一行,其实差别不大,只是HBase多了很多冗余信息而已,

把冗余信息清除一部份我看不出row-oriented和column-oriented有什么本质区别,至少HBase与H2是有点相似的。

反而差异最大的是:

1) LSM-Tree与B+Tree

2) 是否是schema-free的

(以下内容不重要)

2. 3 leaf索引块的格式:

数据块总个数N(int类型,4字节)

N个”数据块在此索引块中的相对位置”(从0开始,根据每个Entry的大小累加,每个相对位置是int类型,4字节)

N个Entry的总字节数(int类型,4字节)

N个Entry {

数据块在文件中的相对位置(long类型,8字节)

数据块的总长度(包括头) (int类型,4字节)

数据块第一个KeyValue中的Key字节数组

}

2. 4 root索引块的格式:

N个leaf索引块Entry {

leaf索引块在文件中的相对位置(long类型,8字节)

leaf索引块的总长度(包括头) (int类型,4字节)

leaf索引块第一个Entry的Key字节数组

}

2. 5 IntermediateLevel索引块

与leaf索引块类似,只不过它的Entry在第一层IntermediateLevel是leaf索引块Entry,

第二层以后是 IntermediateLevel块的entry。

查找key的顺序

root索引块 ==> IntermediateLevel索引块 ==> leaf索引块 ==> 数据块

本文来源于"阿里中间件团队播客",原文发表时间"  2012-07-31"
相关实践学习
云数据库HBase版使用教程
&nbsp; 相关的阿里云产品:云数据库 HBase 版 面向大数据领域的一站式NoSQL服务,100%兼容开源HBase并深度扩展,支持海量数据下的实时存储、高并发吞吐、轻SQL分析、全文检索、时序时空查询等能力,是风控、推荐、广告、物联网、车联网、Feeds流、数据大屏等场景首选数据库,是为淘宝、支付宝、菜鸟等众多阿里核心业务提供关键支撑的数据库。 了解产品详情:&nbsp;https://cn.aliyun.com/product/hbase &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8月前
|
存储 NoSQL 分布式数据库
Hbase+ES和MongoDB存储大数据的选用
Hbase+ES和MongoDB存储大数据的选用
239 0
|
1天前
|
存储 NoSQL 分布式数据库
分布式NoSQL列存储数据库Hbase(一)Hbase的功能与应用场景、基本设计思想
分布式NoSQL列存储数据库Hbase(一)Hbase的功能与应用场景、基本设计思想
182 0
|
10月前
|
存储 分布式数据库 Hbase
分布式数据库HBase的基本概念和架构之概念面向列(簇)的分布式数据库
在分布式数据库 HBase 中,数据的存储和管理是基于列的分布式存储。
52 0
|
9月前
|
资源调度 Java Linux
Hbase实践将所有info列簇下的name列导入到另一张表中
Hbase实践将所有info列簇下的name列导入到另一张表中
|
10月前
|
存储 分布式计算 NoSQL
分布式数据库HBase的重要机制和原理的存储原理
HBase是一个分布式数据库系统,基于Google的BigTable和Apache Hadoop的HDFS构建。它提供了一个高性能、可扩展的数据库平台,适用于大规模的数据存储和处理。在阿里云开发者社区中,很多开发者都会使用HBase进行数据存储和处理。本文将介绍HBase的存储原理和重要机制。
355 0
|
存储 前端开发 Cloud Native
基于 HBase 快速构架海量订单存储系统|学习笔记
快速学习基于 HBase 快速构架海量订单存储系统
354 0
基于 HBase 快速构架海量订单存储系统|学习笔记
|
存储 搜索推荐 Java
如何基于 HBase 构建图片、视频数据的统一存储检索方案|学习笔记
快速学习如何基于 HBase 构建图片、视频数据的统一存储检索方案
761 0
如何基于 HBase 构建图片、视频数据的统一存储检索方案|学习笔记
|
存储 缓存 NoSQL
HBase与HDFS之间的WAL(HLog)存储机制答疑解惑
HBase与HDFS之间的WAL(HLog)存储机制答疑解惑
|
存储 分布式数据库 Hbase
《实战-如何基于HBase构建图片视频数据的统一存储检索方案》电子版地址
实战-如何基于HBase构建图片视频数据的统一存储检索方案
134 0
《实战-如何基于HBase构建图片视频数据的统一存储检索方案》电子版地址