为啥要把表设计拿出来独立成章?因为我觉得像我这样搞了很多年Java后端开发的技术人员,在学习HBase的时候,会受到关系型数据库3NF、BCNF的影响。事实上,数据库范式在HBase里完全没用,必须转变思想。因此把这一点单独写出来,供类似情况的技术人员参考。
HBase逻辑视图
这个图看起来像是Excel表格,不同的是,它的一个单元格可以有多个版本的数据,这是HBase的多版本特性,默认版本数是1。实际存储格式是每个单元格一行记录,如下图。
hbase(main):003:0> scan 'test' ROW COLUMN+CELL rowkey1 column=cf:level, timestamp=1608108298860, value=P9 rowkey1 column=cf:name, timestamp=1607677762394, value=guanyu rowkey2 column=cf:salary, timestamp=1607328820620, value=200w rowkey3 column=cf:corp, timestamp=1607330730061, value=Alibaba rowkey4 column=cf:name, timestamp=1607331563986, value=XiaoYaoZi 4 row(s) Took 1.7952 seconds
我们再来看看存放在HDFS里的hfile文件内容。
[hadoop@server01 hadoop]$ hbase hfile -p -f /hbase/data/default/test/bc89689612a0269a2216349bd23133ec/cf/c66c7553a5d6488a9e1e57ca2b0a5577 SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/usr/hadoop-3.3.0/share/hadoop/common/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:/usr/hbase-2.2.6/lib/client-facing-thirdparty/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory] 2020-12-16 18:30:46,116 INFO [main] metrics.MetricRegistries: Loaded MetricRegistries class org.apache.hadoop.hbase.metrics.impl.MetricRegistriesImpl K: rowkey1/cf:level/1608108298860/Put/vlen=2/seqid=27 V: P9 K: rowkey1/cf:name/1607677762394/Put/vlen=6/seqid=14 V: guanyu K: rowkey2/cf:salary/1607328820620/Put/vlen=4/seqid=0 V: 200w K: rowkey3/cf:corp/1607330730061/Put/vlen=7/seqid=0 V: Alibaba K: rowkey4/cf:name/1607331563986/Put/vlen=9/seqid=0 V: XiaoYaoZi Scanned kv count -> 5
这个文件可以很明显地看出,它是一个键值存储系统,键包含rowkey、列族、列名(列标识符)、时间戳、数据类型(Put、Delete)、字符数组长度、seqid。值就是单元格存储的值。
这个键占用了大量的空间,而且不同数据它们的列族列名完全是一样的,太浪费空间了,这就需要用到HBase的压缩,压缩方式请自行查看官网。
seqid是个什么东西?百度了一下,可能是一个时间序列的标识,提示老的HLog是否可以删除。
HBase表设计原则
- 行键根据需求来设计,尽量短,尽量只调用一次API就可以完成需求。
- HBase原生语法不支持表join操作,适当使用冗余来简化查询操作。
- 列名(列标识符)可以存储数据,每一条记录的列名可以完全不同,但是尽量短。
表设计实战
以微博关注为例来做一个小小的表设计,可能与微博实际不符,仅用于说明设计方法。
关注关系如下:
景天关注重楼、龙葵、雪见
飞蓬关注景天、重楼
重楼关注飞蓬、紫萱
龙葵关注景天
雪见关注景天
紫萱关注雪见
这是一个多对多的关系,如果是关系型数据库,至少要两张表来存放。一张表存放人物信息,一张表存放人物关注关系。
时刻要想到,HBase没有join操作,只能用一张表来存放关注和被关注的信息,这肯定会存在数据冗余。不要怕,HBase可以支持十亿级别的列和百万级别的行,冗余不是问题。
我们可以这么设计
行键 | 列族(关注谁) | 列族(被谁关注) |
001_景天 | cf:003=重楼,cf:004=龙葵,cf:005=雪见 | cf:002=飞蓬,cf:004=龙葵,cf:005=雪见 |
002_飞蓬 | cf:001=景天,cf:003=重楼 | cf:003=重楼 |
003_重楼 | cf:002=飞蓬,cf:006=紫萱 | cf:001=景天,cf:002=飞蓬 |
004_龙葵 | cf:001=景天 | cf:001=景天 |
005_雪见 | cf:001=景天 | cf:001=景天,cf:006=紫萱 |
006_紫萱 | cf:005=雪见 | cf:003=重楼 |
是不是惊呆了,这都什么玩意。这种设计可以只用一次API调用就查出每个人关注了谁,每个人被谁关注了,按照需求来合理设计。