可能有的图片显示不出来,如有需要,请留邮箱我发给你!
1、Hbase基本介绍
1.1 什么是HBase
Hbase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库
- Nosql = no sql : NoSQL作为一种相比新型结构化存储单元,基本上不使用sql
- Nosql = not only sql :不仅仅是sql,它不一定遵循传统数据库的一些基本要求
1.2 Hbase有什么特点
- **大:**一个表可以有上十亿行,上百万列
- **面向列:**列可以灵活指定,面向列(族)的存储和权限控制
- **稀疏:**对于为null的列,不占用空间
- **无严格模式:**每行都有一个可排序的主键和任意多的列,列可以根据需要动态的增加,同一行可以有不同列
1.3 Hbase表结构
- nameSpace: 命名空间,相当于关系型数据库的database概念。
- **region 😗*类似于关系型数据库表的概念,
- **row:**一行数据,每行数据由一个rowkey和多个Column(列)组成,数据是按照rowkey的字典顺序排序的
- Column: HBase中的每个列都由column family(列族)和Column Qualifier(列限定符)进行限定
- TimeStamp: 用户标识数据的不同版本(version),每条数据写入时,如果不指定,系统会自动为其加上该字段,其值为写入HBase的时间
1.4 Hbase基本架构
从物理结构上,Hbase包含了三种类型的server:zookeeper,HMaster,HRegion server,采用一种主从模式的结构。
- region server主要用来服务读写操作。当用户通过client访问数据是,会和HBase Region server直接通信
- HMaster主要进行regeion server的管理、DDL(创建、删除表)操作等
- Zookeeper是HDFS的一部分,主要用来维持整个集群的存活,保障HA,故障自动转移
而底层的存储,还是依赖于HDFS的。
- Hadoop的DataNode存储Region server所管理的数据,以hfile的形式,所有hbase的数据存储在hdfs中
- Hadoop的NameNode维护所有物理数据块的metadata
1.4.1 Region Server
HBase的tables根据rowkey的范围进行水平切分,切分成region后分配到各个region server。一个region包含一个表在start key和end key所有行。而用户都是跟region server进行读写交互。一个region建议大小在5~10G。可通过参数 hbase.hregion.max.filesize
去设置
1.4.2 HBase Master
一般也叫做HMaster ,HMaster主要职责包括三个方面:
- 与Region server的交互,对region server进行统一管理
- 启动时region的分配 崩溃后恢复的region重新分配 负载均衡region的重新分配
- 创建、删除、更新表结构的DDL操作
1.4.3 Zookeeper
Hbase使用zookeeper作为分布式协调服务,来维护集群内的server状态。(通过heartbeat维护那些server是存活并可用的)。负责HMaster的选举工作,通过创建临时节点,并监控状态,来实现HA。
1.4.4 这些组件如何一起协调工作
1)HMaster的高可用
多个HMaster会去竞争zookeeper上的临时节点,而zookeeper会将第一个创建成功的HMaster作为当前active的HMaster,其他HMaster进入stand by的状态。这个active的HMaster会不断发送heartbeat给zk,其他stand by状态的HMaster节点会监听这个active HMaster的故障信息,一旦发现active HMaster宕机了,就会重新竞争新的active HMaster
2) HMaster监控HRegion Server
每个regeion server会创建一个临时节点。HMaster会监控这些节点来确认那些region server是可用的,如果region server没有发送heartbeat给zk,zk会删除这个临时节点,其他监听这会受到这个故障节点被删除的信息。比如HMaster会监控RS的消息,如果发现某个RS下线了,那么就会重新分配RS来恢复相应的region数据。
2、深入理解HBase的原理
2.1 client 访问HBase的数据
有一张特殊的HBase目录表,叫做META table,保存了集群中各个region的位置。zookeeper中保存了这样meta data的位置信息。(在0.96之前,zookeeper保存的是root table 里面存的是meta table的位置)
基本流程如下:
- client 先访问zookeeper,获取meta表位于哪个region server中
- 访问对应的region server,获取.meta表,根据读请求的table/rowkey,查询目标数据位于哪个region server的哪个region 中,并将该table的region信息以及meta表的位置信息缓存在client的meta cache.
- 与目标region server进行通讯
- 分别在block cache(读缓存),memstore和store file(hfile)中查询目标数据,并将所有数据进行合并。
- 将合并的最终结果返回给客户端。
.meta. table的存储结构
- meta table 保存了所有region信息的一张表
- Meta table 存储的数据形式类似一颗b树
- 以keyvalue形式保存数据
- key:region的table name ,start key等信息Values:region server 的相关信息
2.2 深入Region Server
一个region server运行在一个HDFS的data node上,并且拥有以下组件:
- WAL:全称Write Ahead Log,属于分布式系统上的文件。主要用来存储还未被持久化到磁盘的新数据。如果新数据还未持久化,节点发生宕机,那么就可以用WAL来恢复这些数据。
- BlockCache:是一个读缓存。它存储了高频访问的数据。当这个缓存满了后,会清除最近最少访问的数据
- MenStore:是一个写缓存。它存储了还未被写入磁盘的数据。它在写入磁盘前,对自身数据进行排序,从而保证数据的顺序写入。每个region的每个column family 会对应一份mensotre
- HFiles:按照字典排序存储各个row的键值
2.3 Client 写数据到HBase
基本流程:
- client 先访问zookeeper,获取.meta表位于哪个region server
- 访问对应的region server,获取.meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个region server的哪个region 中。并将该table的region信息以及meta表的位置信息缓存在客户端得meta cache,方便下次访问。
- 与目标Region server进行通讯
- 将数据顺序写入(追加)到WAL
- 将数据写入对应的MemStore,数据会在Memstore进行排序
- 向客户端发送ack
- 等达到Memsotre的刷写时机后,将数据写到HFile
2.4 MenStore Flush
Memstore刷写时机:
-
当某个memstore的大小达到了
hbase.hregion.memsotre.flush.size(默认128M)
时 -
当region server中 memstore的总大小达到
java_headpsize*hbase.regionserver.global.memstore.size(默认值0.4) *hbase.regionserver.global.memsotre.size.lower.limit(默认值0.5)
region会按照其所有memstore的大小顺序(由大到下)一次进行刷写。直到region server中所有memstore的总大小减小到上述值一下。
-
到达自动刷写时间,也会触发memstore。
hbase.regionserver.optionalcacheflushinterval(1h)
-
当region server中的memsotre的总大小达到以下值时时,会阻止往所有的memstore写数据。
java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4)
2.5 深入 HFile
在HBase中,数据以有序kv的形式,存储在HFile中。当MemStore存储足够的数据,全部kv对被写入HFile存入HDFS。这里写文件时顺序写,避免了磁盘大量移动磁头的过程,比随机写高效很多。
HFile的逻辑结构主要包括6个部分
- Data Block段:保存表中的数,这部分可以被压缩
- Meta block段(可选的):保存用户自定义的kv对,可以被压缩
- File info : HFile的元数据信息,不被压缩,用户也可以在这一部分添加自己的元信息
- Data block index: Data block的索引。每条索引的key是被索引的block的第一条记录的key
- Meta block index(可选的): Meta block 的索引
- Trailer: 这一段是定长的。保存了每一段的偏移量和寻址信息,还包括其他信息,比如布隆过滤器和时间范围信息。读取一个HFile时,先读取Trailer,找到datablock index段的微信并读取到内存中,当检索某个key时,不需要扫描整个Hfile 。
2.6 StoreFile Compaction
2.6.1 为什么需要Compactioin
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(put/delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。为了减少HFile的文件个数,清理过期数据,所以HBase提供了Compaction机制,将小文件合并成大文件,目的是提高查询性能。compaction分为两种 :Minor Compaction和major Compaction
2.6.2 Minor Compaction和major Compaction
- Minor Compaction会将临近的若干个较小的HFile合并成一个较大的Hfile,但不会清理过期和删除数据
- Major Compcation会将一个Store下的所有HFile合并成一个大HFile,并且会清理掉过期和删除的数据。
2.6.3 触发条件
- MemStore Flush :应该说compaction操作源头就来自flush操作,memstore flush 会产生Hfile文件,文件越多就需要compaction。一旦文件数>=hbase.hstore.compaction.min (default is 3),就会触发compaction。
- 后台线程周期性检查:后台线程compaction checker定期触发检查是否需要执行compaction,检查周期为
hbase.server.thread.wakefrequency*hbase.server.compactchecker.interval.multiplier
- 手动触发:一般来讲,手动触发compaction通常是为了执行major compaction。原因有三:1)很多业务担心自动major compaction影响读写性能 2)用户执行完alter操作后希望立刻生效 3)hbase管理员发现磁盘容量不够的情况下手动触发major compaction删除大量过期数据。
2.7 Region split
默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region 都位于当前的 RS,但处于负载均衡考虑,HMaster有可能会将某个region转移给其他的Region server
Region split时机:
- 当1个region中的某个store下所有storefile的总大小超过
hbase.hregion.max.filesize
(默认是10G),该Region就会进行拆分(0.94版本之前) - 当1个region中的某个store下所有storefile的总大小超过min(R ^2 * hbase.hregion.memstore.flush.size,hbase.hregion.max.filesize),该region就会进行拆分,其中R为当前Region Server中属该Table的个数(0.94版本之后)
2.8 Region Merge
- Region的合并不是为了性能,而是出于维护的目的
- 比如删除大量的数据,这个时候每个Region都变的很小,存储多个Region就浪费了,这个时候可以把Region合并起来,进而减少一些Region服务器节点
实现方法
-
通过Merge类冷合并Region
- 执行前,需要关闭HBase集群
- 直接执行
Hase org.apache.hadoop.hbase.util.Merge region_name
-
通过online_mege 热合并Region
-
在线进行合并
-
online_merge传参是Region的hash值,而Region的hash值就是region名称最后那段在两个.之间的字符串部分
-
需要进入Hbase shell
merge_region 'c2212a3956b814a6f0d57a90983a8515','553dd4db667814cf2f050561167ca030'
-
3、HBase的shell 命令 和 API
3.1 Hbase 的shell命令
1、显示HBase的表列表 list
2、创建表 create '表名','列族','列族' ..
3、查看表的信息: desc '表名'
4、往表里插入数据: put '表名','rowkey','cell...'
5、查询数据 : get '表名','rowkey'
6、查询数据 : scan '表名'
7、删除数据: delte '表名','rowkey'
8、修改表结构: alter
9、修改数据: hbase没有修改数据的显示操作,重复插入就相当于是修改数据
10、清空表 truncate '表名' ,首先要disable 表 启动表:enable table_name
11、删除表 drop table , 先要disable 表
12、过滤器操作:
get 'user', 'rk0001', {FILTER => "ValueFilter(=, 'binary:中国')"}
get 'user', 'rk0001', {FILTER => "(QualifierFilter(=,'substring:a'))"}
3.2 HBase API
public class HbaseUtil {
public static Configuration conf = null ;
public static Connection connection = null ;
public static Admin admin = null ;
/*
* @desc 取得连接
* */
public static void setConf(String quorum,String port){
try {
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum",quorum); //zookeeper地址 qyl01,qyl02,qyl03
conf.set("hbase.zookeeper.property.clientPort",port); //2181
connection = ConnectionFactory.createConnection(conf);
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* @desc 关闭连接
* */
public static void closeHBaseConnection(){
try {
if (connection != null) {
connection.close();
}
if(admin != null){
admin.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
/*
* @desc 创建表
* */
public static void createTable(String tableName,String columnFamily){
try {
TableName tbName = TableName.valueOf(tableName);
if(!admin.tableExists(tbName)){
HTableDescriptor hTableDescriptor = new HTableDescriptor(tbName);
HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(columnFamily);
hTableDescriptor.addFamily(hColumnDescriptor);
admin.createTable(hTableDescriptor);
}else{
System.out.println(tableName+" exists !");
}
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* @desc 添加多条记录
* */
public static void addMoreCord(String tableName, String columnFamily, String qualifier, List<String> rowList, String value){
Table table = null ;
try{
table = connection.getTable(TableName.valueOf(tableName));
List<Put> puts = new ArrayList<Put>();
Put put = null;
for(int i = 0; i < rowList.size(); i++){
put = new Put(Bytes.toBytes(rowList.get(i)));
put.addColumn(Bytes.toBytes(columnFamily),Bytes.toBytes(qualifier),Bytes.toBytes(value));
puts.add(put);
}
table.put(puts);
}catch (IOException e){
e.printStackTrace();
}finally {
if(table != null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
* @desc 查询rowkey下多列值,一起返回
* */
public static String[] getValue(String tableName,String rowKey,String family,String[] qualifier){
Table table = null;
try {
table = connection.getTable(TableName.valueOf(tableName));
Get get = new Get(rowKey.getBytes());
//返回指定列族、列名,避免rowKey下所有数据
get.addColumn(family.getBytes(), qualifier[0].getBytes());
get.addColumn(family.getBytes(), qualifier[1].getBytes());
Result rs = table.get(get);
// 返回最新版本的Cell对象
Cell cell = rs.getColumnLatestCell(family.getBytes(), qualifier[0].getBytes());
Cell cell1 = rs.getColumnLatestCell(family.getBytes(), qualifier[1].getBytes());
String[] value = new String[qualifier.length];
if (cell!=null) {
value[0] = Bytes.toString(CellUtil.cloneValue(cell));
value[1] = Bytes.toString(CellUtil.cloneValue(cell1));
}
return value;
} catch (IOException e) {
e.printStackTrace();
return null;
} finally {
if (table!=null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/*
* @desc 查询rowkey下某一列值
*/
public static String getValue(String tableName, String rowKey, String family, String qualifier) {
Table table = null;
try {
table = connection.getTable(TableName.valueOf(tableName));
Get get = new Get(rowKey.getBytes());
//返回指定列族、列名,避免rowKey下所有数据
get.addColumn(family.getBytes(), qualifier.getBytes());
Result rs = table.get(get);
Cell cell = rs.getColumnLatestCell(family.getBytes(), qualifier.getBytes());
String value = null;
if (cell!=null) {
value = Bytes.toString(CellUtil.cloneValue(cell));
}
return value;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (table!=null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/*
* 全表扫描
* @param tableName
* @return
* @desc qualifier 指的是某列
*/
public static ResultScanner scan(String tableName,String family,String qualifier) {
Table table = null;
try {
table = connection.getTable(TableName.valueOf(tableName));
Scan scan = new Scan();
ResultScanner rs = table.getScanner(scan);
// 一般返回ResultScanner,遍历即可
// if (rs!=null){
// String row = null;
// String quali = null;
// String value = null;
// for (Result result : rs) {
// row = Bytes.toString(CellUtil.cloneRow(result.getColumnLatestCell(family.getBytes(), qualifier.getBytes())));
// quali =Bytes.toString(CellUtil.cloneQualifier(result.getColumnLatestCell(family.getBytes(), qualifier.getBytes())));
// value =Bytes.toString(CellUtil.cloneValue(result.getColumnLatestCell(family.getBytes(), qualifier.getBytes())));
// System.out.println(row+"-"+quali+"-"+value);
// }
// }
return rs;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (table!=null){
try {
table.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
3.3 HBase 过滤器
3.3.1 说明
要完成一个过滤操作,至少需要两个参数,一个是抽象操作符,另一个就是具体的比较器了(Comparator)
抽象操作符
LESS <
LESS_OR_EQUAL <=
EQUAL =
NOT_EQUAL <>
GREATER_OR_EQUAL >=
GREATER >
NO_OP 排除所有
比较器(指定比较机制)
BinaryComparator 按字节索引顺序比较指定字节数组,采用 Bytes.compareTo(byte[])
SubstringComparator 判断提供的子串是否出现在 value 中
3.3.2 分类
- 行键过滤器 rowFilter
- 列簇过滤器 familyFilter
- 列过滤器 QualifierFilter
- 值过滤器 valueFilter
- 时间过滤器 TimeStampsFilter
- 单列值过滤器 SingleColumnValueFilter --会返回满足条件的整行
3.3.3 使用
Filter rowFilter = new RowFilter(CompareOp.GREATER, new BinaryComparator("95007".getBytes()));
scan.setFilter(rowFilter);
4、HBase调优
4.1 表的设计
1、pre-Creating-Regions(预分区)
默认情况下,在创建HBase表的时候会自动创建一个region分区,当导入数据的时候,所有的HBase客户端都向这一个region写数据,直到很大的时候才进行切分,提前创建分区,可以加快批量数据写入。(列如以手机号)
2、Row Key的设计
越小越好,rowkey的设计是要根据实际业务来 ,散列性(取反,用hash值对某个数取余)
3、column family
不要在一张表里定义太多的column family,建议2~3个,因为某个column family在flush的时候,它临近的column family 也会因关联效应被触发flush,最终导致系统产生更多个I/0。
4、compaction & Split
关闭 hbase.hregion.majorcompaction(默认7天),手动编写脚本避开业务高峰期进行major compaction
5、BlockCache
一个rs上的block和memstore大小不能超过heapsize * 0.8 ,对于注重读响应时间的系统,可以将blockcache设置大写 如 blockcache=0.4 memstore = 0.39 ,以加大缓存命中率(默认blockcache=0.2 memstore=0.4)
4.2 写表的优化
1、多个HTable并发写: 创建多个HTable客户端用于写操作,提高写数据的吞吐量
2、使用bulkload写入
3、Auto Flash: 调用HTable.setAutoFlushTo(false)
方法可以将HTable写客户端自动flush关闭,默认是开启的
4、Compression压缩:推荐使用snappy hcd.setCompressionType(Algorithm.SNAPPY)
4.3 读表优化
1、批量读 : 通过调用HTable.get(List)方法可以根据一个指定的rowkey列表,批量获取多行记录,减少IO
2、缓存查询的结果:创建表的时候,可以通过HColumnDEscriptor.setInMemory(true)建表放在rs的缓存中。
5、Hbase的bulkLoad
简单来说 Bulkload 就是利用 HBase 的数据信息按照特定格式存储在 HDFS 内这一原理,直接在 HDFS 中生成持久化的 HFile 数据格式文件,然后上传至合适位置,即完成巨量数据快速入库的办法。
实现步骤:
- 从数据源(文本文件或其他的数据库)提取数据并上传到HDFS
- 利用MapReduce作业处理先准备的数据,需要我们编写map函数,reduce函数由Hbase提供,rowkey作为输出key,keyvalue,put或者delete作为输出value。需要HFileOutputFormat2来生成HBase数据文件。
- 告诉Region Servers数据的位置并导入数据。通常使用 LoadIncrementalHFiles
代码
使用mapReduce生成HFile文件
public class IteblogBulkLoadMapper extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put>{
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] items = line.split("\t");
ImmutableBytesWritable rowKey = new ImmutableBytesWritable(items[0].getBytes());
Put put = new Put(Bytes.toBytes(items[0])); //ROWKEY
put.addColumn("f1".getBytes(), "url".getBytes(), items[1].getBytes());
put.addColumn("f1".getBytes(), "name".getBytes(), items[2].getBytes());
context.write(rowkey, put);
}
}
通过BlukLoad方式加载HFile文件
public class LoadIncrementalHFileToHBase {
public static void main(String[] args) throws Exception {
Configuration configuration = HBaseConfiguration.create();
HBaseConfiguration.addHbaseResources(configuration);
LoadIncrementalHFiles loder = new LoadIncrementalHFiles(configuration);
HTable hTable = new HTable(configuration, "blog_info");
loder.doBulkLoad(new Path("hdfs://iteblog:9000/user/iteblog/output"), hTable);
}
}
驱动程序
public class IteblogBulkLoadDriver {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
final String SRC_PATH= "hdfs://iteblog:9000/user/iteblog/input";
final String DESC_PATH= "hdfs://iteblog:9000/user/iteblog/output";
Configuration conf = HBaseConfiguration.create();
Job job=Job.getInstance(conf);
job.setJarByClass(IteblogBulkLoadDriver.class);
job.setMapperClass(IteblogBulkLoadMapper.class);
job.setMapOutputKeyClass(ImmutableBytesWritable.class);
job.setMapOutputValueClass(Put.class);
job.setOutputFormatClass(HFileOutputFormat2.class);
HTable table = new HTable(conf,"blog_info");
HFileOutputFormat2.configureIncrementalLoad(job,table,table.getRegionLocator());
FileInputFormat.addInputPath(job,new Path(SRC_PATH));
FileOutputFormat.setOutputPath(job,new Path(DESC_PATH));
System.exit(job.waitForCompletion(true)?0:1);
}
}
6、HBase面试题
-
Hbase读写流程
-
rowkey设计
-
简述Hbase filter的实现原理是什么?
-
简述Hbase性能优化的思路。
-
Hbase和传统数据库的区别?
-
hbaes 的bulkload