HBase 是一种非关系型数据库,它不要求数据之间有严格的关系,同时它允许在同一列不同行中存储不同类型的数据。
HBase 采用列式存储或者说是面向列的数据库,在表中它由行排序。HBase 是根据列族来存储数据的。列族下面可以有非常多的列,列族在创建表的时候就必须指定。一个表有多个列族,每一个列族可以有任意数量的列。后续列的值连续存储在磁盘上。表中的每个单元格值都具有时间戳。
总之,在一个 HBase 中:
- 表是行的集合;
- 行是列族的集合;
- 列族是列的集合;
- 列是键值对的集合。
传统数据库像是一个有着严格格式的 Excel 表格,而 HBase 更像是一个巨大、稀疏、多维的"键值对(Key-Value)"字典。
概念理解
HBase 的表(Table)由许多行组成,每一行都有一个全局唯一的行键(RowKey) 。HBase 最大的特点之一是数据按 RowKey 的字典顺序自动排序。
比如我们有三个用户,就是三行,行键分别是 user_001, user_002, user_003。这三行数据构成了这张表,然后按照字典顺序排序 user_001, user_002, user_003在底层存储时,user_001 紧挨着 user_002。
表
| 行键 (RowKey) | 列族:BaseInfo (基础信息) | |
|---|---|---|
user_001 |
||
user_002 |
||
user_003 |
具体到某一行 (比如 user_001),它里面包含的并不是直接的列,而是列族。比如user_001 这一行里,包含了 BaseInfo 和 ActionLog 这两个列族,分别代表基本信息和日志
然后一个列族包含多个列 ,同一列族数据在硬盘中连续存储,每一列是一个键值对,并通过时间戳保留多个版本,比如表中的T1,T2
| 行键 (RowKey) | 列族:BaseInfo (基础信息) | 列族:ActionLog (行为日志) |
|---|---|---|
user_001 |
name = "张三" (T1) age = "27" (T3) age = "26" (T2) |
login_time = "2026-03-26" (T1) click_item = "手机" (T2) |
综合来看就是这样:
| 行键 (RowKey) | 列族:BaseInfo (基础信息) | 列族:ActionLog (行为日志) |
|---|---|---|
user_001 |
name = "张三" (T1) age = "27" (T2) age = "26" (T1) |
login_time = "2026-03-26" (T1) click_item = "手机" (T2) |
user_002 |
name = "李四" (T1) email = "lisi@test.com" (T1) |
(空,不占用额外存储空间) |
user_003 |
nickname = "王五" (T1) |
search_word = "HBase教程" (T1) |
你也许注意到了,对于这三个用户,其列族的列可以是完全不同的,这和我们常见的关系型数据库是最大的不同点,比如mysql,一个用户是一行,那么不同用户属性值的集合应当完全一样,一张表的每个列所有用户都有,但是对于列族数据库来说,一张表的不同用户完全可以有不同的属性值。
我们再来拿常见的商品表并结合操作代码来讲解一下Hbase,不同的商品会有不同的属性,比如食品有保质期,手机有颜色型号等,非常适合用列族数据库存储。
商品库
使用**商品库(Product)**的例子,建表时有两个列族:BaseInfo(基础信息)和 Attr(动态属性),商品的行键(RowKey)是 SKU_1001。
在操作数据前,我们需要先建表和列族。在 HBase 控制台里,建表只需要指定表名和列族名即可,不需要定义具体的列。
Ruby
hbase(main):001:0> create 'Product', 'BaseInfo', 'Attr'
0 row(s) in 1.2340 seconds
即创建一个Product表,有BaseInfo和Attr两个列族
增:上架新商品 (Put)
场景: 录入 SKU_1001 的名称、品牌和首发价格。
操作时刻: T1
在 Shell 中,put 命令的语法是:put '表名', 'RowKey', '列族:列名', '值'
Shell 命令:
Ruby
hbase(main):002:0> put 'Product', 'SKU_1001', 'BaseInfo:name', 'iPhone 15 Pro'
hbase(main):003:0> put 'Product', 'SKU_1001', 'BaseInfo:brand', 'Apple'
hbase(main):004:0> put 'Product', 'SKU_1001', 'Attr:price', '7999'
执行后,HBase 底层的逻辑视图:
| 行键 (RowKey) | 列族:BaseInfo (基础信息) | 列族:Attr (动态属性) |
|---|---|---|
SKU_1001 |
name = "iPhone 15 Pro" (T1) brand = "Apple" (T1) |
price = "7999" (T1) |
改:降价与动态增加属性 (Put)
场景: 双十一降价,并且新增"颜色"和"赠品"属性。
操作时刻: T2
HBase 没有 update 命令,修改和新增字段都是继续使用 put 进行追加。
Shell 命令:
Ruby
# 覆盖旧价格(追加新版本)
hbase(main):005:0> put 'Product', 'SKU_1001', 'Attr:price', '7499'
# 动态新增原本不存在的列
hbase(main):006:0> put 'Product', 'SKU_1001', 'Attr:color', 'Titanium Blue'
hbase(main):007:0> put 'Product', 'SKU_1001', 'Attr:gift', 'AirPods'
执行后,HBase 底层的逻辑视图变为了:
| 行键 (RowKey) | 列族:BaseInfo (基础信息) | 列族:Attr (动态属性) |
|---|---|---|
SKU_1001 |
name = "iPhone 15 Pro" (T1) brand = "Apple" (T1) |
price = "7499" (T2) color = "Titanium Blue" (T2) gift = "AirPods" (T2) price = "7999" (T1) |
查:读取商品信息 (Get)
场景: 查看 SKU_1001 这个商品的最新全部信息。
操作时刻: 随时
使用 get 命令可以获取指定 RowKey 的数据。默认情况下,Shell 会打印出该行所有列的最新版本数据。
Shell 命令:
Ruby
hbase(main):008:0> get 'Product', 'SKU_1001'
COLUMN CELL
Attr:color timestamp=1711540000000, value=Titanium Blue
Attr:gift timestamp=1711540000000, value=AirPods
Attr:price timestamp=1711540000000, value=7499
BaseInfo:brand timestamp=1711530000000, value=Apple
BaseInfo:name timestamp=1711530000000, value=iPhone 15 Pro
(注意看控制台的输出,HBase 自动按列名的字典顺序排好了,并且展示了每条数据的实际数字时间戳。这里返回的价格是 7499,旧的 7999 被隐藏了。)
删:活动结束,移除赠品属性 (Delete)
场景: 撤掉"赠品"这个属性。
操作时刻: T3
语法是:delete '表名', 'RowKey', '列族:列名'。(如果想删除整行数据,可以使用 deleteall '表名', 'RowKey')。
Shell 命令:
Ruby
hbase(main):009:0> delete 'Product', 'SKU_1001', 'Attr:gift'
执行 Delete 操作后,HBase 底层的逻辑视图:
| 行键 (RowKey) | 列族:BaseInfo (基础信息) | 列族:Attr (动态属性) |
|---|---|---|
SKU_1001 |
name = "iPhone 15 Pro" (T1) brand = "Apple" (T1) |
gift = [Tombstone 墓碑标记] (T3) price = "7499" (T2) color = "Titanium Blue" (T2) gift = "AirPods" (T2) price = "7999" (T1) |
此时如果你再次执行 get 'Product', 'SKU_1001',你会发现输出的结果中,Attr:gift 这一行彻底消失了。