HBase应用(4.1)模式设计及反规范化

1.宽表

问题1:假设在微博系统中,设计一张表follows用来存储用户相互关注的关系

由于HBase不像关系数据库支持连表查询,所以为了快速查询我们把被关注者的信息同时插入follows表中,但是会导致用户信息更新时除了users表,还要更新follows表,这是一种典型的反规范化处理:

row_key follows列族
jerry 1:Jack 2:tom
jack 1:jerry

当需要读取某位用户关注了哪些用户时只需要使用get就可以完成:

1
get 'follows', 'jerry'

这样设计导致每次在同一行插入数据时都需要指定唯一的列限定符,所以我们可以做一个改进,直接使用用户名作为列限定符:
| row_key | follows列族 |
| —————- | :——————— |
| jerry | jack:1 tom:1 |
| jack | jerry:1 |

因为一个用户可能关注很多用户,导致follows列族中可能会有很多列,所以这种表称为宽表(wide table)

2.高表

问题2:设计一张follows表,能够快速查询jerry是否关注了jack

按照上面的方式设计表,需要获取jerry关注的所有用户,然后遍历查找jack是否在其中。如果行键按照“关注者”+”被关注者”的方式设计,就可以快速查询到这一结果:

row_key follows列族
jerry:jack I‘m Jack:1
jerry:tom I‘m Tom:1
jack:jerry I’m Jerry:1

这种表被称为 高表

高表和宽表的对比如下:

宽表 高表
查询性能 强。查询条件在row_key中,row_key为索引的一部分;高表每行数据较少,BlockCache可以缓存更多行,以行数为单位的吞吐量更高
分片能力 更均衡。HBase按照row_key分片,高表行数更多
元数据开销 低。高表row_key太多,导致region更多,-ROOT-和.META表数据量更大
事务能力 强。HBase只支持单行事务,高表可能出现多行事务的情况
数据压缩比 强。宽表数据量大,更有利于压缩,还可以环境分片不均的问题

3.MD5行键

上面的例子中,直接使用“关注者”+”被关注者”作为行键,其实也可以使用 “md5(关注者)md5(被关注者)”作为行键,这种行键的好处在于:

  • 行键长度统一,可以更好的预测读写性能
  • 不需要分隔符,scan操作的startRow和endRow更好计算
  • 数据分布更均匀,避免产生热点

弊端在于:

  • 数据有序性丢失
  • 行键数据无法还原

4.反规范化设计

假设下表用于存储用户发布的微博,每个用户登陆时都要从该表读取自己及关注用户的最新微博,假如jerry有1000W粉丝,那么jerry微博所在的region会处理大量请求,如何处理因此产生的读热点?
| row_key(用户名:Long.MAX_VALUE - timestamp) | post列族 |
| —————- | :——————— |
| jack:9223370512384341423 | 大家好:1 |
| jack:9223370512384340110 | 你好:1 |
| jerry:9223370512384347110 |哈哈哈:1|

解决这个问题的一个思路:

为每一位用户维护一个微博流,用户发布一篇微博时,将微博按照时间倒序插入到 自己所有粉丝的微博流中,当用户刷微博时只需要查询自己的微博流中的数据,从而避免读热点。

row_key(md5(用户名):Long.MAX_VALUE - timestamp) post列族
md5(jack)Long.MAX_VALUE - timestamp jerry:这是第一条微博
md5(jack)Long.MAX_VALUE - timestamp jack:大家好
md5(jack)Long.MAX_VALUE - timestamp tom:哈哈哈
md5(jerry)Long.MAX_VALUE - timestamp jerry:这是第一条微博