Hbase入门

大纲

  1. 了解什么是Hbase

  2. 了解列式数据库与行式数据库的区别

  3. 了解hbase的架构

  4. 掌握Hbase的部署和安装

  5. 掌握Hbase的CRUD操作

  6. 实现京东的“规格和包装”功能案例

  7. Hbase高级部分

 

1、了解Hbase

1.1、什么是Hbase

 

官网:https://hbase.apache.org/

HBase是一个使用Java语言实现的,构建于Hadoop分布式文件系统(HDFS)上的分布式数据库

Hbase是参考谷歌的BigTable的论文开发实现的,Hadoop 生态系统引入了Bigtable的大部分功能。

Hadoop生态圈:

 

1.2、Hbase的特点

  • 海量存储

    • Hbase单表可以有百亿行,百万列,相对计较传统关系型数据库而言,存储能力非常强悍。

  • 列式存储

    • 创建表时,无需指定具体的列,根据数据的插入动态插入。在关系型数据库创建表时必须先定义好列。

    • 可以针对列进行权限控制和读取。

  • 多版本

    • 可以为数据添加版本信息,如用户信息的logo变更历史。

  • 稀疏性

    • 为空的列不占用实际存储空间。

    • 传统数据库,为空的列依然要占用存储空间。

  • 高扩展、高可用性

    • 底层基于HDFS,高可用和扩展性得到的了保障。

1.3、列式存储和行式存储

 

有上图可以看出:

  • 行式存储数据库中,表结构是固定的,每行的每一列都站位,无论有无数据。

    • 读取数据时,需要将所有列的数据读取到内存中进行处理,再返回结果。

  • 列式存储数据库中,列的机构是不固定的,如果行的数据某一列没有数据,那么将不占用空间。

    • 读取数据时,可以直接定位到所需要的列,进行返回数据。

 

行式、列式存储底层结构的区别:

 

从上图可以看到,在行式存储下,一张表的数据都是放在一起的,但列式存储下都被分开保存了。

1.4、Hbase中表结构模型

 

  • 表(table):用于存储管理数据,具有稀疏的、面向列的特点。HBase中的每一张表,就是所谓的大表(Bigtable)。

  • 行键(RowKey):类似于MySQL中的主键,HBase根据行键来快速检索数据,一个行键对应一条记录。与MySQL主键不同的是,HBase的行键是天然固有的,每一行数据都存在行键。

  • 列族(簇)(ColumnFamily):是列的集合。列族在表定义时需要指定,而列在插入数据时动态指定。列中的数据都是以二进制形式存在,没有数据类型。在物理存储结构上,每个表中的每个列族单独以一个文件存储(参见图1.2)。一个表可以有多个列族。

  • 时间戳(TimeStamp):是列的一个属性,是一个64位整数。由行键和列确定的单元格,可以存储多个数据,每个数据含有时间戳属性,数据具有版本特性。可根据版本(VERSIONS)或时间戳来指定查询历史版本数据,如果都不指定,则默认返回最新版本的数据。

 

举例,将传统表转成Hbase的存储:

 

转换后的结果:

 

其中:

  • 将原有数据列,拆分成2个列族,分别是user_info和login_info

  • 1001用户有三个版本数据,从数据上可以看出,该用户设置了3个地址

2、Hbase系统架构

全局架构:

 

有此可以看出,Hbase需要依赖于ZooKeeper和HDFS。

  • Zookeeper

    • 保证任何时候,集群中只有一个running master,避免单点问题;

    • 存贮所有Region的寻址入口,包括-ROOT-表地址、HMaster地址;

    • 实时监控Region Server的状态,将Region server的上线和下线信息,实时通知给Master;

    • 存储Hbase的schema,包括有哪些table,每个table有哪些column family。

  • Master

    • 可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master运行。

  • RegionServer

    • HBase中最核心的模块,主要负责响应用户I/O请求,向HDFS文件系统中读写数据。

      • 维护Master分配给它的region,处理对这些region的IO请求;

      • 负责切分在运行过程中变得过大的region。

  • HDFS

    • 负责存储数据。

3、Hbase的部署安装

Hbase的安装有2种方式,一种是单机部署,一种是集群部署。

3.1、下载

https://hbase.apache.org/downloads.html

 

3.2、单机部署

tar -xvf hbase-2.1.0-bin.tar.gz -C /export/servers/
cd /export/servers/hbase-2.1.0
rm -rf docs

#配置环境变量
vim /etc/profile
export HBASE_HOME=/export/servers/hbase-2.1.0
export PATH=${HBASE_HOME}/bin:$PATH

修改配置文件:


cd /export/servers/hbase-2.1.0/conf/
vim hbase-site.xml

---- 输入如下内容
<configuration>
<property>
  <name>hbase.rootdir</name>
    <!--采用本地文件系统存储-->
  <value>file:///export/data/hbase</value>
</property>
<property>
  <name>hbase.zookeeper.property.dataDir</name>
  <!--hbase快照存储的路径-->
  <value>/export/data/zookeeper/</value>
</property>
<property>
  <name>hbase.unsafe.stream.capability.enforce</name>
      <!--使用本地文件系统设置为false,使用hdfs设置为true-->
  <value>false</value>
</property>
</configuration>

启动:


start-hbase.sh
[root@node01 zookeeper]# jps
3878 HMaster
4542 Jps

通过hbase shell命令检查是否启动成功:


[root@node01 zookeeper]# hbase shell
……                    
hbase(main):001:0>
hbase(main):002:0*
hbase(main):003:0* list
TABLE                                                                                                                                        
0 row(s)
Took 0.6230 seconds                                                                                                                          
=> []
hbase(main):004:0>

看到list命令正常执行,说明以及启动成功了。

3.3、Hbase的web管理界面

Hbase启动成功后通过访问http://node01:16010/即可查看web管理界面。

cdh hbase 端口是 60010

 

可以查看RegionServer、服务指标、块的缓存、系统参数信息等。

3.4、集群部署

集群部署需要ZooKeeper和HDFS的支持,所以需要先启动这2个服务。

将Hbase部署到node01、node02、node03节点。


startzk.sh
start-dfs.sh

[root@node01 zookeeper]# jps
5424 DataNode
5638 Jps
5287 NameNode
5146 QuorumPeerMain

修改Hbase的配置文件:

第一步,修改hbase-env.sh


vim hbase-env.sh
export JAVA_HOME=/export/servers/jdk1.8.0_141
#自己不维护ZooKeeper,需要外部配置
export HBASE_MANAGES_ZK=false

第二步,修改hbase-site.xml


<configuration>
 <property>
   <name>hbase.rootdir</name>
   <value>hdfs://node01:8020/hbase</value>
 </property>
<property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
 </property>
 <property>
   <name>hbase.zookeeper.property.dataDir</name>
   <value>/export/data/hbase/zookeeper/</value>
 </property>
 <property>
   <name>hbase.zookeeper.quorum</name>
   <value>node01,node02,node03</value>
 </property>
 <property>
   <name>hbase.zookeeper.property.clientPort</name>
   <value>2181</value>
 </property>
 <property>
   <name>hbase.unsafe.stream.capability.enforce</name>
       <!--使用本地文件系统设置为false,使用hdfs设置为true-->
   <value>true</value>
 </property>
</configuration>

第三步,修改regionservers


node01
node02
node03

第四步,分发到node02、node03


cd /export/servers
scp -r hbase-2.1.0 node02:/export/servers/
scp -r hbase-2.1.0 node03:/export/servers/

scp /etc/profile node02:/etc/
scp /etc/profile node03:/etc/

#分别到node02、node03执行
source /etc/profile

第五步,启动:

start-hbase.sh


[root@node01 servers]# start-hbase.sh
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/export/servers/hadoop-2.7.4/share/hadoop/common/lib/slf4j-log4j12-1.7.10.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/export/servers/hbase-2.1.0/lib/client-facing-thirdparty/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
master running as process 5810. Stop it first.
node03: running regionserver, logging to /export/servers/hbase-2.1.0/bin/../logs/hbase-root-regionserver-node03.out
node02: running regionserver, logging to /export/servers/hbase-2.1.0/bin/../logs/hbase-root-regionserver-node02.out
node01: regionserver running as process 5924. Stop it first.

测试:

 

集群启动成功。

4、Hbase的CRUD操作

通过hbase shell命令进行命令行模式进行操作。

 

4.1、创建表


#指定表名,列族名
create 'user' , 'user_info', 'login_info'
list
describe 'user'

 

create命令用法如下:


hbase(main):023:0> help "create"
Creates a table. Pass a table name, and a set of column family
specifications (at least one), and, optionally, table configuration.
Column specification can be a simple string (name), or a dictionary
(dictionaries are described below in main help output), necessarily
including NAME attribute.
Examples:

Create a table with namespace=ns1 and table qualifier=t1
hbase> create 'ns1:t1', {NAME => 'f1', VERSIONS => 5}

Create a table with namespace=default and table qualifier=t1
hbase> create 't1', {NAME => 'f1'}, {NAME => 'f2'}, {NAME => 'f3'}
hbase> # The above in shorthand would be the following:
hbase> create 't1', 'f1', 'f2', 'f3'
hbase> create 't1', {NAME => 'f1', VERSIONS => 1, TTL => 2592000, BLOCKCACHE => true}
hbase> create 't1', {NAME => 'f1', CONFIGURATION => {'hbase.hstore.blockingStoreFiles' => '10'}}
hbase> create 't1', {NAME => 'f1', IS_MOB => true, MOB_THRESHOLD => 1000000, MOB_COMPACT_PARTITION_POLICY => 'weekly'}

Table configuration options can be put at the end.
Examples:

hbase> create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40']
hbase> create 't1', 'f1', SPLITS => ['10', '20', '30', '40']
hbase> create 't1', 'f1', SPLITS_FILE => 'splits.txt', OWNER => 'johndoe'
hbase> create 't1', {NAME => 'f1', VERSIONS => 5}, METADATA => { 'mykey' => 'myvalue' }
hbase> # Optionally pre-split the table into NUMREGIONS, using
hbase> # SPLITALGO ("HexStringSplit", "UniformSplit" or classname)
hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
hbase> create 't1', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit', REGION_REPLICATION => 2, CONFIGURATION => {'hbase.hregion.scan.loadColumnFamiliesOnDemand' => 'true'}}
hbase> create 't1', {NAME => 'f1', DFS_REPLICATION => 1}

You can also keep around a reference to the created table:

hbase> t1 = create 't1', 'f1'

Which gives you a reference to the table named 't1', on which you can then
call methods.

4.2、插入数据


put 'user', '1001', 'user_info:name','张三'
put 'user', '1001', 'user_info:address', '上海'
put 'user', '1001', 'login_info:user_name', 'zhangsan'
put 'user', '1001', 'login_info:password', '123456'

put 'user', '1002', 'user_info:name','李四'
put 'user', '1002', 'user_info:address', '北京'
put 'user', '1002', 'login_info:user_name', 'lisi'
put 'user', '1002', 'login_info:password', '123456'

4.4、查询数据

Hbase只支持2种查询数据,单行查询,全表查询。


get 'user', '1001'
#查询全部数据
scan 'user'
#查询一条数据
scan 'user', {LIMIT => 1}

get查询:


hbase> t.get 'r1'
hbase> t.get 'r1', {TIMERANGE => [ts1, ts2]}
hbase> t.get 'r1', {COLUMN => 'c1'}
hbase> t.get 'r1', {COLUMN => ['c1', 'c2', 'c3']}
hbase> t.get 'r1', {COLUMN => 'c1', TIMESTAMP => ts1}
hbase> t.get 'r1', {COLUMN => 'c1', TIMERANGE => [ts1, ts2], VERSIONS => 4}
hbase> t.get 'r1', {COLUMN => 'c1', TIMESTAMP => ts1, VERSIONS => 4}
hbase> t.get 'r1', {FILTER => "ValueFilter(=, 'binary:abc')"}
hbase> t.get 'r1', 'c1'
hbase> t.get 'r1', 'c1', 'c2'
hbase> t.get 'r1', ['c1', 'c2']
hbase> t.get 'r1', {CONSISTENCY => 'TIMELINE'}
hbase> t.get 'r1', {CONSISTENCY => 'TIMELINE', REGION_REPLICA_ID => 1}

scan查询:


hbase> scan 'hbase:meta'
hbase> scan 'hbase:meta', {COLUMNS => 'info:regioninfo'}
hbase> scan 'ns1:t1', {COLUMNS => ['c1', 'c2'], LIMIT => 10, STARTROW => 'xyz'}
hbase> scan 't1', {COLUMNS => ['c1', 'c2'], LIMIT => 10, STARTROW => 'xyz'}
hbase> scan 't1', {COLUMNS => 'c1', TIMERANGE => [1303668804000, 1303668904000]}
hbase> scan 't1', {REVERSED => true}
hbase> scan 't1', {ALL_METRICS => true}
hbase> scan 't1', {METRICS => ['RPC_RETRIES', 'ROWS_FILTERED']}
hbase> scan 't1', {ROWPREFIXFILTER => 'row2', FILTER => "
  (QualifierFilter (>=, 'binary:xyz')) AND (TimestampsFilter ( 123, 456))"}
hbase> scan 't1', {FILTER =>
  org.apache.hadoop.hbase.filter.ColumnPaginationFilter.new(1, 0)}
hbase> scan 't1', {CONSISTENCY => 'TIMELINE'}
For setting the Operation Attributes
hbase> scan 't1', { COLUMNS => ['c1', 'c2'], ATTRIBUTES => {'mykey' => 'myvalue'}}
hbase> scan 't1', { COLUMNS => ['c1', 'c2'], AUTHORIZATIONS => ['PRIVATE','SECRET']}
For experts, there is an additional option -- CACHE_BLOCKS -- which
switches block caching for the scanner on (true) or off (false). By
default it is enabled. Examples:

hbase> scan 't1', {COLUMNS => ['c1', 'c2'], CACHE_BLOCKS => false}

4.5、删除数据


#删除一行中的一列数据
delete 'user','1002', 'user_info:name'
#删除一行数据
deleteall 'user','1002'
#清空表
truncate 'user'

4.6、修改数据


#修改用1001的密码为888888,直接put覆盖即可
put 'user', '1001', 'login_info:password', '888888'

#删除列族
alter 'user' , {NAME=>'user_info', METHOD => 'delete'}

#增加列族
alter 'user', 'user_info'
alter 'user', {NAME => 'user_info_2' , VERSIONS => 5}

4.7、删除表


#删除表之前先要禁用表,再删除
disable 'user'
drop 'user'

4.8、多版本


#设置user_info的版本为3,login_info的版本为5
create 'user' , {NAME => 'user_info', VERSIONS => 3 }, {NAME => 'login_info', VERSIONS => 5 }

put 'user','1001', 'user_info:name', 'zhangsan'
put 'user','1001', 'user_info:name', 'zhangsan1'

#查询最新的数据
get 'user', '1001'
#查询3个版本数据
get 'user', '1001', {COLUMN=>'user_info:name',VERSIONS=>3}

 

5、Hbase Java Api

Hbase不仅可以通过shell命令行的方式执行,也可以通过JavaAPI方式进行操作。

5.1、导入依赖


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
   <parent>
       <artifactId>bigdata</artifactId>
       <groupId>cn.bigdata</groupId>
       <version>1.0.0-SNAPSHOT</version>
   </parent>
   <modelVersion>4.0.0</modelVersion>

   <artifactId>bigdata-hbase</artifactId>

   <dependencies>
       <dependency>
           <groupId>org.apache.hbase</groupId>
           <artifactId>hbase-client</artifactId>
           <version>2.1.0</version>
       </dependency>
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <version>4.12</version>
       </dependency>
   </dependencies>

   <build>
       <plugins>
           <plugin>
               <groupId>org.apache.maven.plugins</groupId>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>3.7.0</version>
               <configuration>
                   <source>1.8</source>
                   <target>1.8</target>
               </configuration>
           </plugin>
       </plugins>
   </build>
</project>

5.2、创建表



import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;

public class TestHbase {

   private Connection connection;

   @Before
   public void init() throws IOException {
       Configuration configuration = new Configuration();
       // 配置ZooKeeper信息
       configuration.set("hbase.zookeeper.quorum", "node01:2181");
       // 创建连接
       connection = ConnectionFactory.createConnection(configuration);
  }

   @Test
   public void testCreateTable() throws IOException {
       // 从连接中获得一个Admin对象
       Admin admin = connection.getAdmin();
       TableDescriptorBuilder tableDescriptorBuilder =
               TableDescriptorBuilder.newBuilder(TableName.valueOf("tb_user"));

       // 定义user_info的列族
       ColumnFamilyDescriptorBuilder userInfo = ColumnFamilyDescriptorBuilder.
               newBuilder(Bytes.toBytes("user_info"));
       userInfo.setMaxVersions(3); //设置版本信息
       tableDescriptorBuilder.setColumnFamily(userInfo.build());

       // 定义user_info的列族
       ColumnFamilyDescriptorBuilder loginInfo = ColumnFamilyDescriptorBuilder.
               newBuilder(Bytes.toBytes("login_info"));
       tableDescriptorBuilder.setColumnFamily(loginInfo.build());

       admin.createTable(tableDescriptorBuilder.build());
       System.out.println("创建表成功!");
  }

}

5.3、其它操作


import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.CompareOperator;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.*;
import org.apache.hadoop.hbase.util.Bytes;
import org.junit.Before;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

public class TestHbase {

   private Connection connection;

   @Before
   public void init() throws IOException {
       Configuration configuration = new Configuration();
       // 配置ZooKeeper信息
       configuration.set("hbase.zookeeper.quorum", "node01:2181");
       // 创建连接
       connection = ConnectionFactory.createConnection(configuration);
  }

   @Test
   public void testCreateTable() throws IOException {
       // 从连接中获得一个Admin对象
       Admin admin = connection.getAdmin();
       TableDescriptorBuilder tableDescriptorBuilder =
               TableDescriptorBuilder.newBuilder(TableName.valueOf("tb_user"));

       // 定义user_info的列族
       ColumnFamilyDescriptorBuilder userInfo = ColumnFamilyDescriptorBuilder.
               newBuilder(Bytes.toBytes("user_info"));
       userInfo.setMaxVersions(3); //设置版本信息
       tableDescriptorBuilder.setColumnFamily(userInfo.build());

       // 定义user_info的列族
       ColumnFamilyDescriptorBuilder loginInfo = ColumnFamilyDescriptorBuilder.
               newBuilder(Bytes.toBytes("login_info"));
       tableDescriptorBuilder.setColumnFamily(loginInfo.build());

       admin.createTable(tableDescriptorBuilder.build());
       System.out.println("创建表成功!");
  }

   @Test
   public void testPut() throws IOException {
       Table table = connection.getTable(TableName.valueOf("tb_user"));
       String rowKey = "1001";
       Put put = new Put(Bytes.toBytes(rowKey));
       put.addColumn(Bytes.toBytes("user_info"), Bytes.toBytes("name"), Bytes.toBytes("张三"));
       put.addColumn(Bytes.toBytes("user_info"), Bytes.toBytes("address"), Bytes.toBytes("上海"));
       put.addColumn(Bytes.toBytes("login_info"), Bytes.toBytes("username"), Bytes.toBytes("zhangsan"));
       put.addColumn(Bytes.toBytes("login_info"), Bytes.toBytes("password"), Bytes.toBytes("123456"));

       // 插入数据
       table.put(put);

       // 关闭连接
       table.close();

       System.out.println("插入数据成功!");
  }

   @Test
   public void testGet() throws IOException {
       Table table = connection.getTable(TableName.valueOf("tb_user"));
       String rowKey = "1001";
       Get get = new Get(Bytes.toBytes(rowKey));

       Result result = table.get(get);
       List<Cell> cells = result.listCells();
       for (Cell cell : cells) {
           System.out.println(Bytes.toString(CellUtil.cloneRow(cell))
                   + "==> " + Bytes.toString(CellUtil.cloneFamily(cell))
                   + "{" + Bytes.toString(CellUtil.cloneQualifier(cell))
                   + ":" + Bytes.toString(CellUtil.cloneValue(cell)) + "}");
      }
       table.close();
  }

   @Test
   public void testScan() throws IOException {
       Table table = connection.getTable(TableName.valueOf("tb_user"));

       Scan scan = new Scan();
       scan.setLimit(1); //只查询一条数据
       ResultScanner scanner = table.getScanner(scan);
       Result result = null;
       // 迭代数据
       while ((result = scanner.next()) != null) {
           // 打印数据 获取所有的单元格
           List<Cell> cells = result.listCells();
           for (Cell cell : cells) {
               // 打印rowkey,family,qualifier,value
               System.out.println(Bytes.toString(CellUtil.cloneRow(cell))
                       + "==> " + Bytes.toString(CellUtil.cloneFamily(cell))
                       + "{" + Bytes.toString(CellUtil.cloneQualifier(cell))
                       + ":" + Bytes.toString(CellUtil.cloneValue(cell)) + "}");
          }
      }
       table.close();
  }
}

5.4、使用过滤器进行查询

5.4.1 过滤器

  • RowFilter 基于RowKey的过滤

  • FamilyFilter 基于列簇的过滤

  • QualifierFilter 基于字段的过滤

  • ValueFilter 基于值的过滤

  • DependentColumnFilter 参考值过滤器

5.4.2 比较运算符?

  • LESS 匹配小于设定值的值

  • LESS_OR_EQUAL 匹配小于或等于设定值的值

  • EQUAL 匹配等于设定值的值

  • NOT_EQUAL 匹配与设定值不相等的值

  • GREATER_OR_EQUAL 匹配大于或等于设定值的值

  • GREATER 匹配大于设定值的值

  • NO_OP 排除一切值

5.4.3 比较器有哪些?

  • BinaryComparator 使用Bytes.compareTo()比较当前的阈值

  • BinaryPrefixComparator 与上面的相似,使用Bytes.compareTo()进行匹配,但是是从左端开始前缀匹配

  • NullComparator 不做匹配,只判断当前值不是null

  • BitComparator 通过BitWiseOp类提供的按位与(AND)、或(OR)、异或(XOR)操作执行位级比较。

  • RegexStringComparator 根据一个正则表达式,在实例化这个比较器的时候去匹配表中的数据。

  • SubStringComparator 把阈值和表中数据String实例,同时通过contains()操作匹配字符串

5.4.4 示例代码


//创建RowFilter过滤器
RowFilter rowFilter = new RowFilter(CompareFilter.CompareOp.EQUAL
              , new BinaryComparator("1001".getBytes()));
//创建familyFilter过滤器
FamilyFilter familyFilter = new FamilyFilter(CompareFilter.CompareOp.NO_OP,
               new BinaryComparator("user_Info".getBytes()));
//创建qualifierFilter过滤器
QualifierFilter qualifierFilter = new QualifierFilter(CompareFilter.CompareOp.EQUAL,
               new BinaryComparator("username".getBytes()));
       //创建valueFilter过滤器
ValueFilter valueFilter = new ValueFilter(CompareFilter.CompareOp.EQUAL,
               new BinaryComparator("张三".getBytes()));

5.4.5、查询值等于张三的所有数据


@Test
public void tesValueFilter() throws IOException {
       //1、创建过滤器
       ValueFilter filter = new ValueFilter(CompareFilter.CompareOp.EQUAL, new BinaryComparator("张三".getBytes()));
       //2、创建扫描器
       Scan scan = new Scan();
       //3、将过滤器设置到扫描器中
       scan.setFilter(filter);
       //4、获取HBase的表
       Table table = connection.getTable(TableName.valueOf("tb_user"));
       //5、扫描HBase的表(注意过滤操作是在服务器进行的,也即是在regionServer进行的)
       ResultScanner scanner = table.getScanner(scan);
       for (Result result : scanner) {
           // 6.打印数据 获取所有的单元格
           List<Cell> cells = result.listCells();
           for (Cell cell : cells) {
               // 打印rowkey,family,qualifier,value
               System.out.println(Bytes.toString(CellUtil.cloneRow(cell))
                       + "==> " + Bytes.toString(CellUtil.cloneFamily(cell))
                       + "{" + Bytes.toString(CellUtil.cloneQualifier(cell))
                       + ":" + Bytes.toString(CellUtil.cloneValue(cell)) + "}");
          }
      }
}

6、案例:实现京东的“规格和包装”数据的保存

京东商城的“规格和包装”功能:

https://item.jd.com/7652141.html

 

该功能的要求是:

  • 同一类目下的商品,显示的格式相同

  • 不同类目下的商品,显示的格式不相同

  • 不同的商品,显示的数据不同

由此可以见,由于结构不能固定,所以不能使用传统的数据库的表进行存储,所以可以考虑使用Hbase进行存储。

6.1、表结构设计

表名:tb_jd_param

列族:F1、F2、F3 …… F15

列名:主体:品牌……

列值: 小米(MI)

 

6.2、封装HbaseUtils工具类

为了操作Hbase方便,将Hbase的API操作封装起来。


import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellUtil;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.*;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.util.Bytes;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class HbaseUtils {

   private Connection connection;

   private HbaseUtils() {

  }

   public HbaseUtils(Connection connection) {
       this.connection = connection;
  }

   public HbaseUtils(Configuration configuration) throws IOException {
       this.connection = ConnectionFactory.createConnection(configuration);
  }

   public void deleteTable(String tableName) throws IOException {
       Admin admin = connection.getAdmin();
       admin.disableTable(TableName.valueOf(tableName));
       admin.deleteTable(TableName.valueOf(tableName));
       admin.close();
  }

   public void createTable(String tableName, String... familys) throws Exception {
       if (familys == null || familys.length == 0) {
           throw new Exception("列族名不能为空!");
      }
       // 从连接中获得一个Admin对象
       Admin admin = connection.getAdmin();
       TableDescriptorBuilder tableDescriptorBuilder =
               TableDescriptorBuilder.newBuilder(TableName.valueOf(tableName));
       for (String family : familys) {
           ColumnFamilyDescriptorBuilder userInfo = ColumnFamilyDescriptorBuilder.
                   newBuilder(Bytes.toBytes(family));
           userInfo.setMaxVersions(3); //设置版本信息
           tableDescriptorBuilder.setColumnFamily(userInfo.build());
      }
       admin.createTable(tableDescriptorBuilder.build());
       admin.close();
  }

   public void put(String tableName, String rowKey, String family, String column, String value) throws Exception {
       Table table = connection.getTable(TableName.valueOf(tableName));
       Put put = new Put(Bytes.toBytes(rowKey));
       put.addColumn(Bytes.toBytes(family), Bytes.toBytes(column), Bytes.toBytes(value));
       // 插入数据
       table.put(put);
       // 关闭连接
       table.close();
  }

   public void put(String tableName, String rowKey, List<ColumnData> list) throws Exception {
       Table table = connection.getTable(TableName.valueOf(tableName));
       Put put = new Put(Bytes.toBytes(rowKey));
       for (ColumnData columnData : list) {
           put.addColumn(Bytes.toBytes(columnData.getFamily()), Bytes.toBytes(columnData.getColumn()), Bytes.toBytes(columnData.getValue()));
      }
       // 插入数据
       table.put(put);
       // 关闭连接
       table.close();
  }

   public List<ColumnData> findOne(String tableName, String rowKey) throws IOException {
       Table table = null;
       List<ColumnData> list = null;
       try {
           table = connection.getTable(TableName.valueOf(tableName));
           Get get = new Get(Bytes.toBytes(rowKey));
           Result result = table.get(get);
           List<Cell> cells = result.listCells();
           list = new ArrayList<>();
           for (Cell cell : cells) {
               list.add(new ColumnData(cell));
          }
      } finally {
           if (null != table) {
               table.close();
          }
      }
       return list;
  }

   public List<Map<String, List<ColumnData>>> findAll(String tableName, int limit) throws IOException {
       return scan(tableName, null, limit);
  }

   public List<Map<String, List<ColumnData>>> findAll(String tableName) throws IOException {
       return scan(tableName, null, 0);
  }

   public List<Map<String, List<ColumnData>>> findAll(String tableName, String rowKeyPreFix) throws IOException {
       return findAll(tableName, rowKeyPreFix, 0);
  }

   public List<Map<String, List<ColumnData>>> findAll(String tableName, String rowKeyPreFix, int limit) throws IOException {
       PrefixFilter prefixFilter = new PrefixFilter(Bytes.toBytes(rowKeyPreFix));
       return scan(tableName, prefixFilter, limit);
  }

   private List<Map<String, List<ColumnData>>> scan(String tableName, Filter filter, int limit) throws IOException {
       Table table = null;
       List<Map<String, List<ColumnData>>> list = null;
       try {
           table = connection.getTable(TableName.valueOf(tableName));
           Scan scan = new Scan();
           if (limit > 0) {
               scan.setLimit(limit);
          }
           if (null != filter) {
               scan.setFilter(filter);
          }
           ResultScanner scanner = table.getScanner(scan);
           Result result = null;
           list = new ArrayList<>();
           // 迭代数据
           while ((result = scanner.next()) != null) {
               Map<String, List<ColumnData>> map = new HashMap<>();
               List<Cell> cells = result.listCells();
               List<ColumnData> columnDataList = new ArrayList<>();
               if (cells != null && !cells.isEmpty()) {
                   for (Cell cell : cells) {
                       columnDataList.add(new ColumnData(cell));
                  }
                   map.put(Bytes.toString(CellUtil.cloneRow(cells.get(0))), columnDataList);
              }
               list.add(map);
          }
      } finally {
           if (null != table) {
               table.close();
          }
      }
       return list;
  }

   public static class ColumnData {
       private String family, column, value;

       public ColumnData(String family, String column, String value) {
           this.family = family;
           this.column = column;
           this.value = value;
      }

       public ColumnData(Cell cell) {
           this.family = Bytes.toString(CellUtil.cloneFamily(cell));
           this.column = Bytes.toString(CellUtil.cloneQualifier(cell));
           this.value = Bytes.toString(CellUtil.cloneValue(cell));
      }

       public ColumnData() {
      }

       public String getFamily() {
           return family;
      }

       public void setFamily(String family) {
           this.family = family;
      }

       public String getColumn() {
           return column;
      }

       public void setColumn(String column) {
           this.column = column;
      }

       public String getValue() {
           return value;
      }

       public void setValue(String value) {
           this.value = value;
      }

       @Override
       public String toString() {
           return "ColumnData{" +
                   "family='" + family + '\'' +
                   ", column='" + column + '\'' +
                   ", value='" + value + '\'' +
                   '}';
      }
  }
}

6.3、创建表


   private HbaseUtils hbaseUtils;

   @Before
   public void init() throws IOException {
       Configuration configuration = new Configuration();
       // 配置ZooKeeper信息
       configuration.set("hbase.zookeeper.quorum", "node01:2181");
       this.hbaseUtils = new HbaseUtils(configuration);
  }

@Test
   public void testCreateTable() throws Exception {
       String[] familys = new String[]{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15"};
       this.hbaseUtils.createTable("tb_jd_param", familys);
       System.out.println("创建表成功!");
  }

6.4、插入数据


@Test
   public void testPut() throws Exception {
       String tableName = "tb_jd_param";
       // PARAM_分类ID_商品ID
       String rowKey = "PARAM_1001_7652141";
       List<HbaseUtils.ColumnData> list = new ArrayList<>();
       list.add(new HbaseUtils.ColumnData("F1", "主体:品牌", "小米(MI)"));
       list.add(new HbaseUtils.ColumnData("F1", "主体:型号", "小米8"));
       list.add(new HbaseUtils.ColumnData("F1", "主体:入网型号", "以官网信息为准"));
       list.add(new HbaseUtils.ColumnData("F2", "基本信息:机身颜色", "黑色"));
       list.add(new HbaseUtils.ColumnData("F2", "基本信息:机身长度(mm)", "154.9"));
       list.add(new HbaseUtils.ColumnData("F2", "基本信息:机身宽度(mm)", "74.8"));
       this.hbaseUtils.put(tableName, rowKey, list);

       rowKey = "PARAM_1002_6916787";
       list.clear();
       list.add(new HbaseUtils.ColumnData("F1", "主体参数:产品品牌", "小米(MI)"));
       list.add(new HbaseUtils.ColumnData("F1", "主体参数:产品型号", "L50M5-AD"));
       list.add(new HbaseUtils.ColumnData("F1", "主体参数:产品颜色", "黑色"));
       list.add(new HbaseUtils.ColumnData("F2", "显示参数:屏幕尺寸", "50英寸"));
       this.hbaseUtils.put(tableName, rowKey, list);

       rowKey = "PARAM_1002_6583962";
       list.clear();
       list.add(new HbaseUtils.ColumnData("F1", "主体参数:产品品牌", "创维(Skyworth)"));
       list.add(new HbaseUtils.ColumnData("F1", "主体参数:产品型号", "55V7"));
       list.add(new HbaseUtils.ColumnData("F1", "主体参数:产品颜色", "黑色"));
       list.add(new HbaseUtils.ColumnData("F2", "显示参数:屏幕尺寸", "55英寸"));
       this.hbaseUtils.put(tableName, rowKey, list);
  }

6.5、根据rowkey查询数据

显示商品详情时,根据商品id查询数据。


   @Test
   public void testFindOne() throws Exception {
       String tableName = "tb_jd_param";
       // PARAM_分类ID_商品ID
       String rowKey = "PARAM_1001_7652141";
       List<HbaseUtils.ColumnData> list = this.hbaseUtils.findOne(tableName, rowKey);
       for (HbaseUtils.ColumnData columnData : list) {
           System.out.println(columnData);
      }
  }

6.6、查询某一个类目下的所有数据

有些时候,可能需要需要查询某一个类目下的所有商品的规格数据,所以需要根据rowkey的前缀查询。


   @Test
   public void testFindAllRowKeyPreFix() throws Exception {
       String tableName = "tb_jd_param";
       List<Map<String, List<HbaseUtils.ColumnData>>> all = this.hbaseUtils.findAll(tableName,"PARAM_1001");
       for (Map<String, List<HbaseUtils.ColumnData>> map : all) {
           for (Map.Entry<String, List<HbaseUtils.ColumnData>> entry : map.entrySet()) {
               String rowKey = entry.getKey();
               List<HbaseUtils.ColumnData> list = entry.getValue();
               for (HbaseUtils.ColumnData columnData : list) {
                   System.out.println(rowKey + "-->" + columnData);
              }
          }
      }
  }

查询结果:


PARAM_1001_7652141-->ColumnData{family='F1', column='主体:入网型号', value='以官网信息为准'}
PARAM_1001_7652141-->ColumnData{family='F1', column='主体:品牌', value='小米(MI)'}
PARAM_1001_7652141-->ColumnData{family='F1', column='主体:型号', value='小米8'}
PARAM_1001_7652141-->ColumnData{family='F2', column='基本信息:机身宽度(mm)', value='74.8'}
PARAM_1001_7652141-->ColumnData{family='F2', column='基本信息:机身长度(mm)', value='154.9'}
PARAM_1001_7652141-->ColumnData{family='F2', column='基本信息:机身颜色', value='黑色'}

7、Hbase高级

7.1、Hbase的整体架构

 

7.1.1 Client

  • 使用HBase RPC机制与HMaster和HRegionServer进行通信;

  • Client与HMaster进行通信进行管理类操作;

  • Client与HRegionServer进行数据读写类操作。

7.1.2 Zookeeper

  • 保证任何时候,集群中只有一个running master,避免单点问题;

  • 存贮所有Region的寻址入口,包括-ROOT-表地址、HMaster地址;

  • 实时监控Region Server的状态,将Region server的上线和下线信息,实时通知给Master;

  • 存储Hbase的schema,包括有哪些table,每个table有哪些column family。

7.1.3 HMaster

可以启动多个HMaster,通过Zookeeper的Master Election机制保证总有一个Master运行。

角色功能:

  • 为Region server分配region;

  • 负责region server的负载均衡;

  • 发现失效的region serve并重新分配其上的region;

  • GFS上的垃圾文件回收;

  • 处理用户对标的增删改查操作。

7.1.4 HRegionServer

HBase中最核心的模块,主要负责响应用户I/O请求,向HDFS文件系统中读写数据。

作用:

  • 维护Master分配给它的region,处理对这些region的IO请求;

  • 负责切分在运行过程中变得过大的region。

  • 此外,HRegionServer管理一些列HRegion对象,每个HRegion对应Table中一个Region,HRegion由多个HStore组成,每个HStore对应Table中一个Column Family的存储,Column Family就是一个集中的存储单元,故将具有相同IO特性的Column放在一个Column Family会更高效。

1.3.6 HRegion

table在行的方向上分隔为多个Region。

  • Region是HBase中分布式存储和负载均衡的最小单元,即不同的region可以分别在不同的Region Server上,但同一个Region是不会拆分到多个server上。

  • Region按大小分隔,每个表一行是只有一个region。随着数据不断插入表,region不断增大,当region的某个列族达到一个阈值(默认10G)时就会分成两个新的region。

7.1.4 Store

每一个region有一个或多个store组成,至少是一个store,hbase会把一起访问的数据放在一个store里面,即为每个ColumnFamily建一个store,如果有几个ColumnFamily,也就有几个Store。一个Store由一个memStore和0或者多个StoreFile组成。HBase以store的大小来判断是否需要切分region。

1.3.7 HLog

在分布式系统环境中,无法避免系统出错或者宕机,一旦HRegionServer意外退出,MemStore中的内存数据就会丢失,引入HLog就是防止这种情况。

工作机制:每个HRegionServer中都会有一个HLog对象,HLog是一个实现Write Ahead Log的类,每次用户操作写入Memstore的同时,也会写一份数据到HLog文件,HLog文件定期会滚动出新,并删除旧的文件(已持久化到StoreFile中的数据)。当HRegionServer意外终止后,HMaster会通过Zookeeper感知,HMaster首先处理遗留的HLog文件,将不同region的log数据拆分,分别放到相应region目录下,然后再将失效的region重新分配,领取到这些region的HRegionServer在Load Region的过程中,会发现有历史HLog需要处理,因此会Replay HLog中的数据到MemStore中,然后flush到StoreFiles,完成数据恢复。

7.2、Hbase数据写入流程

  1. Client 也是先访问 zookeeper,找到 Meta 表,并获取 Meta 表信息。

  2. 确定当前将要写入的数据所对应的 RegionServer 服务器和 Region。

  3. Client 向该 RegionServer 服务器发起写入数据请求,然后 RegionServer 收到请求并响 应。

  4. Client 先把数据写入到 HLog,以防止数据丢失。

  5. 然后将数据写入到Memstore。

  6. 如果 Hlog 和 Memstore 均写入成功,则这条数据写入成功。在此过程中,如果 Memstore 达到阈值(默认128M),会把 Memstore 中的数据 flush 到 StoreFile 中。

  7. 当 Storefile 越来越多,会触发 Compact 合并操作,把过多的 Storefile 合并成一个大的 Storefile。当 Storefile 越来越大,Region 也会越来越大,达到阈值后,会触发 Split 操作, 将 Region 一分为二。

 

7.3、Hbase读取数据流程

  1. 首先 Client 先去访问 zookeeper,从 zookeeper 里面获取 meta 表所在的位置信息,即找到这个 meta 表在哪个 HRegionServer 上保存着。

  2. 接着 Client 通过刚才获取到的 HRegionServer 的 IP 来访问 Meta 表所在的HRegionServer,从而读取到 Meta,进而获取到 Meta 表中存放的元数据。

  3. Client 通过元数据中存储的信息,访问对应的 HRegionServer,然后扫描所在 HRegionServer 的 Memstore 和 Storefile 来查询数据。

  4. 最后 HRegionServer 把查询到的数据响应给 Client。

 

 

posted on 2018-11-25 21:30  伪全栈的java工程师  阅读(376)  评论(0编辑  收藏  举报