Hbase的表设计

时间:2024-03-01 18:21:00

一、应用背景

微博:用户表users、微博表weibos、用户关系表relations,和具体哪个公司的微博没关系。

微博中的用户想关注其他用户的微博,首先要维护一个特定用户的关注列表,例如张三关注了李四和王五。

为了要的得到张三应该看到的所有微博,你需要查找列表{李四、王五},然后读出列表中每个用户的所有微博,这个信息需要保存在hbase中。

二、表模式(Schema)设计应该考虑的问题

  • 这个表应该保存多少个列族
  • 列族使用什么数据
  • 每个列族应该有多少列
  • 列名应该是什么
  • 每个单元(Cell)存储多少时间版本
  • 行健结构是什么?应该包括什么信息?

三、问题建模

问题分析:关系表需要存储一个特定用户关注什么用户的数据。

表的访问模式:

  • 读出全部用户列表。张三关注人的列表
  • 查询某指定用户是否在列表里。如张三是否关注了李四

需要几个列族呢?

   张三的关注对象里的所有用户,都有可能被确认是否存在,从访问模式看无法区分彼此,不能假定一个用户比其他用户的被访问的可能性大,推断被关注用户需要同一个列族。

  原因:同一个列族的数据存储在同一个物理存储(store)里面,这个物理存储被分为多个Hfile,理想情况下合并为一个Hfile。一个列族的所有列在物理上存在一起,使用这种模式可以把不同访问模式的列放在不同的列族,以便隔离他们。即Hbase被称为是面向列族存储的。

    初始表设计为:

           说明:follws:1--->TheRealMT.

 

三、定义访问模式(读模式,写模式)

这张表用来干嘛?

  • 张三关注了谁?
  • 张三关注李四了吗?
  • 谁关注了张三?
  • 李四关注张三了吗?

表创建脚本:

1.创建关系表
create \'relation\',{NAME => \'follows\', VERSIONS => 2}
2.添加数据
put \'relation\',\'zhansan\',\'follows:1\',\'lisi\'
put \'relation\',\'zhansan\',\'follows:2\',\'wangwu\'

回答第一个问题:张三关注了谁?

  @Test
       public void demo2() throws Exception{
           Configuration config = HBaseConfiguration.create();
           config.set("hbase.zookeeper.quorum", "node1,node2,node3");
           
           HTable table = new HTable(config, "relation");
           String rowKey = "zhansan";
           Get get = new Get(Bytes.toBytes(rowKey));
           List<String> fllowed = new ArrayList<String>();
     
           Result rs = table.get(get);
           List<KeyValue> list = rs.list();
           Iterator<KeyValue> it = list.iterator();
           while(it.hasNext()){
               KeyValue kv = it.next();
               System.out.println(Bytes.toString(kv.getValue()));
               fllowed.add(Bytes.toString(kv.getValue()));
          }
           table.close();
       }

回答第二个问题:张三关注李四了谁?

     public static boolean  demo3() throws Exception{
           Configuration config = HBaseConfiguration.create();
           config.set("hbase.zookeeper.quorum", "node1,node2,node3");
           
           HTable table = new HTable(config, "relation");
           String rowKey = "zhansan";
        Get get = new Get(Bytes.toBytes(rowKey));
        //要查询的对象
        String followUser = "lisi";
     
           Result rs = table.get(get);
        List<KeyValue> list = rs.list();
        Iterator<KeyValue> it = list.iterator();
        while(it.hasNext()){
            KeyValue kv = it.next();
            System.out.println(followUser.equals(Bytes.toString(kv.getValue())));
            //followUser
            if(followUser.equals(Bytes.toString(kv.getValue())));
                return true;
        }
        return false;
           
       }

现在我们这样的回答能简单回答第一个和第二个问题,另外另个不确定,但是这两个问题的回答都属于表的读模式。

让我么来看看表的写模式:以下应用场景会引发hbase表的写模式

  • 一个用户关注了某人
  • 一个用户取消关注了某人

当用户新增一个新的关注的时候,需要在用户的关注列表里新增一个对象,如之前的表设计,TheFakeMT新增一个关注用户的时候,需要知道这个

用户是用户列表里的第5个,如果不查询hbase客户端并不知道这个信息,还有不指定列限定符,也没办法要求Hbase在已有的行上增加一个单元。

不清楚要插入的列的位置,会出现数据覆盖。

解决办法:

         在同一行维护一个计数器。如下图所示;

  

优点:count列能有效的让任何用户知道所关注的用户数量,避免遍历整个关注列表。

基于现在的表设计,插入关注用户的步骤如下:

这种表设计的确定:

  • 增加了客户端代码的复杂性,写入需要四步
  • hbase不支持事务,线程不安全

怎么解决????--->去掉count计数器

新的表设计如下:

表设计说明:

  • 列限定符设计为被关注人的用户名(用户id)
  • cell的内容可以随便存储内容,可以存储1
  • hbase把一切数据存储为byte[](字节数组),可以存储任意数量的列
  • 这就是传说中的宽表设计,宽表的设计会便于检索数据
  • 由于用户id的是唯一的,所以不会出现数据覆盖

客户端代码:

public static void  demo4() throws Exception{
           Configuration config = HBaseConfiguration.create();
           config.set("hbase.zookeeper.quorum", "node1,node2,node3");
           
           HTable table = new HTable(config, "relation");
           String rowKey = "zhansan";
           Put put = new Put(Bytes.toBytes(rowKey));
          //要存储的对象
           String followUser = "chaosju";
           put.add(Bytes.toBytes("follows"),
                Bytes.toBytes(followUser), 
                Bytes.toBytes(1));
           table.put(put);
           table.close();
       }
hbase(main):018:0> scan \'relation\'
ROW                                COLUMN+CELL                                                                                       
 zhansan                           column=follows:1, timestamp=1442106287998, value=lisi                                             
 zhansan                           column=follows:2, timestamp=1442106352630, value=wangwu                                           
 zhansan                           column=follows:chaosju, timestamp=1442109774792, value=\x00\x00\x00\x01                           
1 row(s) in 0.1640 seconds

学到的东西:

  • hbase没有跨行事务的概念,避开客户端使用复杂的业务逻辑

四、均衡分布数据和负载

背景分析:基于上述的表设计

  • 一个人可能关注很多人,这个表会特别长,这本身不是问题,但会影响读模式。如TheFakeMT关注了TheRealMT吗?如何使用这个表回答这个问题呢,行健指定TheFakeMT,列限定符指定TheRealMT,一个Get请求即可。

follows表的另一种设计——高表(high table)设计

优点:

  • 短的列族和列限定名,减少网络io和磁盘空间占用

具体的存储数据样式:

回答张三是否关注了李四???

      先进性一次索引查找,先找到第一个以张三为前缀的第一个数据块,然后以张三开头的行健对所有数据行进行最后一次扫描。

优化设计,及高表的优点:

  • MD5(user1ID)MD5(user2ID)作为rowkey,使得数据均匀的分布在region上,避免热点问题
  • rowkey长度统一,便于预测读写性能
  • 去掉分隔符,更容易扫描计算起始和停止键

hbase热点问题???数据倾斜

热点:数据集中分布在一小部分region上。

例如插入时间序列数据,行健开头是时间戳,因为任何写入的数据的时间戳都是大于之前写入的时间戳,数据总是追加在表的尾部,最后的region会成为热点。

基于MD5_rowkey的设计

如果需要查找,用户id,客户把用户id保存为列限定符。  

 表设计总结: