一、引言为什么数据模型是HBase的核心在上一篇文章中我们了解了HBase的基本概念和适用场景。但要想真正用好HBase深入理解其数据模型是必经之路。HBase的数据模型与关系型数据库有着本质的不同——它既不是简单的表格也不是纯粹的键值对而是一种独特的**多维映射Multi-dimensional Map**结构。理解HBase数据模型的关键在于把握两个视角逻辑视角看起来像一张二维表有行有列物理视角本质上是稀疏的多维Map按列族物理隔离存储本文将从这两个视角出发逐一剖析HBase数据模型的每个核心概念。二、HBase逻辑结构看起来像一张表2.1 逻辑结构概览从逻辑上看HBase的数据模型与关系型数据库很相似数据存储在一张表中有行Row有列Column。下面是一个典型的HBase表逻辑视图Row Keypersonal_infooffice_infonamecityphoneteladdressrow_key1张三北京131****010-1111atguigurow_key11李四上海132****010-1111atguigurow_key2王五广州159****010-1111atguigurow_key3赵六深圳187****010-1111atguigurow_key4钱七大连134****010-1111atguigu这张表包含了以下逻辑元素Row Key唯一标识每一行数据按字典序排序Column Family列族personal_info和office_infoColumn列name、city、phone属于personal_info列族tel、address属于office_info列族上图更直观地展示了HBase的逻辑结构表按Row Key排序每个Row包含多个Column Family每个Column Family下包含多个Column。这种结构看起来与关系型数据库的表非常相似但本质却截然不同。2.2 逻辑结构的核心特征特征1Row Key是唯一的排序键HBase表中的所有数据都按照**Row Key的字典序Dictionary Order**进行排序存储。这意味着row_key1row_key11row_key2注意是按位比较不是数值比较数据在物理上按照Row Key的顺序连续存储查询时只能根据Row Key进行检索这是HBase查询的唯一入口重要提示Row Key的设计直接决定了数据的分布和查询性能是HBase表设计的核心。后续文章将专门讲解RowKey设计原则。特征2列族是列的逻辑分组列族Column Family是HBase中最重要的概念之一它是物理存储的基本单位不同列族的数据存储在不同的文件夹中访问控制的基本单位可以对不同列族设置不同的权限配置管理的基本单位压缩、缓存、版本数等属性按列族配置一个表可以有一个或多个列族但通常建议不超过3个。列族过多会带来以下问题Flush和Compaction操作需要处理更多文件增加IO压力内存中的MemStore数量增加增加GC压力数据分布不均匀某些列族数据量大某些很小特征3列是动态的无需预先定义这是HBase与关系型数据库最大的区别之一建表时只需声明列族不需要声明具体的列写入时可以动态指定列名Column Qualifier不同行可以拥有完全不同的列例如第一行可以有name、city、phone三列第二行可以有name、age、email三列——这在HBase中是完全合法的。三、HBase物理存储结构本质是一个多维Map虽然HBase在逻辑上看起来像一张表但从底层物理存储来看HBase更像是一个多维度的Map映射。理解物理存储结构是掌握HBase工作原理的关键。3.1 物理存储的核心概念HBase的物理存储由以下核心概念组成上图展示了HBase的物理存储层次每个RegionServer管理多个Region每个Region包含多个Store每个列族对应一个Store每个Store包含一个MemStore内存缓存和多个StoreFile磁盘文件StoreFile以HFile格式存储在HDFS上3.2 从逻辑到物理的映射关系让我们通过一个具体的例子理解数据是如何从逻辑表映射到物理存储的。逻辑数据Row KeyColumn FamilyColumn QualifierValuerow_key1personal_infoname张三row_key1personal_infocity北京row_key1personal_infophone131****row_key1office_infotel010-1111row_key1office_infoaddressatguigu物理存储K-V形式{row_key1, personal_info:name, timestampt1, typePut} → 张三 {row_key1, personal_info:city, timestampt2, typePut} → 北京 {row_key1, personal_info:phone, timestampt3, typePut} → 131**** {row_key1, office_info:tel, timestampt4, typePut} → 010-1111 {row_key1, office_info:address, timestampt5, typePut} → atguigu可以看到逻辑上的一行数据在物理上被拆分成多条独立的K-V记录。每条记录由以下五元组唯一确定{Row Key, Column Family, Column Qualifier, TimeStamp, Type} → Value3.3 物理存储的关键特征特征1按列族物理隔离不同列族的数据存储在完全不同的物理位置/hbase/data/default/student/ ├── personal_info/ ← personal_info列族的数据 │ ├── storefile1.hfile │ ├── storefile2.hfile │ └── ... └── office_info/ ← office_info列族的数据 ├── storefile1.hfile ├── storefile2.hfile └── ...这种设计的优势查询隔离查询时只需读取相关列族的文件减少IO配置独立不同列族可以设置不同的压缩算法、块大小、版本数权限隔离可以对不同列族设置不同的访问权限特征2所有数据都是字节数组HBase不存储任何数据类型信息所有数据都以**字节数组byte[]**的形式存储// Java API中所有值都需要转换为字节数组PutputnewPut(Bytes.toBytes(row_key1));put.add(Bytes.toBytes(personal_info),// Column Family → byte[]Bytes.toBytes(name),// Column Qualifier → byte[]Bytes.toBytes(张三)// Value → byte[]);这意味着HBase对数据内容一无所知它不知道某个值是字符串、整数还是图片。数据类型需要在应用层自行维护。特征3空列不占用存储空间这是HBase适合稀疏数据的关键设计如果某行没有某个列该列在物理上完全不存储不会占用磁盘空间也不会影响查询性能与关系型数据库的NULL不同NULL通常也需要占用一定的存储空间来标记例如一个用户画像表可能有10000个可能的标签列但大多数用户只有几十个标签。在HBase中未设置的标签列完全不占空间而在MySQL中即使值为NULL也需要为每个列预留空间。四、核心数据模型概念详解4.1 Name Space命名空间定义命名空间类似于关系型数据库的Database概念用于对表进行逻辑分组管理。HBase内置的命名空间命名空间用途hbase存放HBase内置的系统表如hbase:metadefault用户默认使用的命名空间未指定命名空间时默认使用自定义命名空间# 创建命名空间hbase(main):001:0create_namespaceweibo# 在指定命名空间中创建表hbase(main):002:0createweibo:content,info# 查看所有命名空间hbase(main):003:0list_namespace命名空间的作用逻辑隔离不同业务的数据表便于权限管理和配额控制类似MySQL中的Database但HBase的命名空间更轻量4.2 Table表定义HBase中的表是数据的逻辑集合由多个列族组成。与关系型数据库表的区别特性HBase表关系型数据库表Schema定义只需定义列族需要定义所有列及其类型列的动态性列可以动态增加列固定需要ALTER TABLE空值处理空列不存储NULL值需要占位数据分布自动分片Region通常需要手动分区建表示例# 创建表时只需指定列族hbase(main):001:0createstudent,info# 创建多个列族hbase(main):002:0createstu,info1,info24.3 Row Key行键定义Row Key是HBase表中每一行数据的唯一标识是HBase数据检索的唯一入口。Row Key的核心特性特性1唯一性每一行数据必须有唯一的Row Key类似于关系型数据库的主键。特性2字典序排序HBase按照Row Key的字典序Dictionary Order存储数据。字典序的比较规则是逐字节比较而不是数值大小比较10 2 # 因为 1 的ASCII码(49) 2 的ASCII码(50) row_1 row_10 row_2 # 逐字符比较 abc abd # 前两个字符相同第三个 c(99) d(100)这种排序特性对RowKey设计有重要影响后续文章将详细讲解。特性3不可修改Row Key一旦写入不能修改。如果需要修改Row Key只能删除旧行并插入新行。特性4最大长度Row Key的最大长度通常为64KB实际应用中建议控制在几百字节以内过长会影响性能。Row Key设计原则将在第9篇详细讲解散列性避免Row Key集中导致热点问题唯一性确保不重复查询友好支持常见的查询模式长度适中越短越好减少存储和传输开销4.4 Column Family列族定义列族是HBase中列的逻辑分组是物理存储的基本单位。列族的核心特性特性1物理隔离不同列族的数据存储在完全不同的物理位置不同的HFile文件和文件夹。这意味着查询一个列族时不需要读取其他列族的数据不同列族可以独立配置压缩、缓存等属性列族之间的IO互不影响特性2建表时定义不可随意修改列族必须在建表时定义虽然可以通过alter命令添加新的列族但添加列族需要重写所有数据代价很大删除列族会删除该列族的所有数据修改列族属性如版本数需要触发Flush特性3数量建议官方建议一个表的列族数量不超过3个。原因每个列族对应一个MemStore列族过多增加内存压力Flush和Compaction需要处理更多文件数据分布不均匀某些列族数据量大某些很小列族配置示例// Java API中配置列族属性HColumnDescriptorinfonewHColumnDescriptor(Bytes.toBytes(info));info.setBlockCacheEnabled(true);// 启用块缓存info.setBlocksize(2097152);// 块大小2MBinfo.setMaxVersions(3);// 最大版本数3info.setMinVersions(1);// 最小版本数1info.setCompressionType(Algorithm.SNAPPY);// 压缩算法4.5 Column Qualifier列限定符定义列限定符是列族下的具体列名用于标识列族中的某一列。列限定符的核心特性特性1动态定义与列族不同列限定符不需要预先定义。在写入数据时动态指定# 第一次写入时自动创建info:name列hbase(main):003:0putstudent,1001,info:name,Nick# 可以写入info列族下任何不存在的列hbase(main):004:0putstudent,1001,info:age,20hbase(main):005:0putstudent,1001,info:email,nickexample.com特性2不同行可以有不同的列Row Key 1001: info:name, info:age, info:email Row Key 1002: info:name, info:phone ← 没有age和email Row Key 1003: info:name, info:address ← 完全不同的列这在关系型数据库中是不可想象的但在HBase中完全合法。特性3列限定符可以很长虽然不建议但列限定符理论上可以很长。实际应用中列限定符通常较短如name、age等。4.6 TimeStamp时间戳定义TimeStamp用于标识数据的不同版本Version每条数据写入时如果不指定时间戳系统会自动为其加上当前时间。时间戳的核心特性特性1自动赋值如果不显式指定时间戳HBase会自动使用当前系统时间毫秒级# 不显式指定时间戳自动使用当前时间hbase(main):006:0putstudent,1001,info:name,Nick# 显式指定时间戳通常不需要hbase(main):007:0putstudent,1001,info:name,Nick,1234567890特性2版本控制同一个CellRowKey ColumnFamily ColumnQualifier可以保存多个版本的数据通过不同的时间戳区分row_key1, info:name, ts1000 → 张三 ← 旧版本 row_key1, info:name, ts2000 → 李四 ← 新版本 row_key1, info:name, ts3000 → 王五 ← 最新版本特性3版本数限制每个列族可以设置保存的最大版本数默认1个即只保留最新版本# 设置info列族保存3个版本hbase(main):008:0alterstudent,{NAMEinfo,VERSIONS3}# 查询时获取多个版本hbase(main):009:0getstudent,1001,{COLUMNinfo:name,VERSIONS3}特性4版本清理超过最大版本数的旧版本会被自动清理可以通过TTLTime To Live设置数据过期时间Major Compaction会彻底清理过期数据和删除标记4.7 Cell单元格定义Cell是由{RowKey, ColumnFamily:ColumnQualifier, TimeStamp}唯一确定的单元是HBase中存储数据的最小单位。Cell的完整结构Cell { Row Key, Column Family, Column Qualifier, TimeStamp, Type ← Put或Delete } → Value上图展示了Cell的完整结构每个Cell由Row Key、Column Family、Column Qualifier、TimeStamp和Type操作类型共同确定一个Value。Cell的核心特性特性1无类型存储Cell中的数据没有类型全部是字节码形式存储。应用层需要自行处理数据类型的转换// 写入时转换为字节数组put.add(Bytes.toBytes(info),Bytes.toBytes(age),Bytes.toBytes(20));// 读取时转换回字符串StringageBytes.toString(cell.getValueArray());特性2包含操作类型Cell中不仅存储数据值还存储操作类型Put或DeletePut表示插入或更新操作Delete表示删除操作物理删除在Compaction时执行特性3最小存储单位HBase的所有读写操作最终都落实到Cell级别。即使是删除一行数据本质上也是为每个Cell添加Delete标记。五、Region表的水平分区5.1 Region的定义Region是HBase表的水平分区是HBase分布式存储和负载均衡的基本单位。一张表在创建初期只有一个Region随着数据量的增长Region会自动分裂Split将数据分布到多个RegionServer上。5.2 Region的组成每个Region包含StartRow该Region负责的RowKey范围的起始值包含EndRow该Region负责的RowKey范围的结束值不包含Store每个列族对应一个StoreMemStoreStore的内存缓存StoreFileStore的磁盘文件HFile格式上图展示了Region分裂的过程当一个Region的数据量超过阈值时会分裂成两个子Region每个子Region负责一半的RowKey范围。分裂后HMaster可能会将某个子Region迁移到其他RegionServer以实现负载均衡。5.3 Region的元数据Region的元数据存储在hbase:meta表中系统表包含表名和Region名StartRow和EndRow所在的RegionServer地址分裂历史等信息客户端首次访问某张表时会从Zookeeper获取hbase:meta表的位置然后读取该表的Region分布信息并缓存到本地Meta Cache后续访问直接使用缓存信息。六、Store与StoreFile列族的物理实现6.1 Store的定义Store是Region中对应一个列族的物理存储单元。一个Region中有多少个列族就有多少个Store。6.2 Store的内部结构每个Store包含MemStore内存中的写缓存数据先写入这里StoreFile磁盘中的数据文件MemStore Flush后生成上图展示了Store的结构每个Store包含一个MemStore内存缓存和多个StoreFile磁盘文件。MemStore中的数据在达到一定条件后会Flush到HDFS生成新的StoreFileHFile格式。6.3 StoreFileHFileStoreFile是HBase中实际存储数据的物理文件以HFile格式存储在HDFS上。HFile的核心特性特性1有序存储HFile中的数据按照Row Key Column Family Column Qualifier TimeStamp的顺序排序存储。这种有序性使得范围查询非常高效合并操作Compaction可以顺序读写特性2不可修改HFile一旦生成不可修改。更新操作不是修改原文件而是写入新的Put记录带更新的时间戳在Compaction时合并旧文件保留最新版本特性3数据块结构HFile内部采用块Block结构存储默认块大小为64KB可配置每个块包含多条记录块是HBase读缓存BlockCache的基本单位查询时如果命中BlockCache可以直接从内存读取整个块七、数据模型完整视图7.1 从逻辑到物理的完整映射让我们通过一个完整的例子理解数据在HBase中的完整生命周期。逻辑表定义# 创建表包含两个列族hbase(main):001:0createuser,basic,behavior写入数据# 写入用户1001的基本信息hbase(main):002:0putuser,1001,basic:name,张三hbase(main):003:0putuser,1001,basic:age,25hbase(main):004:0putuser,1001,basic:city,北京# 写入用户1001的行为信息hbase(main):005:0putuser,1001,behavior:last_login,2024-01-01hbase(main):006:0putuser,1001,behavior:login_count,100物理存储结构/hbase/data/default/user/ ├── basic/ ← basic列族的Store │ ├── memstore ← 内存中的数据 │ └── storefiles/ │ ├── hfile1 ← Flush生成的HFile │ └── hfile2 └── behavior/ ← behavior列族的Store ├── memstore ← 内存中的数据 └── storefiles/ └── hfile1物理K-V记录{1001, basic:name, ts1700000000000, Put} → 张三 {1001, basic:age, ts1700000001000, Put} → 25 {1001, basic:city, ts1700000002000, Put} → 北京 {1001, behavior:last_login, ts1700000003000, Put} → 2024-01-01 {1001, behavior:login_count, ts1700000004000, Put} → 1007.2 数据模型层次总结层次概念作用数量关系集群HBase Cluster整个HBase实例1个命名空间Name Space逻辑分组多个表Table数据集合多个RegionRegion表的水平分区动态增长StoreStore列族的物理实现每Region每列族1个MemStoreMemStore写缓存每Store1个StoreFileStoreFile/HFile磁盘数据文件每Store多个BlockBlockHFile中的数据块每HFile多个CellCell最小存储单元大量八、数据模型设计最佳实践8.1 列族设计原则原则1列族数量不宜过多建议一个表的列族数量不超过3个。如果业务需要更多分类可以考虑拆分成多个表使用列限定符的前缀来区分如tag:001、tag:002原则2将访问模式相似的列放在同一列族如果某些列经常一起被查询应该放在同一个列族中# 好的设计经常一起查询的列放在同一列族 create user, profile, activity # profile: name, age, gender, city ← 用户画像查询时一起读取 # activity: last_login, login_count, browse_count ← 行为分析时一起读取原则3将大小差异大的列分开存储如果一个列族的数据量很大如图片内容另一个很小如元信息应该分开存储# 不好的设计大列族和小列族混合 create file, meta_and_content ← content可能很大影响meta的查询 # 好的设计分开存储 create file, meta, content # meta: filename, size, type, create_time ← 小数据快速查询 # content: data ← 大数据按需读取8.2 列限定符设计原则原则1列名尽量简短列限定符存储在每条记录中过长的列名会浪费存储空间# 不好的设计列名过长 put user, 1001, basic:personal_name, 张三 ← 浪费空间 # 好的设计列名简短 put user, 1001, basic:name, 张三原则2利用列限定符的动态性HBase的列可以动态增加这是处理稀疏数据的优势。例如用户标签系统# 每个用户有不同的标签 put user_tags, 1001, tags:001, 1 ← 用户1001有标签001 put user_tags, 1001, tags:005, 1 ← 用户1001有标签005 put user_tags, 1002, tags:003, 1 ← 用户1002有标签003 # 不需要预先定义所有标签列8.3 版本数设计原则原则1根据业务需求设置版本数不需要历史版本VERSIONS1默认节省存储需要少量历史VERSIONS3~5需要完整历史VERSIONS较大值但要考虑存储成本原则2配合TTL使用// 设置版本数和TTLHColumnDescriptorinfonewHColumnDescriptor(info);info.setMaxVersions(3);// 最多保留3个版本info.setTimeToLive(86400*30);// 30天后过期九、常见问题与误区Q1HBase的列和关系型数据库的列有什么区别关系型数据库的列表结构的一部分预先定义所有行都有相同的列空值用NULL填充ALTER TABLE添加列代价大HBase的列动态定义写入时指定不同行可以有不同的列空列不存储添加新列零成本Q2为什么HBase的Cell包含时间戳时间戳实现了多版本并发控制MVCC同一位置的数据可以保存多个版本读取时可以指定版本数或时间范围删除不是立即物理删除而是添加Delete标记这在以下场景非常有用需要查看历史数据如审计日志需要回滚到某个时间点的数据并发写入时的冲突解决Q3HBase的Row Key为什么只能有一个HBase的设计哲学是单一索引所有数据按Row Key排序查询只能基于Row Key这种设计保证了极高的写入和范围查询性能如果需要多维度查询可以考虑二级索引通过Phoenix或协处理器实现冗余存储将数据以不同Row Key存储多份外部索引使用Elasticsearch等配合HBaseQ4HBase适合存储JSON/XML这样的半结构化数据吗适合但需要合理设计方案1将整个JSON作为Value存储# 简单但无法单独查询JSON中的字段putuser,1001,info:json,{name:张三,age:25}方案2将JSON字段展开为HBase列# 可以单独查询每个字段putuser,1001,info:name,张三putuser,1001,info:age,25推荐方案2充分利用HBase的列动态性。十、总结10.1 核心概念回顾概念定义关键特性Name Space命名空间逻辑分组类似Database默认有hbase和defaultTable表数据集合只需定义列族列动态扩展Row Key行键唯一标识字典序排序不可修改查询唯一入口Column Family列族物理存储单位建表时定义物理隔离建议≤3个Column Qualifier列限定符动态定义不同行可以不同TimeStamp时间戳自动赋值支持多版本可配置版本数Cell单元格最小存储单位{rowkey, CF:CQ, ts} → value无类型Region表的水平分区自动分裂负载均衡的基本单位Store列族的物理实现包含MemStore和StoreFileStoreFile/HFile磁盘数据文件有序、不可修改、块结构10.2 逻辑 vs 物理视角特点类比逻辑看起来像二维表有行有列关系型数据库的表物理多维MapK-V存储列族隔离Redis的Sorted Set 列分组10.3 设计口诀RowKey要短要散列族要少要隔离列名要短要动态版本按需要配合TTL。如果本文对你有帮助欢迎点赞、收藏、关注专栏有问题请在评论区留言讨论。