程序员的自我修养
Home » Apache HBase » HBase表结构设计

HBase表结构设计

3条评论7,110次浏览

概念视图与物理视图

BigTable论文HBase官网上的示例

概念视图

Row Key Time Stamp ColumnFamily contents ColumnFamily anchor
"com.cnn.www" t1 anchor:cnnsi.com = "CNN"
"com.cnn.www" t2 anchor:my.look.ca = "CNN.com"
"com.cnn.www" t3 contents:html = "..."
"com.cnn.www" t4 contents:html = "..."
"com.cnn.www" t5 contents:html = "..."

物理视图

ColumnFamily anchor

Row Key Time Stamp Column Family anchor
"com.cnn.www" t1 anchor:cnnsi.com = "CNN"
"com.cnn.www" t2 anchor:my.look.ca = "CNN.com"

ColumnFamily contents

Row Key Time Stamp Column Family contents
"com.cnn.www" t3 contents:html = "..."
"com.cnn.www" t4 contents:html = "..."
"com.cnn.www" t5 contents:html = "..."
  • 概念视图中空白cell在物理上是不存储的
  • rowkey和column family在实际存储中确实会重复存储
  • 想象一下把传统数据库中每列变成“列名:值”,然后这些列全部存入只有一列的表中

Row Key行键设计

Rowkey可以是任意字符串(最大长度是64KB,实际应用中长度一般为10-100bytes),在HBase内部,rowkey保存为字节数组。存储时,数据按照rowkey的字典序(byte order)排序存储。设计rowkey时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。byte order意味着,对于1-20这样的数据,排列出来的结果为:1,10,11...19,2,20,3,4..9。所以rowkey的长度最好是一致的,长度不够以0或某些字符左补齐。

传统关系型数据库的主键通常有业务主键和逻辑主键之分,关于两者谁更优秀的争论至今未止。而在HBase或者说NoSql中,rowkey的设计通常更像业务主键。rowkey的设计至关重要,若前期rowkey设计不合理,在开发阶段往往会发现想要获取数据进行分析会变得异常困难,在传统数据库向NoSQL数据库转移阶段这个问题会显得尤其明显。

rowkey不要设计成单调递增数据或时间戳数据:在Tom White的《Hadoop: The Definitive Guide》一书中,有一个章节描述了一个值得注意的问题:在一个集群中,一个导入数据的进程一动不动,所有的client都在等待一个region,过了一会后,变成了下一个region。如果使用了单调递增或者时序的key就会造成这样的问题。使用了顺序的key会将本没有顺序的数据变得有顺序,把负载压在一台机器上。所以要尽量避免时间戳或者(e.g. 1, 2, 3)这样的key。

目前我所遇到的业务中,rowkey通常采用K+时间戳(14位)+传统业务主键的形式(12位+),注意,短键对提高访问(scan/get)速度没有帮助,所以不用纠结行键长度过长,当然,无意义的增加行键的长度是不可取的。其中K=集群大小*每台机器的CPU核心数。例如8个tasktracker,每台机器4核心,则rowkey=[00-32随机数]+时间戳(14位)+传统业务主键。这么处理的好处:

  • 建表的时候就可以预先创建多个region,可有效防止热点问题
  • 导入数据时主键前两位随机生成,可有效的将数据分散开来
  • 理想情况下是每个核心数启动N个Map,而每个region又对应着一个Map,所以region若能平均分布在每台机器上能使集群效率最大
  • 按时间处理数据时可以通过RegexStringComparator来过滤数据
  • 当数据量十分大的时候,可以将前面的随机数乘以N,N>=1

当然,rowkey的设计没有最佳实践,符合业务需求的才是最好的。rowkey往往包含了很多信息,这些信息为选择数据而服务。

列族和列的设计

同一个列族在HBase里面会存放在同一个HStore中,所以尽量将IO行为相同的列放在同一个列族中。在HBase的存储文件HFile中,有一个索引用来方便值的随机访问,但是访问一个单元的坐标要是太大的话,会占用很大的内存,这个索引会被用尽。所以要想解决,可以设置一个更大的块大小,当然也可以使用更小的列族名和列名

  • 列族名:尽量短小,最好一个字符
  • 列名:保证易读的前提下尽量短小

现在HBase并不能很好的处理两个或者三个以上的列族,所以尽量让你的列族数量少一些。目前,flush和compaction操作是针对一个Region。所以当一个列族操作大量数据的时候会引发一个flush。那些不相关的列族也有进行flush操作,尽管他们没有操作多少数据。Compaction操作现在是根据一个列族下的全部文件的数量触发的,而不是根据文件大小触发的。当很多的列族在flush和compaction时,会造成很多没用的I/O负载,要想解决这个问题,需要将flush和compaction操作只针对一个列族。

尽量在你的应用中使用一个列族。只有你的所有查询操作只访问一个列族的时候,可以引入第二个和第三个列族。例如,你有两个列族,但你查询的时候总是访问其中的一个,从来不会两个一起访问。

表关联

HBase是否支持表关联,答案是不支持。那么如何才能做到传统关系型数据库中的外键、一对多等功能?目前来说有两种方式:

  • 自己写MR程序进行处理
  • 通过重新设计表结构来实现

自己写MR就不细说了,方法有很多,值得研究的内容也很多,如nested loops vs hash-joins。通过设计表结构应该算是比较合适的方式。先看下图,传统关系型数据库的一对多:hbase01

HBase中则应该如下图所示,物理视图:

RowKey Time Stamp ColumnFamily "info"
电话1 t1 info:入网日期=".."
电话1 t1 info:用户名=".."
电话1 t1 info:性别=".."
电话1 t1 info:...=".."
RowKey Time Stamp ColumnFamily "records"
电话1 t1 r:通话开始时间00001=".."
电话1 t1 r:通话时长00001=".."
电话1 t1 r:通话号码00001=".."
电话1 t1 r:通话开始时间00002=".."
电话1 t1 r:通话时长00002=".."
电话1 t1 r:通话号码00002=".."

逻辑结构:

RowKey Time Stamp ColumnFamily "info" ColumnFamily "r"
info:入网日期 info:用户名 info:性别 info:… r:通话开始时间00001 r:... r:通话开始时间00002 r:...
电话1 t1 .. .. .. .. .. .. .. ..

传统关系型数据库在一对多的"多"端纵向扩展来增加数据,HBase则通过横向扩展来增加"多"端数据。对于上面的例子,有多少的通话记录则扩展多少列。如此结构的表,可以轻松满足一对多的关系,其他的关系也可以类似处理。这样的做的一个可能好处就是,使用QualifierFilter会更加的方便。还有,不用纠结这个场景中列的数量到达99999时怎么办,简单的将后面的列名称变为从9999900001即可,这样你又多了10W行数据。

最后,关于这个问题的处理方式和朋友讨论过几次,目前来说感觉这样处理可能是比较合适的。抛砖引玉,希望能有更佳的处理方式。

参考文档

(转载本站文章请注明作者和出处 程序员的自我修养 – SelfUp.cn ,请勿用于任何商业用途)
分类:Apache HBase
标签:
3条评论
  1. maybe说道:

    你好,请问我现在碰到这么一种场景
    一份全量 用户数据 格式 简单描述为

    userid1_k1_20160616 -> value
    userid1_k2_20160616 -> value
    userid1_k3_20160616 -> value

    userid1 下有很多中不同类型数据 k1 k2 k3
    现在这份数据 存入到了 hbase 中,有一个问题是,假设某一天的数据出了问题,需要重新跑一份
    但是新的数据,并不一定是 k1 k2 k3 都有数据 可能 只有 k1 k2 有数据,k3没有数据
    这样的话,通过bulkload 将数据载入,查询的时候 查 userid1 这样 k3 也会被查出来,一个个删不太现实

    没有想到一个很好的方法, 有时间的话 麻烦帮忙看下有什么好的设计方法。

    • Jeanne说道:

      hoaxthis story was told with different characters , already - several tiems.There never had been an interview with this Australian General in real ;) - although it COULD have happened ^^

发表评论


profile
  • 文章总数:79篇
  • 评论总数:402条
  • 分类总数:31个
  • 标签总数:44个
  • 运行时间:1013天

大家好,欢迎来到selfup.cn。

这不是一个只谈技术的博客,这里记录我成长的点点滴滴,coding、riding and everthing!

最新评论
  • 晴子: 在主节点初始化CM5数据库的时候报错误:Verifying that we can write to /opt/cm-5.9.0/etc/cloudera-scm -server log4j:ERROR Could not...
  • zhangnew: 就4题 :?:
  • linxh: “ 但要是遇到预先并不知道数组的长度而又需要获取正确的(或者称之 为原始的)split长度时,该如何处理呢。。? ” 印象中可以split函数参数传-1?
  • linxh: 班门弄斧一下: ssh host cmd 和直接ssh上后cmd结果不一样是因为ssh直接运行远程命令 是非交互非登录模式与ssh上去得到一个登录交互式Shell二 者加载的环境变量不一样。
  • 匿名: 其实文本分类和数字分类是一样的,只是文本分类需要多一个步骤, 就是计算它的tf-idf值将其转换为double类型
  • yurnom: 可能苹果最近又改变了返回值吧,最近没做测试了。 BadDeviceToken一般测试环境和正式环境弄错的情况 下会出现。
  • Anonymous: :razz: 博主,良心贴啊, 最近也在弄apns推送。 有个问题想请教你一下啊。 你博客中写的 Unregistered 错误,有准确的说明吗, 我看你博客中写的:...
  • 一波清泉: 回复邮箱: 1004161699@qq.com 多谢
  • Anonymous: 17/02/09 01:15:02 WARN Utils: Service ‘SparkUI’ could not bind on port 4040. Attempting port...
  • pacificLee: :twisted:
  • 小码: 为什么没有后面的呢,只有前10个
  • Anonymous: :lol:
  • Anonymous: :razz: 楼主是属于会聊天的。 我想问,sqoop发了几个版本了,应该没这些问题了吧。
  • Anonymous: Config.kafkaConfig.kafkaGroupI d 这个是指自己配置的group id 还是从 import org.apache.kafka.common.config .Config 这个类...
  • Anonymous: ZkUtils.getPartitionsForTopics (zkClient, Config.kafkaConfig.topic) 那个方法是在 spark-streaming_2.10 中 kafka...
  • Anonymous: ZkUtils.getPartitionsForTopics (zkClient, Config.kafkaConfig.topic) 你确定 kafka 里面有这个类 ? 个人在kafka 最新 稳定版...
  • Anonymous: :roll:
  • Anonymous: 很不错,试问有java版的吗?
  • Anonymous: 赞
  • Anonymous: 哈哈 看楼主的吐槽乐死了 where子句是可以写的 同样找不到资料 一点点试出来的 select id from xxxx where ${CONDITIONS} and 1=1 and 2=2 limit 4