写点什么

如何设计高效的 HBase 数据模型

用户头像
Jowin
关注
发布于: 2021 年 05 月 14 日

从学习和使用 HBase 的经历中,整理出对使用者而言,需要了解的 HBase 基础知识,Mark 一下。

1.背景知识

1.1 数据模型

学习 HBase/BigTable 最困难的部分,是理解它的数据模型,换句话说它究竟是咋用的?在 BigTable 论文中明确说明:


A Bigtable is a sparse, distributed, persistent multidimensional sorted map.
复制代码


论文做了进一步解释:


The map is indexed by a row key, column key, and a timestamp; each value in the map is an uninterpreted array of bytes.
复制代码


推荐两篇文章,对此解释的非常清楚:understanding-hbase-and-bigtabIntroduction to HBase Schema Design,下面是一个更形象的示例:


{    // ...    "aaaaa" : {                   // Row Key        "A" : {                   // Column Families            "foo" : {             // Column Qualifiers                15 : "y",         // 15: Timestamp / Version number,  "y": Cell Value                4 : "m"            },            "bar" : {                15 : "d",            }        },        "B" : {            "" : {                6 : "w"                3 : "o"                1 : "w"            }        }    },    // ...}
拆解说明: map : 存储KeyValue数据。 persistent : 数据以文件的形式在HDFS(或S3)/GFS存储。 distributed : HBase和BigTable都建立在分布式文件系统之上,计算/存储分离架构;由Master和一组Storage Server组成,数据分区存储,典型的分布式系统。 sorted : RowKey按字典序排序的。 multidimensional: 如上面的示例看到的,这是一个嵌套Map,或者说一个多维Map。
复制代码


真实的存储是平面文件结构,存储模型是类似下面的结构:


aaaaa, A:foo,15 ---> yaaaaa, A:foo, 4  ---> maaaaa, A:bar, 15 ---> daaaaa, B:,    6  ---> waaaaa, B:,    3  ---> waaaaa, B:,    1  ---> w
Key的逻辑结构是: {RowKey:ColumnFamily:Qualifier:TimeStamp},Key按字母升序排序,TimeStamp按降序排序。
复制代码

1.2 读/写/压缩

HBase/BigTable 存储使用 LSM 方式实现,这个网上文章很多,这里只简单介绍需要了解的主要流程:


(1) 写流程:先写Log文件(WAL),然后写MemTable,当MemTable写满后,把数据落地到文件当中。(2) 读流程:由于MemStore、HFiles中都包含数据,读取操作其实类似一个多路归并排序操作,最近的数据在MemStore中,次新数据在近期生成的HFile中,老数据在更早生成的HFile中,按照这个顺序遍历要查找的key。(3) 压缩流程:当文件越来越多时,就需要进行压缩,回收无效效数据(减少存储占用),减少文件数量(提高读效率)。压缩操作的思路是合并MemStore和HFiles文件,删除无效的key值(被新版本覆盖或删除),生成新的文件,回收旧文件。
复制代码


可以看出,HBase/BigTable 非常适合有大量写操作的应用,顺序读性能也不错,适合批量数据处理(例如 MapReduce)。BigTable 论文提到的其中两个使用场景都符合这个特征:


(1) 存储抓取回来的网页,使用MapReduce进行处理;(2) Google Analytics项目,收集用户点击数据,使用MapReduce定期分析,生成网站访问报告。
复制代码

1.3 存储分区

HBase 支持数据自动分区,分区方式: 水平分割+垂直分割,


  • 水平分割:KeyValue 数据依据 RowKey 划分到不同的 Region,每一个 Region 被分配给一个 Region Server,具备良好的扩展性。当一个 Region 过大时,会被分割成两个 Region。当一个 Region Server 负载过重时,把其中的部分 Region 迁移到其他 Region Server。

  • 垂直分割:在一个 Region 内部,每个 ColumnFamily 的数据是单独存储的,这使他们有更好的访问局部性。


参考http://hbase.apache.org/book.html#trouble.namenode.hbase.objects,HBase 在 HDFS 存储数据的目录结构如下:


/hbase    /data        /<Namespace>                    (Namespaces in the cluster)            /<Table>                    (Tables in the cluster)                /<Region>               (Regions for the table)                    /<ColumnFamily>     (ColumnFamilies for the Region for the table)                        /<StoreFile>    (StoreFiles for the ColumnFamily for the Regions for the table)
复制代码

1.5 KeyValue 存储格式

The KeyValue format inside a byte array is:


  • keylength

  • valuelength

  • key

  • value


在新版本中,支持为 Cell 附加 tags,KeyValue 的格式为: {keylength,valuelength, key, value, tags}


The Key is further decomposed as:


  • rowlength

  • row (i.e., the rowkey)

  • columnfamilylength

  • columnfamily

  • columnqualifier

  • timestamp

  • keytype (e.g., Put, Delete, DeleteColumn, DeleteFamily)


行键和列族/列属性,会编码到每一个行当中,所以行键、列族和列属性应该尽可能短一些。

1.4 访问模型和事务

HBase 主要使用 Put/Delete/Scan 这几个接口访问数据,支持批量读写:


  • Version/TimeStamp:HBase/BigTable 支持一个 Key 的多个 Value 版本(依靠 TimeStamp 区分),查询的时候可以访问最新版本,也可以访问指定时间区间的所有版本。

  • Column:每一个 Column Family 都可以有任意多个 Column,所以必须指定要访问的 Column Qualifiers,才能确认要访问的数据。


HBase 可以保证对同一个 Row 的操作是原子操作,这是因为:


  • Row 只属于一个 Region,只能通过一个 Region Server 进行写操作,不存在写冲突;

  • 通过 WAL,可以保证对一个 Row 的多个操作(可以是多个 ColumnFamily)要么都成功,要么都失败。

  • 一个 Region Server 的多个 Region 共享一组 WAL 日志文件,一个 Batch 操作做为一个 Record 写入 WAL,所以可以保证要么 都成功,要么都失败。


The HDFS directory structure of HBase WAL is..  /hbase    /WALs      /<RegionServer>		(RegionServers)        /<WAL>          (WAL files for the RegionServer)
复制代码


1.5 TTL

可以对表和列族设置 TTL(秒),HBase 会自动删除过期的 Rows;在新近的版本中,HBase 支持对每个 Cell 单独设置 TTL(毫秒)。


(1) 标和列族的TTL保存在Schema中,属于表/列族级别的配置。(2) Cell的TTL是作为tag,编码保存在Cell里面的,因此每个Cell都可以单独设置。
复制代码

1.6 版本数量

HBase 支持多版本,一个 RowKey 可以有多个版本的 Value,使用时间戳区分版本。


  • Maximum Number of Versions。 一般不建议设置成很大的值保(例如几百或更多),除非这些旧版本的数据非常有价值,因为这会使 StoreFile 大小剧增。缺省值是 1。


  • Minimum Number of Versions。这个值和 ttl 参数一起使用,实现类似“保留最后 T 分钟的数据,最多 N 个版本,但至少保留 M 个版本”的需求。缺省值是 0,也就是不启用这个 feature。

2. 模式设计指南

HBase 本质上是一个 kv 数据库,不支持 RDBMS 中常见的索引、事务等特性,它的设计目标是应对高吞吐量、海量数据扩展性的需求。


应用程序应该根据业务需求,来设计高层数据模式。

2.1 梳理数据访问模式

访问模式直接决定我们的设计方案,仔细梳理数据访问模式,列出主要访问场景,在后面设计中时时回看,我们的设计能否满足这些访问场景?还有没有更高的设计方案?

2.2 设计行键

RowKey 是 HBase 表设计中最重要的事情,它决定了应用与 HBase 交互的方式,直接影响数据访问的性能。


(1) 行键,列族,列属性尽可能短,以节省存储空间
(2) 利用行键排序的特性,使用相同key前缀聚合经常一起访问的数据,提高读效率例如,对于网页URL,可以对域名做字符串反转,得到com.demo.xxx/page1.html,使一个域名下的页面在存储上相邻。
(3) 充分发挥分区的并发性能,避免行键设计导致出现热门分区常见的方法: - salt:通过增加不同的前缀,使Key均匀分布在各个region。 - hash:对key值做hash,这可以使key的分布非常均匀,并且可以得到固定长度(例如md5),缺点是不方便做区间遍历。
(4) 尽量使用单行设计,避免多行事务考虑如何在单个API调用中完成访问而不是多个API调用,HBase没有跨行事务,避免在客户端代码中构建这种逻辑。
(5) 对访问最近数据的场景,使用逆序时间戳数据库的一个场景问题是快速查找数据的最新版本,解决这个问题的一个方法是利用Hadoop的key值有序这个特征,把时间戳逆序后append到key值尾部。key格式: [key][reverse_timestamp]`,其中`reverse_timestamp = Long.MAX_VALUE - timestamp
复制代码

2.3 设计列族和列属性

(1) 一个典型的模式每个表有1到3个列族。(2) 把访问模式相似的列放到一个列族中。(3) 使列族名称尽可能短,最好是一个字符。(4) 使用更短的列属性(例如"via", 而不是"myVeryImportantAttribute"这样冗长的列属性)。(5) 列属性也可以用来存储数据,就像Cell一样。(6) 使cell小于10MB;在使用mob时,不要超过50MB;否则,考虑把cell存到HDFS中,在hbase中仅保存一个指针。
复制代码

2.4 规划分区

(1) 使region大小保持在10~50GB(2) 对于包含1或2个列族的表来说,大约50-100个区域是一个很好的数字。(3) 对于RowKey为:`设备ID+时间戳`格式的时序数据,可以允许更多的分区,因为历史数据分区通常是不活动的。(4) 预分区    Hadoop支持自动分区和负载均衡,但如果你非常了解你的数据,对table预先(人工)分区通常是一种最佳实践。    但要小心测试你的数据,务必把key均匀分布在各个region中,避免负载倾斜。    注意:使用Bytes.split (which is the split strategy used when creating regions in           Admin.createTable(byte[] startKey, byte[] endKey,numRegions)分区时,要注意它是按照          Byte范围来分割的(包括了不可见字符),可能会导致有些Region根本不会有key存进来。
复制代码

3.案例解析-OpenTSDB

OpenTSDB 使用 HBase 存储数据,它的两个核心设计思想:

(1) 使用一行存储一个时间区间的所有数据

OpenTSDB 把 1 小时内的事件作为一行存储,所有事件存储在列族 t 中,Key 中的时间戳精确到小时,列名称为以本小时起始的秒数(1 小时 3600 秒,最多也就 3600 个事件),或毫秒数(1 小时可以容纳的事件数就多了)。


在当前时间段过去以后,可以把 1 个小时内的数据压缩到一个列中存储(对照 KeyValue 结构,这能节省太多的冗余数据)。

(2) 对字符串编码,减小 key 的长度

TSDB 把字符串映射成数值,来减小 Key 的长度。指标名称,标签 Key,标签 Value,都映射成一个变长整数(使用映射表:tsdb-uid 存储文本和整数的映射关系),这就使得 Key 值非常短。


RowKey结构:`<指标名称><时间戳><标签1><标签2>`。
注: (1) 时间戳:指明时间点 (2) 指标名称: 这个数据的抽象概括,指明监控内容,如温度,湿气,大小 (3) 标签: 对象,指明监控对象 ,如某个城市,某个CPU,某块区域 (4) 值: 指标数值
复制代码



图 1 OpenTSDB tsdb 表结构

参考:

(1) Bigtable: A Distributed Storage System for Structured Data


(2) Apache HBase Reference Guide


(3) understanding hbase andbigtab


(4) Introduction to HBase Schema Design


(5) OpenTSDB HBase Schema


(6) Hbase 最佳实践系列


发布于: 2021 年 05 月 14 日阅读数: 904
用户头像

Jowin

关注

爱代码,爱生活 2013.08.19 加入

C++/Go Programmer,关注分布式存储,目前从事金融数据开发。

评论

发布
暂无评论
如何设计高效的HBase数据模型