12.大数据学习之旅——HBase第二天

版权声明:版权归零零天所有 https://blog.csdn.net/qq_39188039/article/details/86356284

HBASE完全分布式安装


实现步骤

  1. 准备三台虚拟机,01作为主节点,02、03作为从节点。(把每台虚拟机防火墙都关掉,配
    置免密码登录,配置每台的主机名和hosts文件。)
  2. 01节点上安装和配置:Hadoop+Hbase+JDK+Zookeeper
  3. 02、03节点上安装和配置:Hbase+JDK+Zookeeper
  4. 修改conf/hbase-env.sh

配置示例:

#修改JAVA_HOME
export JAVA_HOME=xxxx
#修改Zookeeper和Hbase的协调模式,hbase默认使用自带的zookeeper,如果需要使用外
部zookeeper,需要先关闭。
export HBASE_MANAGES_ZK=false
  1. 修改hbase-site.xml,配置开启完全分布式模式

配置示例:

<property>
</property>
<name>hbase.cluster.distributed</name>
<value>true</value>
<property>
</property>
#配置Zookeeper的连接地址与端口号
<name>hbase.zookeeper.quorum</name>
<value>hadoop01:2181,hadoop02:2181,hadoop03:2181</value>
<property>
</property>
  1. 配置region服务器,修改conf/regionservers文件,每个主机名独占一行,hbase启动或关闭
    时会按照该配置顺序启动或关闭主机中的hbase

配置示例:

hadoop01
hadoop02
hadoop03
  1. 将01节点配置好的hbase通过远程复制拷贝到02,03节点上
  2. 启动01,02,03的Zookeeper服务
  3. 启动01节点的Hadoop
  4. 启动01节点的Hbase,进入到hbase安装目录下的bin目录
    执行:sh start-hbase.sh
  5. 查看各节点的java进程是否正确
  6. 通过浏览器访问http://xxxxx:60010来访问web界面,通过web见面管理hbase
  7. 关闭Hmaster,进入到hbase安装目录下的bin目录
    执行:stop-hbase.sh
  8. 关闭regionserver,进入到hbase安装目录下的bin目录
    执行:sh hbase-daemon.sh stop regionserver

注:HBASE配置文件说明
hbase-env.sh配置HBase启动时需要的相关环境变量
hbase-site.xml配置HBase基本配置信息
HBASE启动时默认使用hbase-default.xml中的配置,如果需要可以修改hbase-site.xml文
件,此文件中的配置将会覆盖hbase-default.xml中的配置
修改配置后要重启hbase才会起作用

HBASE API

实现步骤:

  1. 导入开发包将hbase安装包中lib下包导入java项目
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

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.Delete;
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;
import org.junit.Test;

public class TestDemo {
	@Test
	public void createTable() throws Exception{
		//--获取HBase的环境参数对象
		Configuration conf=HBaseConfiguration.create();
		conf.set("hbase.zookeeper.quorum",
				"hadoop01:2181,hadoop02:2181,hadoop03:2181");
		HBaseAdmin admin=new HBaseAdmin(conf);
		
		HTableDescriptor table=
				 new HTableDescriptor(TableName.valueOf("tb2"));
		
		HColumnDescriptor cf1=new HColumnDescriptor("cf1".getBytes());
		HColumnDescriptor cf2=new HColumnDescriptor("cf2".getBytes());
		//--将指定的列族和表绑定
		table.addFamily(cf1);
		table.addFamily(cf2);
		
		//--执行建表
		admin.createTable(table);
		admin.close();
	}
	
	@Test
	public void insertData() throws Exception{
		Configuration conf=HBaseConfiguration.create();
		//--zk的集群地址可以只写一个,通过一个地址找到整个的zk集群
		//--但风险是如果这台zk宕机,则无法连接
		conf.set("hbase.zookeeper.quorum","hadoop01:2181");
		
		//--创建表对象,并指定操作的表名
		HTable table=new HTable(conf,"tb1");
		//--创建行对象,并指定行键
		Put put=new Put("row1".getBytes());
		//--①参:列族名 ②参:列名  ③参:列值
		//--对应的指令:put 'tb1','row1','cf1:name','tom'
		put.add("cf1".getBytes(),"name".getBytes(),"tom".getBytes());
		put.add("cf1".getBytes(),"age".getBytes(),"30".getBytes());
		put.add("cf2".getBytes(),"city".getBytes(),"bj".getBytes());
		
		//--执行插入
		table.put(put);
		table.close();
	}
	@Test
	public void batchInsert() throws Exception{
		Configuration conf=new Configuration();
		conf.set("hbase.zookeeper.quorum","hadoop01:2181");
		
		HTable table=new HTable(conf,"tb1");
		List<Put> puts=new ArrayList<>();
		for(int i=0;i<100;i++){
			Put put=new Put(("row"+i).getBytes());
			put.add("cf1".getBytes(),"num".getBytes(),(i+"").getBytes());
			//--插入一行数据到HBase表
			//--注意这种写法是一行一行插入,性能不高
			//table.put(put);
			
			puts.add(put);
		}
		//--执行批量插入,本例中是100行数据一起插入
		//--在生产环境下,都是批量插入
		table.put(puts);
	}
	@Test
	public void getData() throws Exception{
		Configuration conf=new Configuration();
		conf.set("hbase.zookeeper.quorum","hadoop01:2181");
		
		HTable table=new HTable(conf, "tb1");
		//--通过行键去指定表读取数据
		Get get=new Get("row1".getBytes());
		//--执行查询,把结果集封装到Result对象
		Result result=table.get(get);
		//--根据指定的列族名和列名获取值
		byte[] name=result.getValue("cf1".getBytes(),"name".getBytes());
		byte[] age=result.getValue("cf1".getBytes(),"age".getBytes());
		byte[] city=result.getValue("cf2".getBytes(),"city".getBytes());
		System.out.println(new String(name)+":"+new String(age)+":"+new String(city));
		table.close();
	}
	
	@Test
	public void scanTable() throws Exception{
		Configuration conf=new Configuration();
		conf.set("hbase.zookeeper.quorum","hadoop01:2181");
		
		HTable table=new HTable(conf, "tb1");
		//--创建扫描对象,可以通过此对象扫描整表数据
		Scan scan=new Scan();
		//--将整表数据封装到结果集(包含多行数据)
		//--指定扫描的起始行键和终止行键,(不包含终止的行键)
		//--需要记住
		scan.setStartRow("row10".getBytes());
		scan.setStopRow("row19".getBytes());
		
		ResultScanner rs=table.getScanner(scan);
		//--获取行数据的迭代器
		Iterator<Result> it=rs.iterator();
		while(it.hasNext()){
			//--每迭代一次,就获取一行数据
			Result result=it.next();
			byte[] num=result.getValue("cf1".getBytes(),"num".getBytes());
			System.out.println(new String(num));
		}
		table.close();
		
	}
	@Test
	public void  delete() throws Exception{
		Configuration conf=new Configuration();
		conf.set("hbase.zookeeper.quorum","hadoop01:2181");
		HTable table=new HTable(conf, "tb1");
		
		//--根据指定行键删除
		Delete delete=new Delete("row0".getBytes());
		table.delete(delete);
		//--还可以通过List<Delete> 来实现批量删除
		//--在生产环境下,尽可能实现批量操作
		table.close();
	}
	
	@Test
	public void dropTable() throws Exception{
		Configuration conf=new Configuration();
		conf.set("hbase.zookeeper.quorum","hadoop01:2181");
		HBaseAdmin admin=new HBaseAdmin(conf);
		//--先禁用tb2表
		admin.disableTable("tb2");
		//--再删除表
		admin.deleteTable("tb2");
		
		admin.close();
	}
}


HBASE过滤器 API

import java.io.IOException;
import java.util.Iterator;


import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.client.HTable;
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.filter.BinaryComparator;
import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.PrefixFilter;
import org.apache.hadoop.hbase.filter.RegexStringComparator;
import org.apache.hadoop.hbase.filter.RowFilter;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.junit.Test;

public class FilterDemo {

	@Test
	public void rowFilter() throws Exception{
		Configuration conf=new Configuration();
		conf.set("hbase.zookeeper.quorum","hadoop01:2181");
		
		HTable table=new HTable(conf, "tb1");
		Scan scan=new Scan();
		//--①参:比较规则  有:等于;不等于;大于;大于等于;小于;小于等于
		//--下例匹配的是行键中含3的行键
		//Filter filter=new RowFilter(CompareOp.EQUAL,
		//		     new RegexStringComparator("^.*3.*$"));
		
		//Filter filter=new RowFilter(CompareOp.LESS_OR_EQUAL,
		//		new BinaryComparator("row30".getBytes()));
		
		//--行键前缀过滤器
		//Filter filter=new PrefixFilter("row3".getBytes());
		
		//--列值过滤器
		Filter filter=new SingleColumnValueFilter(
				"cf1".getBytes(),"name".getBytes(), 
				CompareOp.EQUAL, "tom".getBytes());
		
		//--绑定过滤器,使其生效
		scan.setFilter(filter);
		//--执行扫描查询并结合过滤器查询
		ResultScanner rs=table.getScanner(scan);
		Iterator<Result> it=rs.iterator();
		while(it.hasNext()){
			Result result=it.next();
			byte[] num=result.getValue("cf1".getBytes(),"num".getBytes());
			System.out.println(new String(num));
		}
		table.close();
	}
}

HBase物理存储原理

Hbase里的一个Table 在行的方向上分割为多个Hregion。
即HBase中一个表的数据会被划分成很多的HRegion,HRegion可以动态扩展并且HBase保证
HRegion的负载均衡。HRegion实际上是行键排序后的按规则分割的连续的存储空间。
在这里插入图片描述
在这里插入图片描述
总结:
一张Hbase表,可能有多个HRegion,每个HRegion达到一定大小(默认是10GB)
时,进行分裂。

拆分流程图:

Hregion是按大小分割的,每个表一开始只有一个Hregion,随着数据不断插入表,Hregion不
断增大,当增大到一个阀值的时候,Hregion就会等分两个新的Hregion。当table中的行不断
增多,就会有越来越多的Hregion。
按照现在主流硬件的配置,每个HRegion的大小可以是1~20GB。这个大小由
hbase.hregion.max.filesize指定,默认为10GB。HRegion的拆分和转移是由
HBase(HMaster)自动完成的,用户感知不到。

Hregion是 Hbase中分布式存储和负载均衡的最小单元
在这里插入图片描述

HRegion虽然是分布式存储的最小单元,但并不是存储的最小单元。
事实上,HRegion由一个或者多个HS tore组成,每个Hstore保存一个columns family。
每个HStore又由一个memStore和0至多个StoreFile组成。如图:
StoreFile以HFile格式保存在HDFS上。
总结:HRegion是分布式的存储最小单位,StoreFile(Hfile)是存储最小单位。
在这里插入图片描述

Hbase系统架构

Hbase与Hadoop架构图

在这里插入图片描述

HBase架构组成

HBase采用Master/Slave架构搭建集群,它隶属于Hadoop生态系统,由以下类型节点组
成:

  1. HMaster节点
  2. HRegionServer节点
  3. ZooKeeper集群
  4. Hbase的数据存储于HDFS中,因而涉及到HDFS的NameNode、DataNode
    等。RegionServer和DataNode一般会放在相同的Server上实现数据的本地化(避免或减少
    数据在网络中的传输,节省带宽)。

HMaster节点
5. 管理HRegionServer,实现其负载均衡。
6. 管理和分配HRegion,比如在HRegion split时分配新的HRegion;在HRegionServer退出
时迁移其内的HRegion到其他HRegionServer上。
7. 实现DDL操作(Data Definition Language,namespace和table的增删改,column
familiy的增删改等)。
8. 管理namespace和table的元数据(实际存储在HDFS上)。
9. 权限控制(ACL).

HRegionServer节点
10. 存放和管理本地HRegion。
11. 读写HDFS,管理Table中的数据。
12. Client直接通过HRegionServer读写数据(从HMaster中获取元数据,找到RowKey所在的
HRegion/HRegionServer后)。

ZooKeeper集群
13. 存放整个 HBase集群的元数据以及集群的状态信息。以及RS服务器的运行状态
14. 实现HMaster主备节点的failover。
下图更全面展示了Hbase于Hadoop的体系图:
在这里插入图片描述
HBase Client通过RPC方式和HMaster、HRegionServer通信;一个HRegionServer可以存
放1000个HRegion(1000个数字的由来是来自于Google的Bigtable论文);底层Table数据
存储于HDFS中,而HRegion所处理的数据尽量和数据所在的DataNode在一起,实现数据的
本地化;数据本地化并不是总能实现,比如在HRegion移动(如因Split)时,需要等下一次
Compact才能继续回到本地化。

HBASE架构原理详解

整体架构图

在这里插入图片描述
Hregion
在这里插入图片描述
HBase使用RowKey将表水平切割成多个HRegion,从HMaster的角度,每个HRegion都纪录了它
的StartKey和EndKey,由于RowKey是排序的,因而Client可以通过HMaster快速的定位每个
RowKey在哪个HRegion中。HRegion由HMaster分配到相应的HRegionServer中,然后由
HRegionServer负责HRegion的启动和管理,和Client的通信,负责数据的读(使用HDFS)。每个
HRegionServer可以同时管理1000个左右的HRegion(这个数字怎么来的?超过1000个会引起性
能问题?这个1000的数字是从BigTable的论文中来的(5 Implementation节):Each tablet
server manages a set of tablets(typically we have somewhere between ten to a thousand tablets per
tablet server))。

HMaster
在这里插入图片描述

HMaster没有单点故障问题,可以启动多个HMaster,通过ZooKeeper的Master Election机制保证
同时只有一个HMaster处于Active状态,其他的HMaster则处于热备份状态。一般情况下会启动
两个HMaster,非Active的HMaster会定期的和Active HMaster通信以获取其最新状态,从而保证
它是实时更新的,因而如果启动了多个HMaster反而增加了Active HMaster的负担。前文已经介
绍过了HMaster的主要用于HRegion的分配和管理,DDL(Data Definition Language,既Table的新
建、删除、修改等)的实现等,既它主要有两方面的职责:

  1. 协调HRegionServer
    1. 启动时HRegion的分配,以及负载均衡和修复时HRegion的重新分配。
    2. 监控集群中所有HRegionServer的状态(通过Heartbeat和监听ZooKeeper中的状态)。
  2. Admin职能
    1. 创建、删除、修改Table的定义。

ZooKeeper:协调者
在这里插入图片描述
ZooKeeper(根据Google的《The Chubby lock service for loosely coupled distributed
System》)为HBase集群提供协调服务,它管理着HMaster和HRegionServer的状态
(available/alive等),并且会在它们宕机时通知给HMaster,从而HMaster可以实现HMaster之间
的failover(失败恢复),或对宕机的HRegionServer中的HRegion集合的修复(将它们分配给其他
的HRegionServer)。ZooKeeper集群本身使用一致性协议(PAXOS协议,PAXOS算法的思想是:
在分布式的环境下,如何就某个决议达成一致性的算法,PAXOS算法的缺点是存在活锁问
题,ZK是基于PAXOS实现的Fast PAXOS)保证每个节点状态的一致性。

How The Components Work Together

ZooKeeper协调集群所有节点的共享信息,在HMaster和HRegionServer连接到ZooKeeper后创建
Ephemeral( 临时)节点,并使用Heartbeat机制维持这个节点的存活状态,如果某个Ephemeral
节点实效,则HMaster会收到通知,并做相应的处理。
在这里插入图片描述
另外,HMaster通过监听ZooKeeper中的Ephemeral节点(默认:/hbase/rs/*)来监控HRegionServer
的加入和宕机。在第一个HMaster连接到ZooKeeper时会创建Ephemeral节点(默认:/hbasae/master)来表示Active的HMaster,其后加进来的HMaster则监听该Ephemeral节点,
如果当前Active的HMaster宕机,则该节点消失,因而其他HMaster得到通知,而将自身转换成
Active的HMaster,在变为Active的HMaster之前,它会创建在/hbase/back-masters/下创建自己
的Ephemeral节点。

HBase的第一次读写
在HBase 0.96以前,HBase有两个特殊的Table:-ROOT-和.META.(如BigTable中的设计),其中-
ROOT- Table的位置存储在ZooKeeper,它存储了.META. Table的RegionInfo信息,并且它只能存
在一个HRegion,而.META. Table则存储了用户Table的RegionInfo信息,它可以被切分成多个
HRegion,因而对第一次访问用户Table时,首先从ZooKeeper中读取-ROOT-Table所在
HRegionServer;然后从该HRegionServer中根据请求的TableName,RowKey读取.META. Table所
在HRegionServer;最后从该HRegionServer中读取.META. Table的内容而获取此次请求需要访问
的HRegion所在的位置,然后访问该HRegionSever获取请求的数据,这需要三次请求才能找到
用户Table所在的位置,然后第四次请求开始获取真正的数据。当然为了提升性能,客户端会
缓存-ROOT- Table位置以及-ROOT-/.META. Table的内容。如下图所示:
在这里插入图片描述

可是即使客户端有缓存,在初始阶段需要三次请求才能直到用户Table真正所在的位置也是性
能低下的,而且真的有必要支持那么多的HRegion吗?或许对Google这样的公司来说是需要
的,但是对一般的集群来说好像并没有这个必要。在BigTable的论文中说,每行METADATA存
储1KB左右数据,中等大小的Tablet(HRegion)在128MB左右,3层位置的Schema设计可以支持2^
34个Tablet(HRegion)。即使去掉-ROOT-Table,也还可以支持2^17(131072)个HRegion, 如果每
个HRegion还是128MB,那就是16TB,这个貌似不够大,但是现在的HRegion的最大大小都会设
置的比较大,比如我们设置了2GB,此时支持的大小则变成了4PB,对一般的集群来说已经够

了,因而在HBase 0.96以后去掉了-ROOT-Table,只剩下这个特殊的目录表叫做Meta
Table(hbase:meta),它存储了集群中所有用户HRegion的位置信息,而ZooKeeper的节点中
(/hbase/meta-region-server)存储的则直接是这个Meta Table的位置,并且这个Meta Table如以前
的-ROOT- Table一样是不可split的。这样,客户端在第一次访问用户Table的流程就变成了:

  1. 从ZooKeeper(/hbase/meta-region-server)中获取hbase:meta的位置(HRegionServer的位
    置),缓存该位置信息。
  2. 从HRegionServer中查询用户Table对应请求的RowKey所在的HRegionServer,缓存该位置信
    息。
  3. 从查询到HRegionServer中读取Row。
    从这个过程中,我们发现客户会缓存这些位置信息,然而第二步它只是缓存当前RowKey对应
    的HRegion的位置,因而如果下一个要查的RowKey不在同一个HRegion中,则需要继续查询
    hbase:meta所在的HRegion,然而随着时间的推移,客户端缓存的位置信息越来越多,以至于
    不需要再次查找hbase:meta Table的信息,除非某个HRegion因为宕机或Split被移动,此时需要
    重新查询并且更新缓存。

    在这里插入图片描述

HRegionServer详解
在这里插入图片描述

HRegionServer一般和DataNode在同一台机器上运行,实现数据的本地性。HRegionServer存活
和管理多个HRegion,由WAL(HLog)、BlockCache、MemStore、HFile组成。

  1. WAL即Write Ahead Log,在早期版本中称为HLog,它是HDFS上的一个文件,如其名字所
    表示的,所有写操作都会先保证将写操作写入这个Log文件后,才会真正更新
    MemStore,最后写入HFile中。采用这种模式,可以保证HRegionServer宕机后,我们依然
    可以从该Log文件中恢复数据,Replay所有的操作,而不至于数据丢失。

HLog文件就是一个普通的Hadoop Sequence File,Sequence File 的Key是HLogKey对
象,HLogKey中记录了写入数据的归属信息,除了table和region名字外,同时还包括
sequence number和timestamp,timestamp是”写入时间”,sequence number的起始值
为0,或者是最近一次存入文件系统中sequence number。HLog Sequece File的Value是
HBase的KeyValue对象,即对应HFile中的KeyValue。
HRegionServer一般和DataNode在同一台机器上运行,实现数据的本地性。HRegionServer存活
和管理多个HRegion,由WAL(HLog)、BlockCache、MemStore、HFile组成。

这个Log文件会定期Roll出新的文件而删除旧的文件(那些已持久化到HFile中的Log可以删
除)。WAL文件存储在/hbase/WALs/${HRegionServer_Name}的目录中(在0.94之前,存储
在/hbase/.logs/目录中),一般一个HRegionServer只有一个WAL实例,也就是说一个
HRegionServer的所有WAL写都是串行的(就像log4j的日志写也是串行的)。一个RS服务器
只有一个HLOG文件,在0.94版本之前,写HLOG的操作是串行的,所以效率很低,所
以1.0版本之后,Hbase引入多管道并行写技术,从而提高性能。

  1. BlockCache是一个读缓存,即“引用局部性”原理(也应用于CPU,分空间局部性和时间局部性,空间局部性是指CPU在某一时刻需要某个数据,那么有很大的概率在一下时
    刻它需要的数据在其附近;时间局部性是指某个数据在被访问过一次后,它有很大的概
    率在不久的将来会被再次的访问),将数据预读取到内存中,以提升读的性能。
    这样设计的目的是为了提高读缓存的命中率

HBase中默认on-heap LruBlockCache。(LRU -evicted,是一种数据的回收策略, LRU–
最近最少使用的:移除最长时间不被使用的对象。)

  1. HRegion是一个Table中的一个Region在一个HRegionServer中的表达。一个Table可以有
    一个或多个HRegion,他们可以在一个相同的HRegionServer上,也可以分布在不同的
    HRegionServer上,一个HRegionServer可以有多个HRegion,他们分别属于不同的Table。
    HRegion由多个Store(HStore)构成,每个HStore对应了一个Table在这个HRegion中的一个
    Column Family,即每个Column Family就是一个集中的存储单元,因而最好将具有相近I/O
    特性的Column存储在一个Column Family,以实现高效读取(数据局部性原理,可以提高
    缓存的命中率)。HStore是HBase中存储的核心,它实现了读写HDFS功能,一个HStore由
    一个MemStore 和0个或多个StoreFile组成。

  2. MemStore是一个写缓存(In Memory Sorted Buffer),所有数据的写在完成WAL日志写
    后,会写入MemStore中,由MemStore根据一定的算法(LSM-TREE算法 日志合并树算
    法,这个算法的作用是将数据顺序写磁盘,而不是随机写,减少磁头调度时间,从而提
    高写入性能)将数据Flush到底层的HDFS文件中(HFile),通常每个HRegion中的每个
    Column Family有一个自己的MemStore。

  3. HFile(StoreFile) 用于存储HBase的数据(Cell/KeyValue)。在HFile中的数据是按
    RowKey、Column Family、Column排序,对相同的Cell(即这三个值都一样),则按
    timestamp倒序排列。
    因为Hbase的HFile是存到HDFS上,所以Hbase实际上是具备数据的副本冗余机制的。

HBase写流程

HRegionServer中数据写流程图解

当客户端发起一个Put请求时,首先它从hbase:meta表中查出该Put数据最终需要去的
HRegionServer。然后客户端将Put请求发送给相应的HRegionServer,在HRegionServer中它首先
会将该Put操作写入WAL日志文件中(Flush到磁盘中)。

在这里插入图片描述

写完WAL日志文件后,然后会将数据写到Memstore,在Memstore按Rowkey排序,以及用
LSM-TREE对数据做合并处理。HRegionServer根据Put中的TableName和RowKey找到对应的
HRegion,并根据Column Family找到对应的HStore,并将Put写入到该HStore的MemStore中。此
时写成功,并返回通知客户端。
在这里插入图片描述

MemStore Flush
MemStore是一个In Memory Sorted Buffer,在每个HStore中都有一个MemStore,即它是一个
HRegion的一个Column Family对应一个实例。它的排列顺序以RowKey、Column Family、Column
的顺序以及Timestamp的倒序,如下所示:
在这里插入图片描述

每一次Put/Delete请求都是先写入到MemStore中,当MemStore满后会Flush成一个新的
StoreFile(底层实现是HFile),即一个HStore(Column Family)可以有0个或多个StoreFile(HFile)。有
以下三种情况可以触发MemStore的Flush动作:

  1. 当一个HRegion中的MemStore的大小超过了:
    hbase.hregion.memstore.flush.size的大小,默认128MB。
    此时当前的MemStore会Flush到HFile中。
  2. 当RS服务器上所有的MemStore的大小超过了:
    hbase.regionserver.global.memstore.upperLimit的大小,默认35%的内存使用量。

比如:一台服务器内存是64GB,留出10GB给操作系,留出4GB给他技术框架,剩余的
50GB给HBase用,即当RS服务器上所有的Memstore总内存大小达到 50GB*35%,会触
发flush
此时当前HRegionServer中所有HRegion中的MemStore可能都会Flush。从最大的
Memostore开始flush

  1. 当前HRegionServer中WAL的大小超过了 1GB
    hbase.regionserver.hlog.blocksize(32MB)* hbase.regionserver.max.logs(32)的数量,当前
    HRegionServer中所有HRegion中的MemStore都会Flush
    这里指的是两个参数相乘的大小。
    查代码发现:hbase.regionserver.max.logs默认值是32,而hbase.regionserver.hlog.blocksize
    是HDFS的默认blocksize,32MB

此外,在MemStore Flush过程中,还会在尾部追加一些meta数据,其中就包括Flush时最大的
WAL sequence值,以告诉HBase这个StoreFile写入的最新数据的序列,那么在Recover时就直到
从哪里开始。在HRegion启动时,这个sequence会被读取,并取最大的作为下一次更新时的起
始sequence。
在这里插入图片描述

HFile格式
HBase的数据以KeyValue(Cell)的形式顺序的存储在HFile中,在MemStore的Flush过程中生成
HFile,由于MemStore中存储的Cell遵循相同的排列顺序,因而Flush过程是顺序写,我们知道磁
盘的顺序写性能很高,因为不需要不停的移动磁盘指针。
在这里插入图片描述
HFile参考BigTable的SSTable和Hadoop的TFile实现,从HBase开始到现在,HFile经历了三个版
本,其中V2在0.92引入
首先我们来看一下V1的格式:
在这里插入图片描述

V1的HFile由多个Data Block、Meta Block、FileInfo、Data Index、Meta Index、Trailer组成,其中
Data Block是HBase的最小存储单元,在前文中提到的BlockCache就是基于Data Block的缓存的。
一个Data Block由一个魔数和一系列的KeyValue(Cell)组成,魔数是一个随机的数字,用于表示
这是一个Data Block类型,以快速检测这个Data Block的格式,防止数据的破坏。Data Block的大
小可以在创建Column Family时设置(HColumnDescriptor.setBlockSize()),默认值是64KB,
大号的DadaBlock有利于顺序Scan,小号DataBlock利于随机查询,
因而需要权衡。Meta块是可选的,FileInfo是固定长度的块,它纪录了文件的一些Meta信息,
例如:AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR, MAX_SEQ_ID_KEY等。Data
Index和Meta Index纪录了每个Data块和Meta块的起始点、未压缩时大小、Key(起始RowKey)
等。Trailer纪录了FileInfo、Data Index、Meta Index块的起始位置,Data Index和Meta Index索引
的数量等。其中FileInfo和Trailer是固定长度的。
HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,
并且有固定的结构。我们来看看里面的具体结构:
在这里插入图片描述
开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定
长度的数值,表示RowKey的长度,紧接着是RowKey,然后是固定长度的数值,表示Family的
长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key
Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。随着HFile版
本迁移,KeyValue(Cell)的格式并未发生太多变化,只是在V3版本,尾部添加了一个可选的Tag数组。

HFileV1版本的在实际使用过程中发现它占用内存多,因而增加了启动时间。为了解决这些问
题,在0.92版本中引入HFileV2版本:
在这里插入图片描述

在这个版本中,为了提升启动速度,还引入了延迟读的功能,即在HFile真正被使用时才对其
进行解析。

对HFileV2格式具体分析,它是一个多层的类B+树索引,采用这种设计,可以实现查找不需要
读取整个文件:
在这里插入图片描述

Data Block中的Cell都是升序排列,每个block都有它自己的Leaf-Index,每个Block的最后一个Key
被放入Intermediate-Index中,Root-Index指向Intermediate-Index。在HFile的末尾还有Bloom
Filter(布隆过滤)用于快速定位那么没有在某个Data Block中的Row;TimeRange信息用于给那
些使用时间查询的参考。在HFile打开时,这些索引信息都被加载并保存在内存中,以增加以
后的读取性能。

HBase读的实现

HBase读的实现
我们先来分析一下相同的Cell(数据)可能存在的位置:首先对新写入的Cell,它会存在于
MemStore中;然后对之前已经Flush到HFile中的Cell,它会存在于某个或某些
StoreFile(HFile)中;最后,对刚读取过的Cell,它可能存在于BlockCache中。既然相同的
Cell可能存储在三个地方,在读取的时候只需要扫瞄这三个地方,然后将结果合并即可
(Merge Read),在HBase中扫瞄的顺序依次是:
BlockCache、MemStore、StoreFile(HFile)(这个扫描顺序的目的也是为了减少磁盘的
I/O次数)。其中StoreFile的扫瞄先会使用Bloom Filter(布隆过滤算法)过滤那些不可能符合
条件的DataBlock,然后使用Block Index快速定位Cell,并将其加载到BlockCache中,然后
从BlockCache中读取。我们知道一个HStore可能存在多个StoreFile(HFile),此时需要扫瞄
多个HFile,如果HFile过多又是会引起性能问题。
在这里插入图片描述

Compaction机制
MemStore每次Flush会创建新的HFile,而过多的HFile会引起读的性能问题,那么如何解决
这个问题呢?HBase采用Compaction机制来解决这个问题。在HBase中Compaction分为两
种:Minor Compaction和Major Compaction。

  1. Minor Compaction是指选取一些小的、相邻的StoreFile将他们合并成一个更大的
    StoreFile,在这个过程中不会处理已经Deleted或Expired的Cell。一次Minor
    Compaction的结果是更少并且更大的StoreFile。(这个是对的吗?BigTable中是这
    样描述Minor Compaction的:As write operations execute, the size of the
    memtable in- creases. When the memtable size reaches a threshold, the
    memtable is frozen, a new memtable is created, and the frozen memtable is
    converted to an SSTable and written to GFS. This minor compaction process has
    two goals: it shrinks the memory usage of the tablet server, and it reduces the
    amount of data that has to be read from the commit log during recovery if this
    server dies. Incom- ing read and write operations can continue while com-
    pactions occur. 也就是说它将memtable的数据flush的一个HFile/SSTable称为一次
    Minor Compaction)
  2. Major Compaction是指将所有的StoreFile合并成一个StoreFile,在这个过程中,标记
    为Deleted的Cell会被删除,而那些已经Expired的Cell会被丢弃,那些已经超过最多版
    本数的Cell会被丢弃。一次Major Compaction的结果是一个HStore只有一个
    StoreFile存在。Major Compaction可以手动或自动触发,然而由于它会引起很多的
    I/O操作而引起性能问题,因而它一般会被安排在周末、凌晨等集群比较闲的时间。

如何实现Compaction:
1.通过API:

//--minor compact
admin.compact("tab2".getBytes());
//--major compact
admin.majorCompact("tab2".getBytes());

2.通过指令:

compact('tab2')
major_compact('tab2')

更形象一点,如下面两张图分别表示Minor Compaction和Major Compaction。
Hbase默认用的是Minor compaction。之所以默认不用Major Compaction的原因是在于,
Major Compaction可能会带来大量的磁盘I/O,从而阻塞HBase其他的读写操作。所以对
于Major Compactoin,一般选择在业务峰值低的时候执行。所以使用Major Compact 一般
在周末或凌晨去执行

在这里插入图片描述

在这里插入图片描述

上一篇 11.大数据学习之旅——HBase

猜你喜欢

转载自blog.csdn.net/qq_39188039/article/details/86356284