Hbase—学习笔记(一)

时间:2022-01-04 20:24:32

此文的目的:

  1、重点理解Hbase的整体工作机制

  2、熟悉编程api,能够用来写程序

1.  什么是HBASE

1.1.   概念特性

HBASE是一个数据库----可以提供数据的实时随机读写

HBASE与mysql、oralce、db2、sqlserver等关系型数据库不同,它是一个NoSQL数据库(非关系型数据库)

* Hbase的表模型与关系型数据库的表模型不同:

* Hbase的表没有固定的字段定义;

* Hbase的表中每行存储的都是一些key-value对

* Hbase的表中有列族的划分,用户可以指定将哪些kv插入哪个列族

* Hbase的表在物理存储上,是按照列族来分割的,不同列族的数据一定存储在不同的文件中

* Hbase的表中的每一行都固定有一个行键,而且每一行的行键在表中不能重复

* Hbase中的数据,包含行键,包含key,包含value,都是byte[ ]类型,hbase不负责为用户维护数据类型

* HBASE对事务的支持很差

HBASE相比于其他nosql数据库(mongodb、redis、cassendra、hazelcast)的特点:

Hbase的表数据存储在HDFS文件系统中

从而,hbase具备如下特性:存储容量可以线性扩展; 数据存储的安全性可靠性极高!

1.1.1、各种数据库之间的差别比较

Hbase—学习笔记(一)

Hbase,Hive区别:

Hive数据仓库的理解:

  1、仓库就是存放历史数据存的地方,反复对历史数据进行读操作,统计分析操作,历史数据不需要修改。

  2、Hive严格意义上来讲不能算是数据库;

  Hive与Hbase巨大的区别在于,Hive底层依赖的文件系统HDFS中的数据是用户提交的,没有固定的格式,可以理解成按照分隔符分割的简单文本,而不是精心设计的文件(如Mysql那样精心设计的文件加上mysql*的软件系统,可以对数据进行随机的访问和修改操作),Hive只能对这些数据进行读取,分析,不能对修改和跟新数据。

  3、mysql也当然具备做为数据仓库的功能和能力,但是数据量太大是,mysql不适合,mysql适于联机事务处理(在线实时交互)。

Hbase

  1、同msyql一样,底层的文件系统的精心设计的,Hbase的底层文件系统也是HDFS。

  2、具有联机事务处理数据库的特性(快速 实时操作数据库,增删改查)。

  3、Hbase本身的特性:

        • 文件系统:HDFS(表可以很大很大)

        • 分布式系统
        • nosql表结构。

1.2  Hbase快速理解

两个基本问题:

  1、怎么存数据

  2、怎么查数据

1.2.1 、Hbase特性与表结构

Hbase—学习笔记(一)

(以上是Hbase的逻辑结构)

列族:KV分为若干的大类:,如上表所示。

  1、每个列族中的kv数据可以随意存放,key可以不同,没有严格要求,完全有用户决定,当然一般使用情况下,数据是规整的;

  如:下表是可以的,但是为了数据的规整,一般不建议随意为key起名字,最好保持一致。

rowkey base_info
001 name:jj, age:12, sex:mal
002  nick:ls, age:15, xb:male

  2、同一个列祖中的kv的个数也是灵活的,可以省略某些kv

cell:同一个数据可以保存多个值

  1、一个kv就是一个cell

  2、一个key可有有多个版本的值

  3、时间戳作为版本

1.2.2、Hbase整体工作机制示意图

  如下图:

1.2.2.1、存储问题(分散存储)

  按照region划分范围存储(region目录还细分为列族目录,列族目录下才存放具体的文件)

  

1.2.2.2、查询问题(分布式:分任务查询)

  Hbase底层文件系统是HDFS,Hbase中的表最终也会落地HDFS,那么Hbase的一张表可以很大很大,表中的数据不断的增加增加存储也是可以的,但是怎么查询呢?

    

  当请求特别多的时候,一台Hbase服务器(region server)是不行的,Hbase是一个分布式的系统,当有多个Hbase提供服务的时候,某一次客户端的请求具体由那个服务器来处理呢?

  当某一台服务器挂了,谁来接替他的工作,如何接替?

  解决:服务器需要分任务(分布式系统里肯定是要分任务的)

      一台服务器,负责Hbase中某个表的某一个部分

  如何界定部分?

    需要划分范围:按照行健范围

  这样通过分任务之后就是一个分布式系统。不同的regionServer可以并行的去访问hdfs中的数据(表数据)这样还有一个问题,若某一张表中的所有数据都存在同一个HDFS中的文件中,即使是负责同一张表的不同范围regionserver,大量的并行请求也会同时访问同一个hdfs文件,这会造成性能上的瓶颈,所以表中的数据在HDFS中是按照region划分范围存储(region目录还细分为列族目录,列族目录下才存放具体的文件)的这样同一个表的不同region范围的数据落地HDFS中不同的文件中。否则会造成即是分了任务一个dataNode被频繁的访问。

1.2.2.2.1、客户端读写数据是的路由流程:客户端找数据的流程

      问题描述:客户端怎么知道他要访问的某个region在那一台regionserver上呢?

            master是不会保存哪些region在哪些regionserver上的,否则就是有状态的节点了,一旦master挂了,regionserver立刻无法提供服务,而事实不是这样。

            上述信息就是所谓的索引信息,master是不会保存索引信息的,索引信息是保存在系统索引表中的。

  1、索引表当然也存在于hdfs中,且只有一个region;

  2、谁来负责查询索引表

  下图所示,索引表数据的查询由hdp-02机器上的regionserver负责,那么客户端怎样知道meta数据由hdp-02负责

  Hbase—学习笔记(一)

  zookeeper上会记录元数据索引表,有哪一台regionserver负责管理。 客户单端,每次访问数据之前,先查询zookeeper。

 Hbase—学习笔记(一)

  下图为Zookeeper节点meta-region-server的信息

  Hbase—学习笔记(一)

访问流程:

  1、客户端去Zookeeper上查询,负责索引表数据的regionserver;

  2、找该台regionserver服务器,查询出客户端要访问的region数据由哪一台regionserver负责;

  3、客户端找具体的regionserver要数据;

总结:

    1、Hbase表中的数据是存放在hdfs中的。

  2、regionserver只负责逻辑功能,对数据进行增删改查,不存储它负责的region的数据。

  3、一个regionserver可以负责多个表的多个region。

  4、region是regionServer管理数据的基本单元。

    

  1、客户端查找数据不经过master

  2、客观端查找数据一定经过Zookeeper

 

 

Hbase整体工作机制示意图

Hbase—学习笔记(一)

Hbase集群中有两个角色

  region server

  master

region server负责数据的逻辑处理(增删改查),region server对数据的操作是不经过master。某一个瞬间master挂了,regionserver还是可以正常服务的,但是一定时间之后,万一某一个regionserver挂了,该regionserver负责的任务得不到重新分配,就会出问题。

1.2.2.3、服务器宕机问题(借助Zookeeper实现HA)

master对regionserver的监管,状态协调

  1、所有的状态信息记录在Zookeeper里。

  2、master负责监管region server的状态,知道每一个regionserver负责哪些表的哪些region,不负责帮用户查数据,一旦发现某个region server发生故障,会找另外的一台机器来接替该region server负责的region区域。

  3、master通过Zookeeper来获取regionserver的状态。

  4、master通过Zookeeper监听region server,maste是没有状态的节点,master存在单点故障的风险;通过主备容灾实现HA机制。

master HA

  状态信息记录在Zookeeper里。master是无状态节点,standby 切换为 active状态,查看Zookeeper后,立马知道现在的集群是什么样子。

1.2.3、Hbase工作机制补充—regionserver数据管理

首先在hbase的表中插入一些数据,然后来观察一下hdfs中存的数据,发现hdfs下并没有数据,但是scan明明可以查到数据的,这是怎么回事呢?

Hbase—学习笔记(一)

scan可以查到数据。而上图hdfs中却没有数据文件。

Hbase—学习笔记(一)

其实:此时此刻的数据位于内存中。

Hbase—学习笔记(一)

1.2.3.1、内存缓存热数据

每个region在内存中都对应分配一块缓存空间,memstore,但是memstore毕竟有限,不会将全部的数据都存入到内存中,还是有很大的数据是存在hdfs中的。当数据量很小的时候没有必要写入到hdfs文件中,这就解释了为什么上述hdfs中没有文件数据。

上述用户插入的数据都保存在了内存中,这样速度会比存入hdfs中快很多,但是又不能吧全部数据都存入到内存中,内存中只会保存一些热数据【刚刚被访问过的,刚刚被插入的数据】

如果有人找regionserver查数据是,regionserver内存中没有该数据,就会去hdfs中查找,找到之后作为热数据,然后缓存在内存中,超过一段时间没有人访问就不是热数据了,就不会继续保存在内存中。

2、数据保存在内存中就有风险,万一没有来的落地hdfs,宕机了,内存中的数据会丢失,怎么办?

  解决方案,regionserver一方面在自己内存中写数据,一方面在hdfs中写日志,一旦宕机后,master找来替换机器后,该机器会读取日志信息,还原内存中的数据。

hdfs中查看相应的痕迹

Hbase—学习笔记(一)

Hbase—学习笔记(一)

Hbase—学习笔记(一)

 总结:

  1、热数据存储

  2、日志记录

1.2.3.2、持久化到hdfs

1、当内存中的数据插满时候,数据会持久化到hdfs中

2、当hbase退出时候,数据也会持久化到hdfs中

Hbase—学习笔记(一)

列族目录下已经有数据文件。

Hbase—学习笔记(一)

使用hadoop 命令查看具体数据

Hbase—学习笔记(一)

 

1.3、Hbase表模型

hbase的表模型跟mysql之类的关系型数据库的表模型差别巨大

hbase的表模型中有:行的概念;但没有字段的概念

行中存的都是key-value对,每行中的key-value对中的key可以是各种各样,每行中的key-value对的数量也可以是各种各样

1.3.1、Hbase表模型的要点

1、一个表,有表名

2、一个表可以分为多个列族(不同列族的数据会存储在不同文件中)

3、表中的每一行有一个“行键rowkey”,而且行键在表中不能重复

4、表中的每一对kv数据称作一个cell

5、hbase可以对数据存储多个历史版本(历史版本数量可配置)

6、整张表由于数据量过大,会被横向切分成若干个region(用rowkey范围标识),不同region的数据也存储在不同文件中

7、hbase会对插入的数据按顺序存储:

  要点一:首先会按行键排序

  要点二:同一行里面的kv会按列族排序,再按k排序

Hbase—学习笔记(一)

Hbase—学习笔记(一)

1.3.2、Hbase的表能存什么数据类型

hbase中只支持byte[]

此处的byte[] 包括了: rowkey,key,value,列族名,表名

1.3.3、hbase表的物理结构

Hbase—学习笔记(一)

3、安装Hbase

HBASE是一个分布式系统

其中有一个管理角色:  HMaster(一般2台,一台active,一台backup)

其他的数据节点角色:  HRegionServer(很多台,看数据容量),最好部署在datanode节点上。

3.1、 安装准备:

首先,要有一个HDFS集群,并正常运行; regionserver应该跟hdfs中的datanode在一起

其次,还需要一个zookeeper集群,并正常运行

然后,安装HBASE

角色分配如下:

Hdp01:  namenode  datanode  regionserver  hmaster  zookeeper

Hdp02:  datanode   regionserver  zookeeper

Hdp03:  datanode   regionserver  zookeeper

不需要yarn集群,不需要跑mapreduce等运算框架的程序,但是mapreduce可以读取操作Hbase中的数据。

3.2、安装步骤

3.2.1、安装Zookeeper

3.2.2、安装Hbase

解压hbase安装包,conf目录下的配置文件如下:

Hbase—学习笔记(一)

3.2.2.1、hbase-env.sh

  1、java_Home

  2、拒使用自己的zookeeper

修改conf目录下的hbase-env.sh

export JAVA_HOME=/root/apps/jdk1..0_67
export HBASE_MANAGES_ZK=false

Hbase自带一套Zookeeper,这里选择关闭再带的Zookeeper,用我们自己提供的zookeeper。

Hbase—学习笔记(一)

3.2.2.2、hbase-site.xml

  1、HDFS

  2、指定Hbase为分布式模式,默认是单机模式

  3、Zookeeper地址

    <configuration>
<!-- 指定hbase在HDFS上存储的路径 -->
<property>
<name>hbase.rootdir</name>
          <!-- 会在hdfs根目录/下建立一个文件夹,用来放hbase中的数据【Hbase基于HDFS】-->
<value>hdfs://hdp01:9000/hbase</value>
</property>
<!-- 指定hbase是分布式的 -->
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<!-- 指定zk的地址,多个用“,”分割 -->
<property>
<name>hbase.zookeeper.quorum</name>
<value>hdp01:2181,hdp02:2181,hdp03:2181</value>
</property>
</configuration>

3.2.2.3、regionservers

告知Hbase启动脚本,在哪些机器上启动,regionservers

hdp01
hdp02
hdp03

确保hdfs没有问题

Hbase—学习笔记(一)

3.3、启动hbase集群

确保Zookeeper正常,确保hdfs正常。

3.3.1、批量启动

使用自动批量启动脚本

bin/start-hbase.sh

改命令会在本机起送master,

启动完后,还可以在集群中找任意一台机器启动一个备用的master

bin/hbase-daemon.sh start master

新启的这个master会处于backup状态

3.3.2、逐个启动

人肉,逐个机器,启动进程。

bin/hbase-daemon.sh start master
bin/hbase-daemon.sh start regionserever

同步服务器时间

Hbase—学习笔记(一)

写入硬件时钟,否则重启无效。

3.3.3、查看hdfs文件

启动Hbase后会在hbase-site.xml中配置的HDFS路径下,建立对应的文件夹。

Hbase—学习笔记(一)

hbase文件夹内容如下

Hbase—学习笔记(一)

数据存放在data文件下,data文件夹里有default库【默认数据库】,库里会存放用户建立的表,hbase是系统的一些数据。

Hbase—学习笔记(一)

3.3.4、查看Hbase网页端

通过网页端查看Hbase信息,HMaster监听两个端口。一个是内部通信端口16000,一个是外部服务端口16010。

Hbase—学习笔记(一)

Hbase—学习笔记(一)

Hbase—学习笔记(一)

Hbase—学习笔记(一)

3.3.5、索引表

系统表

记录所有用户表的region位置信息。

索引表,记录索引,哪一个用户表的哪一个region范围在哪一台regionserver上

客户端找数据的时候,先查索引表,确定自己要访问的数据范围在一个regionserver上,然后再去访问该regionserver去拿数据。

Hbase—学习笔记(一)

Hbase—学习笔记(一)

3.3.6、查看Zookeeper

Hbase集群中不同角色的信息沟通是通过Zookeeper的,那么必定会在Zookeeper记录一下状态信息。

Hbase—学习笔记(一)

4、hbase客户端

4.1、命令行客户端

bin/hbase shell
Hbase> list // 查看表
Hbase> status // 查看集群状态
Hbase> version // 查看集群版本

进入命令行客户端,help查看都有哪些命令【命令分为不同的组别 ddl dml tools replication...】。

bin/hbase shell

如果shell命令行无法退格删除字符,则如下操作

Hbase—学习笔记(一)

Hbase—学习笔记(一)

Hbase—学习笔记(一)

语句没有分号

4.1.1、建表

create 't_user_info','base_info','extra_info'

         表名      列族名   列族名

Hbase—学习笔记(一)

 查看建表后的状态

Hbase—学习笔记(一)

Hbase—学习笔记(一)

HDFS中的数据

Hbase—学习笔记(一)

Hbase—学习笔记(一)

Hbase—学习笔记(一)

Hbase—学习笔记(一)

4.1.2、插入数据

put命令

语法:

put 't_user_info','行健','列族:key','value'

Hbase—学习笔记(一)

hbase(main):011:0> put 't_user_info','','base_info:username','zhangsan'
0 row(s) in 0.2420 seconds hbase(main):012:0> put 't_user_info','','base_info:age',''
0 row(s) in 0.0140 seconds hbase(main):013:0> put 't_user_info','','base_info:sex','female'
0 row(s) in 0.0070 seconds hbase(main):014:0> put 't_user_info','','extra_info:career','it'
0 row(s) in 0.0090 seconds hbase(main):015:0> put 't_user_info','','extra_info:career','actoress'
0 row(s) in 0.0090 seconds hbase(main):016:0> put 't_user_info','','base_info:username','liuyifei'
0 row(s) in 0.0060 seconds

  

4.1.3、查询数据方式一:get 单行查询

语法:

-- 返回该行全部数据
get 't_user_info','行健' -- 返回该行指定列族:key的值
get 't_user_info','行健', '列族:key'

特性:Hbase会对 ' 列族:key ' 进行字典序排序

   timestamp:是key的版本号 

hbase(main):020:0> get 't_user_info',''
COLUMN CELL
base_info:age timestamp=1496568160192, value=19
base_info:sex timestamp=1496567934669, value=female
base_info:username timestamp=1496567889554, value=zhangsan
extra_info:career timestamp=1496567963992, value=it
4 row(s) in 0.0770 seconds

Hbase—学习笔记(一)

 

Hbase—学习笔记(一)

4.1.3、查询数据方式二:scan 扫描

 scan是全表扫描

特性:

  1、先按照行健排序。

  2、同一行健,按照key的字典序排序。

hbase(main):017:0> scan 't_user_info'
ROW COLUMN+CELL
001 column=base_info:age, timestamp=1496567924507, value=18
001 column=base_info:sex, timestamp=1496567934669, value=female
001 column=base_info:username, timestamp=1496567889554, value=zhangsan
001 column=extra_info:career, timestamp=1496567963992, value=it
002 column=base_info:username, timestamp=1496568034187, value=liuyifei
002 column=extra_info:career, timestamp=1496568008631, value=actoress
2 row(s) in 0.0420 seconds

4.1.4、delete 删除一个kv数据

hbase(main):021:0> delete 't_user_info','','base_info:sex'
0 row(s) in 0.0390 seconds

4.1.5、deleteall 删除整行数据

hbase(main):024:0> deleteall 't_user_info',''
0 row(s) in 0.0090 seconds hbase(main):025:0> get 't_user_info',''
COLUMN CELL
0 row(s) in 0.0110 seconds

4.1.6、删除整个表

  disable

  drop

删除表之前先要停用表。

Hbase—学习笔记(一)

hbase(main):028:0> disable 't_user_info'
0 row(s) in 2.3640 seconds hbase(main):029:0> drop 't_user_info'
0 row(s) in 1.2950 seconds hbase(main):030:0> list
TABLE
0 row(s) in 0.0130 seconds => []

4.2、客户端api

DDL

  如何描述一个表

  如何创建一个表

    删除一个表

    修改一个表

* 1、构建连接
* 2、从连接中取到一个表DDL操作工具admin
* 3、admin.createTable(表描述对象);
* 4、admin.disableTable(表名);
* 5、admin.deleteTable(表名);
* 6、admin.modifyTable(表名,表描述对象);

DML

4.2.1、创建连接对象

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.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.regionserver.BloomType; Connection conn = null; @Before
public void getConn() throws Exception{
// new Configuration() 加载的是hadoop的配置文件:core-site.xml hdfs-site.xml,不会加载hbase-site.xml
     // 构建一个连接对象
     // Hbase提供了HbaseConfiguraton 用来加载hbase-site.xml
     Configuration conf = HBaseConfiguration.create(); // 会自动加载hbase-site.xml
     // 客户端查询数据的路由流程可知:客户端需要先链接 Zookeeper 获取索引表
conf.set("hbase.zookeeper.quorum", "hdp-01:2181,hdp-02:2181,hdp-03:2181");

     // 创建链接对象
conn = ConnectionFactory.createConnection(conf);
}

4.2.2、DDL操作

1、创建一个连接

Connection conn = ConnectionFactory.createConnection(conf);

2、拿到一个DDL操作器:表管理器admin

Admin admin = conn.getAdmin();

3、用表管理器的api去建表、删表、修改表定义

admin.createTable(HTableDescriptor descriptor);

4.2.2.1、创建表

@Test
public void testCreateTable() throws Exception{ // 从连接中构造一个DDL操作器
Admin admin = conn.getAdmin(); // 创建一个表定义描述对象
HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("user_info")); // 创建列族定义描述对象
// 通过列族描述定义对象,可以设置列族的很多重要属性信息
HColumnDescriptor hColumnDescriptor_1 = new HColumnDescriptor("base_info");
hColumnDescriptor_1.setMaxVersions(3); // 设置该列族中存储数据的最大版本数,默认是1 HColumnDescriptor hColumnDescriptor_2 = new HColumnDescriptor("extra_info"); // 将列族定义信息对象放入表定义对象中
hTableDescriptor.addFamily(hColumnDescriptor_1);
hTableDescriptor.addFamily(hColumnDescriptor_2); // 用ddl操作器对象:admin 来建表
admin.createTable(hTableDescriptor); // 关闭连接
admin.close();
conn.close(); }

Hbase—学习笔记(一)

4.2.2.2、删除表

先停用表 disableTable

然后删除表 deleteTable

@Test
public void testDropTable() throws Exception{ Admin admin = conn.getAdmin(); // 停用表
admin.disableTable(TableName.valueOf("user_info"));
// 删除表
admin.deleteTable(TableName.valueOf("user_info")); admin.close();
conn.close();
}

Hbase—学习笔记(一)

4.2.2.3、修改表

// 修改表定义--添加一个列族
@Test
public void testAlterTable() throws Exception{ Admin admin = conn.getAdmin(); // 取出旧的表定义信息
HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("user_info")); // 新构造一个列族定义
HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("other_info");
hColumnDescriptor.setBloomFilterType(BloomType.ROWCOL); // 设置该列族的布隆过滤器类型 // 将列族定义添加到表定义对象中
tableDescriptor.addFamily(hColumnDescriptor); // 将修改过的表定义交给admin去提交
admin.modifyTable(TableName.valueOf("user_info"), tableDescriptor); admin.close();
conn.close(); }

Hbase—学习笔记(一)

 完整代码

import java.io.IOException;

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.TableName;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.regionserver.BloomType;
import org.junit.Before;
import org.junit.Test; public class HbaseClientDemo {
Connection conn = null; @Before
public void getConn() throws Exception{
// 构建一个连接对象
Configuration conf = HBaseConfiguration.create(); // 会自动加载hbase-site.xml
conf.set("hbase.zookeeper.quorum", "hdp-01:2181,hdp-02:2181,hdp-03:2181"); conn = ConnectionFactory.createConnection(conf);
} /**
* DDL
* @throws Exception
*/
@Test
public void testCreateTable() throws Exception{ // 从连接中构造一个DDL操作器
Admin admin = conn.getAdmin(); // 创建一个表定义描述对象
HTableDescriptor hTableDescriptor = new HTableDescriptor(TableName.valueOf("user_info")); // 创建列族定义描述对象
HColumnDescriptor hColumnDescriptor_1 = new HColumnDescriptor("base_info");
hColumnDescriptor_1.setMaxVersions(3); // 设置该列族中存储数据的最大版本数,默认是1 HColumnDescriptor hColumnDescriptor_2 = new HColumnDescriptor("extra_info"); // 将列族定义信息对象放入表定义对象中
hTableDescriptor.addFamily(hColumnDescriptor_1);
hTableDescriptor.addFamily(hColumnDescriptor_2); // 用ddl操作器对象:admin 来建表
admin.createTable(hTableDescriptor); // 关闭连接
admin.close();
conn.close(); } /**
* 删除表
* @throws Exception
*/
@Test
public void testDropTable() throws Exception{ Admin admin = conn.getAdmin(); // 停用表
admin.disableTable(TableName.valueOf("user_info"));
// 删除表
admin.deleteTable(TableName.valueOf("user_info")); admin.close();
conn.close();
} // 修改表定义--添加一个列族
@Test
public void testAlterTable() throws Exception{ Admin admin = conn.getAdmin(); // 取出旧的表定义信息
HTableDescriptor tableDescriptor = admin.getTableDescriptor(TableName.valueOf("user_info")); // 新构造一个列族定义
HColumnDescriptor hColumnDescriptor = new HColumnDescriptor("other_info");
hColumnDescriptor.setBloomFilterType(BloomType.ROWCOL); // 设置该列族的布隆过滤器类型 // 将列族定义添加到表定义对象中
tableDescriptor.addFamily(hColumnDescriptor); // 将修改过的表定义交给admin去提交
admin.modifyTable(TableName.valueOf("user_info"), tableDescriptor); admin.close();
conn.close(); } }

4.2.3、布隆过滤器BloomType

假设有一个互联网爬虫程序,不断将网页中的url爬取下来,这里有一个问题,因为链接可能存在回路,会造成程序的死循环,因此需要判断每条url是否已经被爬取过。

最传统的办法就是将url放入数据库中,每次新爬取的url和数据库进行比对,但是当数据量很大是,每一条url都要和全部的数据进行一次比对,显然不可。

后来出现布隆过滤器专门来解决这个问题。

大致思路:

  提供一个64k(或者其他长度,越长精度越大)大小的二进制数组。

  将url通过一个算法(简单理解为hash算法)映射成8个bit,对应在一个64k大小的二进制的8个索引上。

  经过这个算法索引位置全部吻合的连个url有很大概率是同一个url;

  但是索引位置不吻合的两位url一定不是同一个url;

Hbase中的BloomType的应用

  之前说过hbase中表在hdfs中是按照如下目录存放的,有hbase的持久化操作可知,一旦内存中的数据写满或者其他原因,就会序列化内存中的数据到hdfs,而内存中保存的是热数据,这样可能会造成同一个key出现在不同的序列化文件中,而且每个key还有不同的版本,更加大了这个可能性。

    /库名/表名/region/列族/文件1

               .../文件2

               .../文件3

               .../文件4

问题:

  1、不同的文件中可能保存相同的key

  2、文件太多,在查询时挨个比对显然效率太低

解决方式:

  1、可以为每行数据生成已给bloom过滤器记录

  2、或者为每个字段生成一个bloom过滤器记录

这样在找数据的时候,对数据进行结算得到若干比特,然后去比较bloom过滤器,这样会快很多。

Hbase—学习笔记(一)

4.2.4、DML操作

// 获取一个操作指定表的table对象,进行DML操作
Table table = conn.getTable(TableName.valueOf("user_info"));
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.Cell;
import org.apache.hadoop.hbase.CellScanner;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Connection;
import org.apache.hadoop.hbase.client.ConnectionFactory;
import org.apache.hadoop.hbase.client.Delete;
import org.apache.hadoop.hbase.client.Get;
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;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.util.Bytes;

4.2.4.1、增加数据

1、Table对象,进行DML操作;

2、数据封装对象put;

3、Table.put(put) | Table.put(List<put>puts);

    /**
* 增
* 改:put来覆盖
* @throws Exception
*/
@Test
public void testPut() throws Exception{ // 获取一个操作指定表的table对象,进行DML操作
Table table = conn.getTable(TableName.valueOf("user_info")); // 构造要插入的数据为一个Put类型(一个put对象只能对应一个rowkey)的对象
Put put = new Put(Bytes.toBytes("001"));
put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("张三"));
put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("18"));
put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("北京")); Put put2 = new Put(Bytes.toBytes("002"));
put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("李四"));
put2.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes("28"));
put2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("上海")); ArrayList<Put> puts = new ArrayList<>();
puts.add(put);
puts.add(put2); // 插进去
table.put(puts); table.close();
conn.close(); }
/**
* 循环插入大量数据
* @throws Exception
*/
@Test
public void testManyPuts() throws Exception{ Table table = conn.getTable(TableName.valueOf("user_info"));
ArrayList<Put> puts = new ArrayList<>(); for(int i=0;i<100000;i++){
Put put = new Put(Bytes.toBytes(""+i));
put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("username"), Bytes.toBytes("张三"+i));
put.addColumn(Bytes.toBytes("base_info"), Bytes.toBytes("age"), Bytes.toBytes((18+i)+""));
put.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr"), Bytes.toBytes("北京")); puts.add(put);
} table.put(puts); }

4.2.4.2、删除数据

对称结构,插入的时候需要Put对象

删除的时候,需要Delete对象

    /**
* 删
* @throws Exception
*/
@Test
public void testDelete() throws Exception{
Table table = conn.getTable(TableName.valueOf("user_info")); // 构造一个对象封装要删除的数据信息
     // 全部删除
Delete delete1 = new Delete(Bytes.toBytes("001"));

     // 删除指定的key
Delete delete2 = new Delete(Bytes.toBytes("002"));
     // qualifier为用户意义上的key,hbase中 family+qualifier 为一个key
delete2.addColumn(Bytes.toBytes("extra_info"), Bytes.toBytes("addr")); ArrayList<Delete> dels = new ArrayList<>();
dels.add(delete1);
dels.add(delete2); table.delete(dels); table.close();
conn.close();
}

Hbase—学习笔记(一)

4.2.4.3、修改数据

  使用put来覆盖

4.2.4.5、查看数据

qualifier为用户意义上的key,hbase中 family+qualifier 为一个key

对称结构,插入的时候需要Put对象

删除的时候,需要Delete对象

查看单个行键数据,需要Get对象

4.2.4.5.1、取出单行数据

Table.get(Get)

可以取出该行特定 familyName:key 的 value

也可以遍历该行全部的value

/**
* 查
* @throws Exception
*/
@Test
public void testGet() throws Exception{ Table table = conn.getTable(TableName.valueOf("user_info")); // Get对象 指定行健
     Get get = new Get("002".getBytes());

// 行健为002的全部数据
Result result = table.get(get); // 从结果中取用户指定的某个key的value
byte[] value = result.getValue("base_info".getBytes(), "age".getBytes());
System.out.println(new String(value)); System.out.println("-------------------------"); // 遍历整行结果中的所有kv单元格
     // 类似迭代器
CellScanner cellScanner = result.cellScanner();
while(cellScanner.advance()){
Cell cell = cellScanner.current(); byte[] rowArray = cell.getRowArray(); //本kv所属的行键的字节数组
byte[] familyArray = cell.getFamilyArray(); //列族名的字节数组
byte[] qualifierArray = cell.getQualifierArray(); //列名的字节数据
byte[] valueArray = cell.getValueArray(); // value的字节数组

       // Hbase不仅仅是存储用户数据,同时还会存储很多附加的信息,以上get方法直接将用户数据和附加数据一起返回,若想获取用户信息,需要指定其实偏移量和数据长度 
System.out.println("行键: "+new String(rowArray,cell.getRowOffset(),cell.getRowLength()));
System.out.println("列族名: "+new String(familyArray,cell.getFamilyOffset(),cell.getFamilyLength()));
System.out.println("列名: "+new String(qualifierArray,cell.getQualifierOffset(),cell.getQualifierLength()));
System.out.println("value: "+new String(valueArray,cell.getValueOffset(),cell.getValueLength())); } table.close();
conn.close(); }

Hbase—学习笔记(一)

Hbase—学习笔记(一)

4.2.4.5.2、批量取出数据

取出多个行健范围的数据,需要Scan对象

Table.get(Get)只能取出一个行健范围的数据;

如何按照行健范围取出数据?

table.getScanner(scan)

拿到一个扫描器

/**
* 按行键范围查询数据
* @throws Exception
*/
@Test
public void testScan() throws Exception{ Table table = conn.getTable(TableName.valueOf("user_info")); // 包含起始行键,不包含结束行键,但是如果真的想查询出末尾的那个行键,那么,可以在末尾行键上拼接一个不可见的字节(\000
     // Scan scan = new Scan("10".getBytes(), "10000".getBytes());
        Scan scan = new Scan("10".getBytes(), "10000\001".getBytes());

        ResultScanner scanner = table.getScanner(scan);

        Iterator<Result> iterator = scanner.iterator();

        while(iterator.hasNext()){
// 拿到一行数据
Result result = iterator.next();
// 遍历整行结果中的所有kv单元格
CellScanner cellScanner = result.cellScanner();
while(cellScanner.advance()){
Cell cell = cellScanner.current(); byte[] rowArray = cell.getRowArray(); //本kv所属的行键的字节数组
byte[] familyArray = cell.getFamilyArray(); //列族名的字节数组
byte[] qualifierArray = cell.getQualifierArray(); //列名的字节数据
byte[] valueArray = cell.getValueArray(); // value的字节数组 System.out.println("行键: "+new String(rowArray,cell.getRowOffset(),cell.getRowLength()));
System.out.println("列族名: "+new String(familyArray,cell.getFamilyOffset(),cell.getFamilyLength()));
System.out.println("列名: "+new String(qualifierArray,cell.getQualifierOffset(),cell.getQualifierLength()));
System.out.println("value: "+new String(valueArray,cell.getValueOffset(),cell.getValueLength()));
}
System.out.println("----------------------");
}
}

范围查询的细节

道理:

在真正的结尾行健后面,拼接一个数字0的字节

\000是一个字节,全是0

\表示转移,此时后面的0不是数字0,不是字符0

Hbase—学习笔记(一)

@Test
public void test(){
String a = "000";
String b = "000\0"; System.out.println(a);
System.out.println(b); byte[] bytes = a.getBytes();
byte[] bytes2 = b.getBytes(); System.out.println("");
}

结果

000
000

Hbase—学习笔记(一)

5、 Hbase重要特性--排序特性(行键)

插入到hbase中去的数据,hbase会自动排序存储:

排序规则:  首先看行键,然后看列族名,然后看列(key)名; 按字典顺序

Hbase的这个特性跟查询效率有极大的关系

比如:一张用来存储用户信息的表,有名字,户籍,年龄,职业....等信息

然后,在业务系统中经常需要:

  查询某个省的所有用户

  经常需要查询某个省的指定姓的所有用户

思路:如果能将相同省的用户在hbase的存储文件中连续存储,并且能将相同省中相同姓的用户连续存储,那么,上述两个查询需求的效率就会提高!!!

做法:将查询条件拼到rowkey内

6、数据类型

Hbase中只有一种数据类型:二进制数组

Hbase内部没有对放入的数据维护类型。

这就要求,将来往Hbase里插入数据是,需要将数据转换成二进制数组,去除数据后还要进行解析。

无论是表名,列族名,key,还是value都是二进制数组。

在命令行客户端是以字符串形式来显示的二进制数组数据。

7、练习

{
"events": "1473367236143\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000027\u0001\n1473367261933\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000028\u0001\n1473367280349\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000029\u0001\n1473367331326\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000030\u0001\n1473367353310\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000031\u0001\n1473367387087\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000032\u0001\n1473367402167\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000033\u0001\n1473367451994\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000034\u0001\n1473367474316\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000035\u0001\n1473367564181\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000036\u0001\n1473367589527\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000037\u0001\n1473367610310\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000038\u0001\n1473367624647\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000039\u0001\n1473368004298\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000040\u0001\n1473368017851\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000041\u0001\n1473369599067\u00010\u0001AppLaunch\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000042\u0001\n1473369622274\u00010\u0001connectByQRCode\u0001\u00010\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u0001\u00011609072239570000043\u0001\n",
"header": {
"cid_sn": "1501004207EE98AA",
"mobile_data_type": "",
"os_ver": "22",
"mac": "1c:xx:xx:xx:xx:xx",
"resolution": "1080x1920",
"commit_time": "1473396818952",
"sdk_ver": "103",
"device_id_type": "mac",
"city": "江门市",
"android_id": "86783xx:xx:xx:xx:xx",
"device_model": "HUAWEI VNS-AL00",
"carrier": "中国xx",
"promotion_channel": "1",
"app_ver_name": "1.4",
"imei": "8678300xx:xx:xx:xx:xx",
"app_ver_code": "401xx:xx:xx:xx:xx",
"pid": "pid",
"net_type": "3",
"device_id": "m.1c:xx:xx:xx:xx:xx",
"app_device_id": "m.1c:xx:xx:xx:xx:xx",
"release_channel": "1009",
"country": "CN",
"time_zone": "28800000",
"os_name": "android",
"manufacture": "OPPO",
"commit_id": "fde7ee2e48494b24bf3599771d7c2a78",
"app_token": "XIAONIU_A",
"account": "none",
"app_id": "com.appid.xiaoniu",
"build_num": "YVF6R163xx:xx:xx:xx:xx",
"language": "zh"
}
}

1/假如公司有一个app,每日会在日志服务器上生成大量的日志(在给的样本数据中)
2/公司有一个需求,经常需要在网页上查看某一段时间范围内的日志数据

3/实现思路:
a、每天的日志数据要入库(读文件、解析、插入hbase)
行键:2017-09-17-10-device_id-commit_time .....
分两个列族:events数据列族; headers数据列族

b、开发一个web系统(页面-表单(填查询条件:日期范围; 日期范围+device_id); 页面--展现日志数据表格)
dao层有两种方式: 方式一,继承hbase的客户端jar包,直接在web项目中查询hbase数据
方式二,自己封装一个查询hbase的后台服务,web项目中就去请求你自己的服务


===================未完(下个笔记)==================