hbase使用

1. 简介

HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库。Hbase面向列存储,,基于hadoop的数据库,提供一个十亿级行*百万级列级别的表存储,对表中的数据提供实时的随机读写操作!

1.1 优点

  1. 海量存储:HBase适合存储PB级别的海量数据,在PB级别的数据以及采用廉价PC存储的情况下,能在几十到百毫秒内返回数据。这与HBase的极易扩展性息息相关。正式因为HBase良好的扩展性,才为海量数据的存储提供了便利。
  2. 列式存储:这里的列式存储其实说的是列族存储,HBase是根据列族来存储数据的。列族下面可以有非常多的列,列族在创建表的时候就必须指定。对列的定义很灵活
  3. 扩展性强:主要体现在两个方面,一个是基于上层处理能力(RegionServer)的扩展,一个是基于存储的扩展(HDFS)。通过横向添加RegionSever的机器,进行水平扩展,提升HBase上层的处理能力,提升Hbsae服务更多Region的能力。
  4. 高并发:在并发的情况下,HBase的单个IO延迟很低。能获得高并发、低延迟的服务。
  5. 稀疏:稀疏主要是针对HBase列的灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的。

1.2 缺点

  1. 架构设计复杂:使用HDFS作为分布式存储,因此只是存储少量数据,它也不会很快。在大数据量时,它慢的不会很明显!
  2. 不支持表的关联操作,因此数据分析是HBase的弱项。常见的 group by或order by只能通过编写MapReduce来实现!

总结:适合单表超千万,上亿,且高并发的场景,不适合数据分析,比如做报表及数据量规模不大,对实时性要求高!

2. 物理存储结构

每插入一行数据,其实都是在向hdfs中写一行数据,数据包括行键,列族,列名,时间戳,该行记录的状态,值等。

HBase支持随机写,虽然其构建于hdfs上(hdfs不允许随机写,只允许追加写),但是其通过特殊的方式实现:当更新一条操作时,其实是将原来的数据进行标记为删除,再新增一条数据,并且会有一个时间戳,也称为版本标识,只允许客户端查询时返回时间戳最新的数据!

2.1 名词解释

  1. NameSpace:命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表。HBase两个自带的命名空间,分别是hbase和default,hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。
  2. table:类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,数据属性,比如超时时间(TTL),压缩算法(COMPRESSION)等,都在列族的定义中定义,不需要声明具体的列。这意味着,往HBase写入数据时,字段可以动态按需指定。因此,和关系型数据库相比,HBase能够轻松应对字段变更的场景。
  3. row:HBase表中的每行数据都由一个RowKey和多个Column(列)组成。一个行包含了多个列,这些列通过列族来分类,行中的数据所属列族只能从该表所定义的列族中选取,不能定义这个表中不存在的列族
  4. RowKey: Rowkey由用户指定的一串不重复的字符串定义,是一行的唯一标识!数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,如果使用了之前已经定义的RowKey,那么会将之前的数据更新掉
  5. Column Family:列族是多个列的集合。一个列族可以动态地灵活定义多个列。表的相关属性大部分都定义在列族上,同一个表里的不同列族可以有完全不同的属性配置,但是同一个列族内的所有列都会有相同的属性。列族存在的意义是HBase会把相同列族的列尽量放在同一台机器上,所以说,如果想让某几个列被放到一起,你就给他们定义相同的列族。

  6. Colum:Hbase中的列是可以随意定义的,一个行中的列不限名字、不限数量,只限定列族。因此列必须依赖于列族存在!列的名称前必须带着其所属的列族!例如info:name,info:age。创建表的时候并不需要指定列!列只有在你插入第一条数据的时候才会生成。其他行有没有当前行相同的列是不确定,只有在扫描数据的时候才能得知

  7. TimeStamp:用于标识数据的不同版本(version)。时间戳默认由系统指定,也可以由用户显式指定。在读取单元格的数据时,版本号可以省略,如果不指定,Hbase默认会获取最后一个版本的数据返回!

  8. Cell:一个列中可以存储多个版本的数据。而每个版本就称为一个单元格(Cell)。Cell中的数据是没有类型的,全部是字节码形式存贮。

  9. Region:由一个表的若干行组成!在Region中行的排序按照行键(rowkey)字典排序。其不能跨RegionSever,且当数据量大的时候,HBase会拆分Region,Region由RegionServer进程管理。HBase在进行负载均衡的时候,一个Region有可能会从当前RegionServer移动到其他RegionServer上。Region就是存储真实数据的地方,所以基于HDFS

2.2 架构中的角色

Region Server :  是一个服务,负责多个Region的管理。其实现类为HRegionServer,主要作用:

对于数据的操作:get, put, delete;对于Region的操作:splitRegion、compactRegion。客户端从ZooKeeper获取RegionServer的地址,从而调用相应的服务,获取数据。

Master: Master是所有Region Server的管理者,其实现类为HMaster,主要作用如下:

1. 对于表的操作:create, delete, alter,这些操作可能需要跨多个ReginServer,因此需要Master来进行协调!

2. 对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,负载均衡和故障转移。

3.即使Master进程宕机,集群依然可以执行数据的读写,只是不能进行表的创建和修改等操作!

Zookeeper:RegionServer非常依赖ZooKeeper服务,ZooKeeper管理了HBase所有RegionServer的信息,包括具体的数据段存放在哪个RegionServer上。

客户端每次与HBase连接,其实都是先与ZooKeeper通信,查询出哪个RegionServer可以连接。Zookeeper中记录了读取数据所需要的元数据表hbase:meata,因此关闭Zookeeper后,客户端是无法实现读操作的!HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。

hdfs: HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高可用的支持

3. hbase的安装

3.1 修改配置

3.1.1 修改hbase-env.sh

配置位于/HBASE_HOME/conf/hbase-env.sh

#放开注释,并设置为false,true代表使用hbase自带的zk
export HBASE_MANAGES_ZK=false

3.1.2 修改hbase-site.xml

<configuration>
 <!-- 启动分布式模式 -->
 <property>
    <name>hbase.cluster.distributed</name>
    <value>true</value>
  </property>

  <!-- hbase存储自身临时数据的地方,默认/tmp,会在每次系统重启时清除数据,为了保证数据不丢失,设置为新增目录 -->
  <property>
    <name>hbase.tmp.dir</name>
    <value>/usr/local/workspace/hbase-2.4.5/tmp</value>
  </property>

  <!-- 不是很明白,反正设置为false -->
  <property>
    <name>hbase.unsafe.stream.capability.enforce</name>
    <value>false</value>
  </property>

  <!-- region server的共享目录,持久化hbase数据 -->
  <property>
    <name>hbase.rootdir</name>
    <value>hdfs://hadoop101:9000/HBase</value>
  </property>

  <!-- Zookeeper集群的地址列表,用逗号分割 -->
  <property>
    <name>hbase.zookeeper.quorum</name>
    <value>hadoop102:2181,hadoop103:2181,hadoop101:2181</value>
  </property>

  <!-- Zookeeper存放数据的位置,与zk的zoo.cfg配置文件中的dataDir属性值一致即可 -->
  <property>
    <name>hbase.zookeeper.property.dataDir</name>
    <value>/usr/local/workspace/zookeeper/zkData</value>
  </property>
</configuration>

3.1.3 修改regionservers配置文件

hadoop101
hadoop102
hadoop103

配置hbase集群机器主机名

3.2 添加环境变量

3.3 启动

3.3.1 启动zookeeper

#进入zookeeper安装目录下的bn目录,执行以下命令,3台机器均需启动
zkServer.sh start

3.3.2 启动hadoop

start-all.sh

3.3.3 启动hbase

start-hbase.sh

启动后,通过访问http://hadoop101:16010/或使用jps命令查看判断hbase是否启动成功

端口说明:  

  1. 16000是master进程的RPC端口
  2. 16010是master进程的http端口
  3. 16020是RegionServer进程的RPC端口
  4. 16030是RegionServer进程的http端口

4. 命令

4.1 基本命令

# 开启一个hbase shell来操作hbase
hbase shell
开启hbase shell后可使用以下命令来操作hbase
#查看hbase集群状态
status

说明:在hbase shell中不要敲 ;,如果敲了;,需要敲两个单引号结束

4.2 表的操作

  1. list: 查看所有的表,list后可以使用*等通配符来进行表的过滤!
  2. create:创建表,需要指定表名和列族名,而且至少需要指定一个列族
    格式:create '表名', { NAME => '列族名1', 属性名 => 属性值}, {NAME => '列族名2', 属性名 => 属性值}, …  如果你只需要创建列族,而不需要定义列族属性,那么可以采用快捷写法:create'表名','列族名1' ,'列族名2', …
  3. desc:查看某张表
  4. disable:禁止表,可以防止在对表做一些维护时,客户端依然可以持续写入数据到表。一般在删除表前,必须停用表。在对表中的列族进行修改时,也需要停用表。
  5. drop:删除表
  6. truncate:清空表中的数据

4.3 数据操作

  1. scan:可以按照rowkey的字典顺序来遍历指定的表的数据。
    scan '表名':默认当前表的所有列族。
    scan '表名',{COLUMNS=> ['列族:列名'],…} : 遍历表的指定列
    scan '表名', { STARTROW => '起始行键', ENDROW => '结束行键' }:指定rowkey范围。如果不指定,则会从表的开头一直显示到表的结尾。区间为左闭右开。
    scan '表名', { LIMIT => 行数量}: 指定返回的行的数量
    scan '表名', {VERSIONS => 版本数}:返回cell的多个版本
    scan '表名', { TIMERANGE => [最小时间戳, 最大时间戳]}:指定时间戳范围, 注意:此区间是一个左闭右开的区间,因此返回的结果包含最小时间戳的记录,但是不包含最大时间戳记录
    scan '表名', { RAW => true, VERSIONS => 版本数}
    scan '表名', { FILTER => "过滤器"} and|or { FILTER => "过滤器"}: 使用过滤器扫描
    
    示例如下:
    scan 'student'
    scan 'student',{STARTROW => '1001', STOPROW  => '1001'}
    scan 'student',{STARTROW => '1001'}

    Scan时可以设置是否开启Raw模式,开启Raw模式会返回包括已添加删除标记但是未实际删除的数据。在HBase被删除掉的记录并不会立即从磁盘上清除,而是先被打上墓碑标记,然后等待下次major compaction的时候再被删除掉。注意RAW参数必须和VERSIONS一起使用,但是不能和COLUMNS参数一起使用。

  2. put:可以新增记录还可以为记录设置属性

    put '表名', '行键', '列名', '值'
    put '表名', '行键', '列名', '值',时间戳
    put '表名', '行键', '列名', '值', { '属性名' => '属性值'}
    put '表名', '行键', '列名', '值',时间戳, { '属性名' =>'属性值'}
    
    示例如下:student是表明,info为列族名
    put 'student','1001','info:name','Nick'
    put 'student','1001','info:sex','male'
  3. get:和scan查询类似,不过get只能查询出最新的一条记录
    get 'student','1001'
    get 'student','1001','info:name'
  4. delete

    #删除某rowkey的全部数据:
    deleteall 'student','1001'
    #删除某rowkey的某一列数据
    delete 'student','1002','info:sex'

5. hbase的表现

1.默认有两张系统表
        hbase:meta:  保存的是用户的表和region的对应信息
        hbase:namespace:  保存的是用户自己创建的namespace的信息
2.hbase中的对象的表现形式
        库以目录的形式存放在 /HBase/data中
        表是以子目录的形式存在在 /HBase/data/库名 中
        region也是以子目录的形式存在  /HBase/data/库名/表名 中
        列族也是以子目录的形式存在   /HBase/data/库名/表名/region 中
        数据以文件的形式存放在   /HBase/data/库名/表名/region/列族 目录中

 6. RegionServer 架构

  1.  StoreFile:保存实际数据的物理文件,StoreFile以Hfile的形式存储在HDFS上。每个Store会有一个或多个StoreFile(HFile),数据在每个StoreFile中都是有序的。
  2. MemStore:写缓存,由于HFile中的数据要求是有序的,所以数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile

  3. WAL:由于数据要经MemStore排序后才能刷写到HFile,但把数据保存在内存中会有很高的概率导致数据丢失,为了解决这个问题,数据会先经过WAL处理,然后再写入MemStore中。所以在系统出现故障的时候,数据可以通过WAL恢复。一个RegionServer上的所有Region共享一个WAL实例

  4. BlockCache:读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询

7. 写流程

  1. Client先访问zookeeper的/hbase/meta-region-server,获取hbase:meta表位于哪个Region Server。
  2. 访问对应的Region Server,获取hbase:meta表,根据写请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
  3. 与目标Region Server进行通讯
  4. 将数据顺序写入(追加)到WAL
  5. 将数据写入对应的MemStore,数据会在MemStore进行排序
  6. 向客户端发送ack
  7. 等达到MemStore的刷写时机后,将数据刷写到HFile。

代码所体现的写流程:

  1. 尝试获取尽可能多的锁,至少得获取一个(为了防止多台主机同时写入数据)
  2. 更新时间,在插入记录时,timestamp是可选的,一旦不提供,使用服务器最近的时间戳来作为
  3. 构建WAL对象
  4. 将最新的编辑操作添加到WAL对象的buffer(WAL对象内存中的一块区域)中,buffer中新添加的数据暂时先不同步到wal的磁盘日志文件中!
  5. 获取最新的MVCC(multi-version concurency control)号(相当于数据库的乐观锁,有一个版本号)
  6. 将数据写入到memstore,(当前我们的数据只是在WAL对象的buffer中,还没有sync到磁盘文件) 此时就把数据写入到memstore,是完全没问题的!因为我们还没有滚动mvcc版本号,(只有将WAL中的buffer中的数据同步到磁盘文件后,MVCC号才会滚动!在MVCC未滚动时,向memstore中写的数据,scanner是查不到的!)
  7. 把行锁释放,将WALbuffer中的数据sync到磁盘
  8. 滚动mvcc版本号,滚动成功之后,scan和get操作就可以查询到此条数据
  9. 如果wal buffer中的数据同步到磁盘失败,回滚已经写入到memstore中的cell,同时保持MVCC不变

8. 读流程

  1. Client先访问zookeeper的/hbase/meta-region-server,获取hbase:meta表位于哪个Region Server 
  2. 访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。

  3. 与目标Region Server进行通讯

  4. 分别在Block Cache(读缓存),MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)

  5. 将查询到的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache

  6. 将合并后的最终结果返回给客户端

8.1 读取数据具体流程

1. 读取的数据存储在列族(store)中!列族在HDFS上就是一个目录,这个目录下存储了很多文件(storefile),数据如果是刚写入到store中,还没有刷写到磁盘,当前数据就存储在memstore中(有可能这个列的历史版本的数据已经刷写到磁盘存在storefile中),在扫描时,需要既扫memstore,又扫磁盘上的storefile,扫描出当前列的所有版本的数据,从这些数据中挑选出timestamp最大的返回!
2. 如果扫描历史版本的数据(即扫storefile),那么会发送磁盘IO,效率低,因此可以把扫描到的数据 所在的块(block)缓存到内存中,在内存中保存缓存块的区域,称为blockcache!
在以后的查询中,如果查询的数据在blockcache中有,那么就不需要再扫描storefile了!如果没有,再扫描storefile,将数据所在的block缓存到blockcache!
3. Blockcache是RegionServer中的读缓存,默认大小为当前RS所在堆缓存的40%,有LRU的回收策略!
4. block不是HDFS上中的block,是HFile中的block(默认64k)!

8.2 VERSION

每个不同时间戳的cell就是一个版本,时间戳就是版本.

可以设置列族的VERSIONS属性,当执行flush操作时,put的记录会根据时间戳选择最新的VERSIONS个版本的数据flush到磁盘中!(列族中version属性表示每列保留多少个版本数量,历史版本太多会占大量空间)

9 MemStore Flush

MemStore存在的意义是在写入HDFS前,将其中的数据整理有序

刷写时间:

1. 当某个memstore的大小达到了hbase.hregion.memstore.flush.size(默认值128M,其所在region的所有memstore都会刷写。(为什么所有的memstore都会刷写?因为同一个region中的memstore会涉及到所有的列族,而行数据是由列族组成的,如果只刷写一个memstore,会导致行数据不统一)

2. 当region server中memstore的总大小达到设置的最大内存(jvm heapsize)时

3. 到达自动刷写的时间,自动刷新的时间间隔由该属性进行配置

hbase.regionserver.optionalcacheflushinterval(默认1小时)

10 StoreFile Compaction

compaction(合并小文件)意义:

  1. 每次对数据进行增删改时,都会新增一条数据,进而由memstore刷写到磁盘,会导致storeFile小文件越来越多,占用HDFS的存储能力(HDFS存储量由内存决定,每个小文件都会占内存)
  2. 清理已被废弃的数据

HBase每间隔一段时间都会进行一次合并(Compaction),合并的对象为HFile文件。合并分为两种minor compaction和major compaction。调用方式有调用API、使用hbase shell命令、系统到达一定条件后自动执行

10.1 minor合并(minor compaction)

将多个小文件(通过参数配置决定是否满足合并的条件)重写为数量较少的大文件,减少存储文件数量(多路归并),因为hfile的每个文件都是经过归类的,所以合并速度很快,主要受磁盘IO性能影响,经过测试0.94以后的版本,minor合并也会忽略做了删除标记的数据,即物理删除已废弃的数据

10.2 major合并(major compaction)

将一个region中的一个列簇的若干个hfile重写为一个新的hfile。而且major合并能扫描所有的键/值对,顺序重写全部数据,重写过程中会忽略过做了删除标记的数据(超过版本号限制、超过生存时间TTL、客户端API移除等数据)。默认一周切一次,生产最好关闭此配置,如果切分时存在大量的读写操作会导致响应时间变慢

11. Region Split

默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server。

可以手动切分也可由配置指定切分条件,自动切分,切分时不同版本采用不同的切分策略, 之后也可能会出现新的切分策略。

12 API

12.1 准备工作

12.1.1 引入pom

<dependency>
	<groupId>org.apache.hbase</groupId>
	<artifactId>hbase-server</artifactId>
	<version>2.4.5</version>
</dependency>

<dependency>
	<groupId>org.apache.hbase</groupId>
	<artifactId>hbase-client</artifactId>
	<version>2.4.5</version>
</dependency>

12.1.2 添加配置文件到resource目录

由于hbase需要创建Connection对象连接hbase集群,所以需要设置配置参数供Connection对象使用,在resource目录下创建hbase-site.xml文件,内容如下

<configuration>
	<property>     
		<name>hbase.rootdir</name>     
		<value>hdfs://hadoop101:9000/HBase</value>   
	</property>

	<property>   
		<name>hbase.cluster.distributed</name>
		<value>true</value>
	</property>

	<property>    
		<name>hbase.zookeeper.quorum</name>
	     <value>hadoop102:2181,hadoop103:2181,hadoop101:2181</value>
	</property>

	<property>   
		<name>hbase.zookeeper.property.dataDir</name>
	     <value>/opt/module/zookeeper-3.4.10/datas</value>
	</property>
</configuration>

12.2 操作Connection对象

Connection代表客户端和集群的一个连接!这个连接包含对master的连接,和zk的连接!可以使用ConnectionFactory来创建,有以下几点说明:

  1. Connection的创建是重量级的,因此建议一个应用只创建一个Connection对象
  2. Connection是线程安全的,可以在多个线程中共享同一个Connection实例
  3. Connection的生命周期由用户自己控制
  4. 从Connection中获取Table和Admin对象的实例!Table和Admin对象的创建是轻量级,且不是线程安全的,因此不建议池化或缓存Table和Admin对象的实例,每个线程有自己的Table和Admin对象的实例!
/*
 * 1.创建和关闭Connection对象
 * 2.在创建Connection对象时需要传入Configuration对象,那么如何在HBase中创建一个Configuration对象
 * 		可以使用HBaseConfiguration.create(),返回的Configuration,既包含hadoop8个配置文件的参数,又包含hbase-default.xml和hbase-site.xml中所有的参数配置!
 * 3. ConnectionFactory.createConnection()创建出来的Connection对象,Configuration就是使用HBaseConfiguration.create()方法创建出来的
 */
public class ConnectionUtil {
	//创建一个Connection对象
	public static Connection getConn() throws IOException {
		return ConnectionFactory.createConnection();
	}
	
	public static void close(Connection conn) throws IOException {
		if (conn !=null) {
			conn.close();
		}
	}
}

12.3 操作Namespace对象

/*
 * 1. 创建/删除/查询/判断是否存在   名称空间
 * 		hbase shell :  开启一个客户端对象
 * 		hbase shell : create_namespace 库名
 * 2. Admin :  提供对hbase管理的一些api,例如创建,删除,查询表等!
*       可以使用Connection.getAdmin()来获取Admin的一个实例,使用完成后,调用close关闭!
 * 3. NamespaceDescriptor: 用来代表和定义名称空间
 */
public class NameSpaceUtil {
	
	private static Logger logger=LoggerFactory.getLogger(NameSpaceUtil.class);
	
	//查询所有的名称空间
	public static List<String> listNameSpace(Connection conn) throws IOException{
		List<String> nss=new ArrayList();
		//提供一个Admin
		Admin admin = conn.getAdmin();
		//查询所有的库
		NamespaceDescriptor[] namespaceDescriptors = admin.listNamespaceDescriptors();
		for (NamespaceDescriptor namespaceDescriptor : namespaceDescriptors) {
			//取出每个库描述中库的名称
			nss.add(namespaceDescriptor.getName());
		}
		//关闭admin
		admin.close();
		return nss;
		
	}
	
	//判断是否库存在
	public static boolean ifNSExists(Connection conn,String nsname) throws IOException {
		//库名校验
		if (StringUtils.isBlank(nsname)) {
			logger.error("请输入正常的库名!");
			return false;
		}
		//提供一个Admin
		Admin admin = conn.getAdmin();
		//根据库名查询对应的NS,如果找不到就抛异常
		try {
			admin.getNamespaceDescriptor(nsname);
			return true;
		} catch (Exception e) {
			return false;
		}finally {
			admin.close();
		}
	}
	
	//创建库
	public static boolean creatNS(Connection conn,String nsname) throws IOException {
		//库名校验
		if (StringUtils.isBlank(nsname)) {
			logger.error("请输入正常的库名!");
			return false;
		}
		//提供一个Admin
		Admin admin = conn.getAdmin();
		//新建库
		try {
			//先创建库的定义或描述
			NamespaceDescriptor descriptor = NamespaceDescriptor.create(nsname).build();
			admin.createNamespace(descriptor);
			return true;
		} catch (Exception e) {
			return false;
		} finally {
			admin.close();
		}
	}
	
	//删除库
		public static boolean deleteNS(Connection conn,String nsname) throws IOException {
			if (StringUtils.isBlank(nsname)) {
				return false;
			}
			
			//提供一个Admin
			Admin admin = conn.getAdmin();
			//只能删除空库,判断当前库是否为empty,不为空无法删除
			//查询当前库下有哪些表
			List<String> tables = getTablesInNameSpace(conn, nsname);
			if (tables.size()==0) {
				admin.deleteNamespace(nsname);
				//关闭admin
				admin.close();
				return true;
			}else {
				//关闭admin
				admin.close();
				logger.error(nsname+"库非空!无法删除!");
				return false;
			}
		}
		
		//查询库下有哪些表
		public static List<String> getTablesInNameSpace(Connection conn,String nsname) throws IOException{
			//库名校验
			if (StringUtils.isBlank(nsname)) {
				logger.error("请输入正常的库名!");
				//在后台提示,库名非法
				return null;
			}
			List<String> tables=new ArrayList();
			//提供一个Admin
			Admin admin = conn.getAdmin();
			//查询当前库所有的表
			HTableDescriptor[] tableDescriptors = admin.listTableDescriptorsByNamespace(nsname);
			for (HTableDescriptor tableDescriptor : tableDescriptors) {
				//取出每个表描述中表的名称
				tables.add(tableDescriptor.getNameAsString());
			}
			//关闭admin
			admin.close();
			return tables;
	    }
}

12.4 表操作

/*
 * 1. 创建表和删除表
 * 2. TableName:  代表表名,调用valueof(String 库名,String 表名),返回表名!如果库名为null,此时使用default作为库名
 * 3. HTableDescriptor: 代表表的细节(描述),包含表中列族的描述!
 */
public class TableUtil {
	
	private static Logger logger=LoggerFactory.getLogger(TableUtil.class);

	//验证表名是否合法并返回
	public static TableName checkTableName(String tableName,String nsname) {
		if (StringUtils.isBlank(tableName)) {
			logger.error("请输入正确的表名!");
			return null;
		}
		return TableName.valueOf(nsname, tableName);
	}
	
	//判断表是否存在
	public static boolean ifTableExists(Connection conn,String tableName,String nsname) throws IOException {
		//校验表名
		TableName tn = checkTableName(tableName, nsname);
		if (tn == null) {
			return false;
		}
		Admin admin = conn.getAdmin();
		//判断表是否存在,需要传入TableName对象
		boolean tableExists = admin.tableExists(tn);
		admin.close();
		return tableExists;
	}
	
	//创建表
	public static boolean createTable(Connection conn,String tableName,String nsname,String...cfs) throws IOException {
		//校验表名
		TableName tn = checkTableName(tableName, nsname);
		if (tn == null) {
			return false;
		}
		//至少需要传入一个列族
		if (cfs.length < 1) {
			logger.error("至少需要指定一个列族!");
			return false;
		}
		
		Admin admin = conn.getAdmin();
		
		//创建表的描述
		HTableDescriptor hTableDescriptor = new HTableDescriptor(tn);
		//讲列族的描述也添加到表的描述中
		for (String cf : cfs) {
			HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(cf);
			//添加列族的设置
			hColumnDescriptor.setMinVersions(3);
			hColumnDescriptor.setMaxVersions(10);
			hTableDescriptor.addFamily(hColumnDescriptor);
		}
		//根据表的描述创建表
		admin.createTable(hTableDescriptor);
		admin.close();
		return true;
	}
	
	//删除表
	public static boolean dropTable(Connection conn,String tableName,String nsname) throws IOException {
		//检查表是否存在
		if (!ifTableExists(conn, tableName, nsname)) {
			return false;
		}
		//校验表名
		TableName tn = checkTableName(tableName, nsname);
		Admin admin = conn.getAdmin();
		//删除之前需要先禁用表
		admin.disableTable(tn);
		//删除表
		admin.deleteTable(tn);
		admin.close();
		return true;
	}
}

12.5 数据的操作

/*
 * 1.数据的增删改查,需要使用的是Table
 * 2.Put: 代表对单行数据的put操作
 * 3.在hbase中,操作的数据都是以byte[]形式存在,需要把常用的数据类型转为byte[]
 * 	 hbase提供了Bytes工具类:
 *   Bytes.toBytes(x): 基本数据类型转byte[],Bytes.toXxx(x): 从byte[]转为Xxx类型!
 * 4. Get: 代表对单行数据的Get操作!
 * 5. Result: scan或get的单行的所有的记录!
 * 6. Cell: 代表一个单元格,hbase提供了CellUtil.clonexxx(Cell),来获取cell中的列族、列名和值属性!
 */
public class DataUtil {
	//先获取到表的table对象
	public static Table getTable(Connection conn,String tableName,String nsname) throws IOException {
		//验证表名是否合法
		TableName tn = TableUtil.checkTableName(tableName, nsname);
		if (tn == null) {
			return null;
		}
		//根据TableName获取对应的Table
		return conn.getTable(tn);
	}
	
	//put 表名,rowkey,列名(列族名:列名),value
	public static void put(Connection conn,String tableName,String nsname,String rowkey,String cf,
			String cq,String value) throws IOException {
		//获取表对象
		Table table = getTable(conn, tableName, nsname);
		if (table==null) {
			return;
		}
		//创建一个Put对象
		Put put = new Put(Bytes.toBytes(rowkey));
		//向put中设置cell的细节信息
		put.addColumn(Bytes.toBytes(cf), Bytes.toBytes(cq), Bytes.toBytes(value));
		//.addColumn(family, qualifier, value)
		table.put(put);
		table.close();
	}
	
	// get 表名   rowkey
	public static void get(Connection conn,String tableName,String nsname,String rowkey) throws IOException {
		//获取表对象
		Table table = getTable(conn, tableName, nsname);
		if (table==null) {
			return ;
		}
		Get get = new Get(Bytes.toBytes(rowkey));
		//设置单行查询的详细信息
		//设置查哪个列
		//get.addColumn(family, qualifier)
		//设置查哪个列族
		//get.addFamily(family)
		//只查某个时间戳的数据
		//get.setTimeStamp(timestamp)
		//设置返回的versions
		//get.setMaxVersions(maxVersions)
		Result result = table.get(get);
		//System.out.println(result);
		parseResult(result);
		table.close();
	}
	
	//遍历result
	public static void parseResult(Result result) {
		if (result != null) {
			Cell[] cells = result.rawCells();
			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)));
			}
		}
	}
}

13 HBase集成Hive

由于HBase不擅长分析数据,而Hive可以,所以可以将他们结合起来,HBase负责存储,Hive负责分析!

13.1 环境准备

后续可能会在操作Hive的同时对HBase也会产生影响,所以Hive需要持有操作HBase的Jar。

13.1.1 将hbase中的部分jar包赋值到hive中

将hbase中的jar通过软连接赋值到hive中(不用赋值jar包本身,因为会占不少磁盘空间)

ln -s $HBASE_HOME/lib/hbase-common-2.4.5.jar  $HIVE_HOME/lib/hbase-common-2.4.5.jar
ln -s $HBASE_HOME/lib/hbase-server-2.4.5.jar $HIVE_HOME/lib/hbase-server-2.4.5.jar
ln -s $HBASE_HOME/lib/hbase-client-2.4.5.jar $HIVE_HOME/lib/hbase-client-2.4.5.jar
ln -s $HBASE_HOME/lib/hbase-protocol-2.4.5.jar $HIVE_HOME/lib/hbase-protocol-2.4.5.jar
ln -s $HBASE_HOME/lib/hbase-it-2.4.5.jar $HIVE_HOME/lib/hbase-it-2.4.5.jar
ln -s $HBASE_HOME/lib/hbase-hbtop-2.4.5.jar $HIVE_HOME/lib/hbase-hbtop-2.4.5.jar
ln -s $HBASE_HOME/lib/hbase-hadoop2-compat-2.4.5.jar $HIVE_HOME/lib/hbase-hadoop2-compat-2.4.5.jar
ln -s $HBASE_HOME/lib/hbase-hadoop-compat-2.4.5.jar $HIVE_HOME/lib/hbase-hadoop-compat-2.4.5.jar

13.1.2 在hive-site.xml中修改zookeeper的属性

在${HIVE_HOME}/conf/hive-site.xml中添加以下配置

<property>
  <name>hive.zookeeper.quorum</name>
  <value>hadoop102,hadoop103,hadoop101</value>
</property>
<property>
  <name>hive.zookeeper.client.port</name>
  <value>2181</value>
</property>

13.2 hbase创建数据

13.3 hive创建表来加载hbase数据 

create external table hbase_t3(
   id int,
   age int,
   sex string,
   name string
)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,info:age,info:sex,info:name")
TBLPROPERTIES ("hbase.table.name" = "student");

查询结果即可,注意点如下:

1. 表一定要外部表,不能是管理表,因为数据并不来自于hive,而是由hbase管理

2. 在写hbase.columns.mapping值时,顺序一定要与hive定义的表字段顺序相同,(:key表示hbase的rowkey,写第一个表示将hbase中student表的rowkey作为id,位置随意,但是要与字段对应,info表示列族)

3.hbase.table.name表示hbase中的表明,不能自定义

4. STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'是固定写法,表示hive使用hbase的数据格式读取数据

13.4 当hbase还不存在数据而使用hive来插入数据

数据还尚未插入到hbase,可以在hive中建表,建表后,在hive中执行数据的导入,将数据导入到hbase,再分析。 表必须是managed non-native table!

先在hive上创建一张表,并定义hbase的表

CREATE TABLE `hbase_emp`(
  `empno` int, 
  `ename` string, 
  `job` string, 
  `mgr` int, 
  `hiredate` string, 
  `sal` double, 
  `comm` double, 
  `deptno` int)
STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":key,info:ename,info:job,info:mgr,
info:hiredate,info:sal,info:comm,info:deptno")
TBLPROPERTIES ("hbase.table.name" = "emp");
insert into table hbase_emp select * from emp

13.5 注意事项

  1. 在建表时,hive中的表字段的类型要和hbase中表列的类型一致,以避免类型转换失败造成数据丢失
  2. row format 的作用是指定表在读取数据时,使用什么分隔符来切割数据,只有正确的分隔符,才能正确切分字段.但是hbase中的数据并不能通过常规字符串来分割,必须使用STORED BY 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'

14. 理论知识

1.Storage Handlers:是一个扩展模块,帮助hive分析不在hdfs存储的数据!例如数据存储在hbase上,可以使用hive提供的对hbase的Storage Handlers(HBaseStorageHandler),来读写hbase中的数据!
2.native table(本地表):hive无需通过Storage Handlers就能访问的表。例如之前创建的表,都是native table! 创建native表:[ROW FORMAT row_format] [STORED AS file_format]
file_format: ORC|TEXTFILE|SEQUNCEFILE|PARQUE,都是hive中支持的文件格式,由hive负责数据的读写!
3.non-native table(非本地表):hive必须通过Storage Handlers才能访问的表,例如和hbase集成的表!创建non-native表: STORED BY 'storage.handler.class.name' [WITH SERDEPROPERTIES (...)],数据在外部存储,hive通过Storage Handlers来读写数据!
4. SERDE:序列化器和反序列化器,即表中的数据是什么样的格式,就必须使用什么样的SerDe!

  1. 纯文本:row format delimited ,默认使用LazySimpleSerDe
  2. JSON格式:  使用JsonSerde
  3. ORC: 使用读取ORC的SerDe
  4. Paquet: 使用读取PaquetSerDe

例如: 数据中全部是JSON格式
{"name":"songsong","friends":["bingbing","lili"]}
{"name":"songsong1","friends": ["bingbing1" , "lili1"]}

错误写法:

create table testSerde(
name string,
friends array<string>
)
row format delimited fields terminated by ','
collection items terminated by ','
lines terminated by '\n';

如果指定了row format delimited ,此时默认使用LazySimpleSerDe!LazySimpleSerDe只能处理有分隔符的普通文本!现在数据是JSON,格式{},只能用JSONSerDE

create table testSerde2(
name string,
friends array<string>
)
ROW FORMAT SERDE 
'org.apache.hive.hcatalog.data.JsonSerDe' 
STORED AS TEXTFILE

15 优化

15.1 高可用

在HBase中Hmaster负责监控RegionServer的生命周期,均衡RegionServer的负载,如果Hmaster挂掉了,那么整个HBase集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase支持对Hmaster的高可用配置。

配置步骤:在conf目录下创建backup-masters文件,并将需要使用备份的master节点的主机名写在里面

15.2 预分区

通常情况下,每次建表时,默认只有一个region!随着这个region的数据不断增多,region会自动切分!自动切分方式: 将当前region中的所有的rowkey进行排序,排序后取start-key和stopkey中间的rowkey,由这个rowkey一分为二,一分为二后,生成两个region,新增的Region会交给其他的RS负责,目的是为了达到负载均衡,但是通常往往会适得其反!比如切分之后,某一个region成了热点区,导致负载不均衡
为例避免某些热点Region同时分配到同一个RegionServer,可以在建表时,自己提前根据数据的特征规划region!

1.手动设定预分区

 create 'staff1','info','partition1',SPLITS => ['1000','2000','3000','4000']

2. 生成16进制序列预分区

create 'staff2','info','partition2',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}

3.当预分区的分区数过多导致shell命令不好操作时,可以编写一个文件,按照文件中设置的规则预分区,例如,常见一个splits.txt文件

create 'staff3','partition3',SPLITS_FILE => 'splits.txt'

4. 使用JavaAPI创建预分区

//自定义算法,产生一系列Hash散列值存储在二维数组中
byte[][] splitKeys = 某个散列值函数
//创建HBaseAdmin实例
HBaseAdmin hAdmin = new HBaseAdmin(HBaseConfiguration.create());
//创建HTableDescriptor实例
HTableDescriptor tableDesc = new HTableDescriptor(tableName);
//通过HTableDescriptor实例和散列值二维数组创建带有预分区的HBase表
hAdmin.createTable(tableDesc, splitKeys);

15.3 rowkey设计

一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的 ,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。

原则: 
①rowkey作为数据的唯一主键,需要紧密和业务相关,从业务中选择某个代表性的字段作为rowkey
②保证rowkey字段选取时的唯一性,不重复性
③rowkey足够散列,负载均衡
④让有业务关联的rowkey尽量分布到一个region中

rowkey常用的设计方案如下:

1. 生成随机数、hash、散列值.原本rowKey为1001的,SHA1后变成:dd01903921ea24941c26a48f2cec24e0bb0e8cc7,在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的rowKey来Hash后作为每个分区的临界值

2. 字符串反转

3.字符串拼接

15.4 布隆过滤器

这个组件一般是用作过滤,在海量数据中,用非常高的效率和性能,判断一个数据是否在集合中存在,布隆过滤器只能判断一个数据要么一定在集合中不存在,要么在集合中可能存在!布隆过滤器判断数据可能存在,实际扫描后,发现不存在,这种情况有存在的几率

当经常要判断一个元素是否在一个集合中(一般来讲,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间)就可以使用布隆过滤器,其大致工作原理为:初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0,Bloom Filter使用n个相互独立的哈希函数来计算集合中每个元素的hash值,并存储在自己的位数组中,当要查询某个元素时,就将利用n个哈希函数计算该元素在位数组所在的位置,如果发现n个哈希函数所计算的结果中有一个不能在位数组中找到,那么就说明数据一定不存在,否则即使n个哈希函数计算的结果全部都能在位数组中找到,也只能是可能存在,因为不同的数据可能存在相同的hash值,会导致误判,一般来说,数据量再大,误判率也就在30%左右

15.5 hbase中的布隆过滤器

HBase支持两种布隆过滤器:  ROW|ROWCOL

ROW: 布隆过滤器在计算时,使用每行的rowkey作为参数,进行判断,举例:
storefile1: (r1,info:age,20) ,(r2,info:age,20) 
storefile2: (r3,info1:age,20) ,(r4,info1:age,20) 

查询r1时,如果命中,在storefile1中可能有,storefile2中一定没有r1的数据,!

ROWCOL: 布隆过滤器在计算时,使用每行的rowkey和column一起作为参数,进行判断!依然用上面的数据举例:

查询rowkey=r1,只查info:age=20 列时,如果命中,在storefile1中可能有,storefile2中一定没有此数据,

启用布隆过滤器后,会占用额外的内存,hbase的布隆过滤器通常是在blockcache和memstore中!

举例:执行get 't1','r1'

①扫描r1所在region的所有列族的memstore,扫memstore时,先通过布隆过滤器判断r1是否存在,如果不存在,就不扫!可能存在,再扫描!

②扫描Storefile时,如果storefile中,r1所在的block已经缓存在blockcache中,直接扫blockcache在扫描blockcache时,先使用布隆过滤器判断r1是否存在,如果不存在,就不扫!可能存在,再扫描!

16 错误解析

16.1 关闭HBase时无法找到Master:no hbase master found

修改pid文件存放路径

进入conf 目录,找到 hbase-env.sh 进行修改将文件中的对应行修改,大体确定报错原因,系统找不到HBase的pid文件,pid文件里面是HBase的进程号,找不到进程号系统就没有办法去结束这个进程。HBase的pid文件默认存放路径为 /tmp 路径,可以进去看一下有没有和HBase相关的文件。肯定没有,因为很有可能被操作系统删掉了。

# 改成自定义地址
export HBASE_PID_DIR=/var/hbase/pids

16.2 没有自动选择HMATER节点或启动失败即访问页面无响应

解决:重启zookeeper并重启hbase服务,

如果在conf目录下有创建过backup-masters文件,那么直接将此文件删除,因为通过start-hbase.sh启动时会自动选择hmater节点,无需此配置

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巴中第一皇子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值