Hbase全称为Hadoop Database,即hbasehadoop数据库,是一个分布式的存储系统。Hbase利用Hadoop的HDFS作为其文件存储系统,利用Hadoop的MapReduce来处理Hbase中的海量数据。利用zookeeper作为其协调工具。 
本篇文章将重点介绍Hbase三个方面的内容:Hbase体系结构(架构)的介绍、Hbase shell的操作、Hbase的Java api的客户端操作。 

一、Hbase的架构介绍 

这里写图片描述 
Hbase的体系结构是一个主从式的结构,主节点Hmaster在整个集群当中只有一个在运行,从节点HRegionServer有很 多个在运行,主节点Hmaster与从节点HRegionServer实际上指的是不同的物理机器,即有一个机器上面跑的进程是Hmaster,很多机器上面跑的进程是HRegionServer,Hmaster没有单点问题,Hbase集群当中可以启动多个Hmaster,但是通过zookeeper的事件处理机制保证整个集群当中只有一个Hmaster在运行。 
既然Hbase是数据库,那么数据库从根本上来说就是存储表table的,但是我们在这里必须注意一下,Hbase并非是传统的关系型数据库(例如: MySQLOracle),而是非关系型数据库,因为Hbase是一个面向列的分布式存储系统。下面我们有必要介绍一下Hbase中表table的数据模型,先给大家呈现一个hbase数据库的表users: 
这里写图片描述 

特别注意:表中的空白单元并不表示有这个单元存在,在传统的数据库中,空白单元表示该单元存在其值为空(null,这是因为传统数据库总是结构化的)。但在Hbase中,画成二维表只是在逻辑上便于理解,其本质完全是非结构化的。 

我在下面举例子的过程中用的也是这张users表。 

上面这个表就是一个典型的hbase table,与传统的关系型数据库具有很大的差别,下面我们详细介绍有关table的相关概念: 

RowKey(行健):table的主键,table中的记录默认按照RowKey升序排序。 
列族(Column Family):即表中的address、info。table在水平方向上有一个或者多个Column Family组成,一个Column Family中可以由任意多个Column(例如address中的province、city、country、town)组成,即列族支持动态扩展,无需预先定义Column的数量以及类型,所有Column均已二进制格式进行存储,用户需要自行进行类型转换。 
TimeStamp(时间戳):每次用户对数据进行操作对应的时间,可以看做是数据的Version number。例如在上面的表中,xiaoming所对应的company有两个数据信息(alibaba、baidu),而这两个单元格信息实际上是对应操作时间的,如下图所示: 
这里写图片描述 
既然Hbase可以将表中的数据进行分布式存储,那么它到底是以怎样的形式进行分布式存储的呢?我们自然而然想到了HDFS这个分布式文件管理系统是将海量数据切分成若干个block块进行存储的,同理Hbase也采取了类似的存储机制, 将一个table切分成若干个region进行存储,下面我们就介绍Region的相关概念: 
当Table随着记录数不断增加而变大后,Table在行的方向上会被切分成多个Region,一个Region由[startkey,endkey) 表示,每个Region会被Master分散到不同的HRegionServer上面进行存储,类似于我的block块会被分散到不同的DataNode节点上面进行存储。下面是Hbase表中的数据与HRegionServer的分布关系,如图所示: 
这里写图片描述 
接下来介绍在Hbase的体系结构中,Hmaster、HRegionServer、Zookeeper集群这三个角色的作用: 
这里写图片描述 
Hmaster节点的作用: 
①不负责存储表数据,负责管理RegionServer的负载均衡(即防止某些RegionServer存储数据量大,有些 
RegionServer存储数据量小),调整RegionServer上面Region的分布 
②管理RegionServer的状态,例如在HRegionServer宕机后,负责失效HRegionServer上Regions的迁移 
③在Region Split后,负责新Region的分配 
HRegionServer节点的作用: 
HRegionServer主要负责响应用户的I/O请求,即负责响应用户向表中的读写操作,是Hbase体系结构中最核心的模块。HRegionServer内部存储了很多的HRegion,就像DataNode节点中存储了很多的Block块一样,从上图Hbase完整的体系结构中我们可以看到,HRegion实际上是由很多个HStore组成的,所谓HStore就是表中的一个Column Family,可以看出每个Column Family其实就是一个集中的存储单元,这恰恰也帮助我们理解了为什么Hbase是NoSql系列的数据库,为什么是面向列的数据库,在Hbase的表设计中,我们最好将具备共同I/O特性的Column放在同一个列族中,这样读写才最高效,为了让大家更好的理解HRegionServer、HRegion、HStore、ColumnFamily四者之间的关系,我结合之前提到的users表画了一个四者关系的示意图: 
这里写图片描述 
简单来说:就是HRegionServer服务器中存储了很多的HRegion,每个HRegion是由很过个HStore组成的,每个ColumnFamily就是一个HSore。 
在此还要简单介绍一下HLog与MemStore这两个角色的作用: 
Hlog:Hlog中存储了用户对表数据的最新的一些操作日志记录。 
MemSore:HRegion会将大量的热数据、访问频次最高的数据存储到MemStore中,这样用户在读写数据的时候不需要从磁盘中进行操作,直接在内存中既可以读取到数据,正因为MemStore这个重要角色的存在,Hbase才能支持随机,高速读取的功能。 
Zookeeper集群的作用: 
①通过zk集群的事件处理机制,可以保证集群中只有一个运行的Hmater 
②Zookeeper集群中记录了-ROOT-表的位置 
在这里顺便介绍一下Hbase中两张特殊的表:-ROOT-表与.META.表 
-ROOT-表:记录了所有.META.表的元数据信息,-ROOT-表只有一个Region 
.META.表:记录了Hbase中所有用户表的HRegion的元数据信息,.META.表可以有多个Region 
③Zookeeper集群实时监控着HRegionServer这些服务器的状态,将HRegionServer的上线和下线信息实时通知给Hmaster节点,使得Hmaster节点可以随时感知各个HRegionServer的健康状态。 
在上面我们依次介绍了HMaster、HRegionServer、zookeeper集群的作用,Client使用Hbase的RPC机制与Hmaster与HRegionServer进行通信,对于管理类操作,Client与Hmaster进行RPC进行通信,对于数据(表)的读写类操作,Client与HRegionServer进行通信。注意:在用户对数据表的读写过程中,与Hmaster是没有任何关系的,Hmaster在这一点上不同于我们的NameNode节点,可以看出由于zookeeper集群的存在,Hmaster节点的作用被大大弱化了。 
在Hbase的架构介绍中在介绍最后一点:Hbase的寻址机制,即Hbase在海量的表数据中,是如何找到用户所需要的表数据的呢?——Hbase是通过索引的机制解决了这个问题。 
这里写图片描述 
Client访问用户数据之前需要首先访问zookeeper集群,通过zookeeper集群首先确定-ROOT-表在的位置,然后在通过访问-ROOT- 表确定相应.META.表的位置,最后根据.META.中存储的相应元数据信息找到用户数据的位置去访问。通过 这种索引机制解决了复杂了寻址问题。 

二、Hbase中shell的相关操作 

前面讲的都是Hbase的理论知识,对于Hbase的操作分为两种方式:shell与 java api的方式,既然是对数据库的操作,无非就是增删改查四中操作,下面我列出里几个常用的Hbase Shell操作,如下: 
这里写图片描述 
以之前的users表为例,我现在用shell命令进行相应操作: 
①创建users表 
create ‘users’, ‘address’,’info’ 
②向表中用put进行插入操作 
put ‘users’,’xiaoming’,’address:province’,’zhejiang’ 
put ‘users’,’xiaoming’,’address:city’,’hangzhou’ 
put ‘users’,’xiaoming’,’address:country’,’china’ 
put ‘users’,’xiaoming’,’info:age’,’24’ 
put ‘users’,’xiaoming’,’info:age’,’25’ 
put ‘users’,’xiaoming’,’info:birthday’,’1987/06/17’ 
put ‘users’,’xiaoming’,’info:company’,’alibaba’ 
put ‘users’,’xiaoming’,’info:company’,’baidu’ 
……………… 
③通过get命令获取表中的信息,例如: 
1、获取xiaoming的country单元格信息 
get ‘users’,’xiaoming’,’address:country’ 
2、获取小明两个版本的年龄信息 
get ‘users’,’xiaoming’,{COLUMN=>’info:age’,VERSIONS=>2} 
3、获取zhangyifei全部的信息 
get ‘users’,’zhangyifei’ 
4、获取zhangyifei的address列族的全部信息 
get ‘users’,’zhangyifei’,’address’ 
5、遍历表中的全部信息 
scan ‘users’ 
④用shell命令删除users表 
disable ‘users’ 
drop ‘users’ 
综上:Hbase的shell命令操作整体来说还是非常简单的,获取信息的方法主要是按照下面的主线进行: 
tablename———》rowkey———》Column Family:Colum———->Version信息——》获取信息! 
这里写图片描述 
这里写图片描述 
注:在 Java api操作中,对遍历一条记录或者遍历所有记录所用的思想就是:key为{行健、类族、列名、版本号},而value为{具体数值},即一行记录本质上为包含很多键值对。 

三、Hbase中Java api的相关操作 

Hbase中,可以通过shell命令进行增删改查四种操作,但是shell命令用起来比较麻烦,比如说用put一次只能查询一个单元格的信息等等。因此我们在日常工作中总是用Java api的方式对Hbase进行相应操作,在这里还是以上面的users举例,下面是完整的代码操作: 
这里写图片描述

package IT01;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.KeyValue;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;

public class HbaseJavaapi
{
       public static String tablename = "users";
       public static void main(String[]  args) throws Exception
       {
             Configuration conf = HBaseConfiguration.create();
             /**下面这行代码必须有,指定Hbase所用的zookeeper集群信息****/
             /*Configuration conf = new Configuration();这样写的话不会自动读取hbase-site.xml文件,所以别用这个*/
             conf.set("hbase.zookeeper.quorum","hadoop80");

             /*创建表-----用的Java api是HbaseAdmin**/
            @SuppressWarnings("resource")
            HBaseAdmin hBaseAdmin = new HBaseAdmin(conf);
            if(hBaseAdmin.tableExists(tablename))
            {
                hBaseAdmin.disableTable(tablename);
                hBaseAdmin.deleteTable(tablename);
            }
            HTableDescriptor desc = new HTableDescriptor(tablename);//指定创建表的名字users
            HColumnDescriptor family1 = new HColumnDescriptor("address");
            desc.addFamily(family1 );
            HColumnDescriptor family2 = new HColumnDescriptor("info");//指定列族的名称
            desc.addFamily(family2 );
            hBaseAdmin.createTable(desc);

            /*****插入记录------用的Java api是HTable****/
            @SuppressWarnings("resource")
            HTable hTable = new HTable(conf,tablename);  //一定要注意:HTable这个客户端只有在表已经存在的时候才有意义!!!!不然抛出异常!
            Put put1 = new Put("xiaoming".getBytes());//插入记录时要指定行健
            put1.add("address".getBytes(), "province".getBytes(), "zhejiang".getBytes());
            put1.add("address".getBytes(), "city".getBytes(), "hangzhou".getBytes());
            put1.add("address".getBytes(), "country".getBytes(), "china".getBytes());
            put1.add("info".getBytes(),"age".getBytes(),"24".getBytes());
            put1.add("info".getBytes(),"age".getBytes(),"25".getBytes());
            put1.add("info".getBytes(),"birthday".getBytes(),"1987/06/17".getBytes());
            put1.add("info".getBytes(),"company".getBytes(),"baidu".getBytes());
            put1.add("info".getBytes(),"company".getBytes(),"alibaba".getBytes());
            hTable.put(put1 );
            Put put2 = new Put("zhangyifei".getBytes());//插入记录时要指定行健
            put2.add("address".getBytes(), "province".getBytes(), "guangdong".getBytes());
            put2.add("address".getBytes(), "city".getBytes(), "shenzhen".getBytes());
            put2.add("address".getBytes(), "city".getBytes(), "nansha".getBytes());
            put2.add("address".getBytes(), "country".getBytes(), "china".getBytes());
            put2.add("address".getBytes(), "town".getBytes(), "xianqiao".getBytes());
            put2.add("info".getBytes(),"age".getBytes(),"45".getBytes());
            put2.add("info".getBytes(),"birthday".getBytes(),"1987/02/12".getBytes());
            put2.add("info".getBytes(),"company".getBytes(),"tengxun".getBytes());
            put2.add("address".getBytes(), "favorite".getBytes(), "movie".getBytes());
            hTable.put(put2 );


            /****查询记录--------------用的Java api是Htable***/
            /**1------>获取小明的country单元格信息****/
            Get get = new Get("xiaoming".getBytes());
            Result result = hTable.get(get);
            byte[] value = result.getValue("address".getBytes(), "country".getBytes());
            System.out.println(new String(value));

            System.out.println("**************************");

            /**2------>获取zhangyifei的一行的记录****/
            Get get2 = new Get("zhangyifei".getBytes());
            Result result2 = hTable.get(get2);
            for(KeyValue key  : result2.list())//一行的记录当中包含很多的键值对信息,依次遍历即可
            {
                 System.out.println(key.toString());
                 System.out.println(new String(key.getValue()));        
            }

            System.out.println("**************************");

            /**3------>遍历所有的记录****/
            Scan scan = new Scan();
            ResultScanner scanner = hTable.getScanner(scan);//scanner中包含很多条行记录
            for (Result result3 : scanner)
            {
                for(KeyValue key  : result3.list())//一行的记录当中包含很多的键值对信息,依次遍历即可
                {
                     System.out.println(key.toString());
                     System.out.println(new String(key.getValue()));        
                }
            }

            System.out.println("**************************");

            /**4------>仅仅获取每行的city信息****/
            Scan scan1 =new Scan();
            ResultScanner scanner1 = hTable.getScanner(scan1 );
            for (Result result4 : scanner1)
            {
                  System.out.println(new String(result4.getRow()));
                  System.out.println(new String(result4.getValue("address".getBytes(), "city".getBytes())));
            }
       }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110

代码的运行结果如下: 
这里写图片描述 
这里写图片描述 
对于Hbase的Java api操作就简单的介绍到这里! 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐