目录
HBase原理
1 HBase架构
2 HBase中的核心概念
3 HBase的存储机制
4 HBase的寻址机制
5 HBase的读写流程
6 HBase的设计
7 HBase和Hive的整合
HBase原理
1 HBase架构
HBase的架构为主从架构,HMaster为主节点,HRegionServer为从节点
(1)HMaster职责:
①进行Region的分配,每一个Region分配到哪一个RegionServer上;
②负责RegionServer的负载均衡
③通过zookeeper发小失效的RegionServer并重新分配在它上面的Region
④HDFS上的HBase的垃圾文件回收
⑤处理schema更新请求,表的创建,删除,更改,列簇的增加等,将schema写入到zookeeper中
(2)HRegionserver职责
①管理每个Region
②负责每一个Region的分裂
(3)zookeeper职责
①进行HMaster的active的选举,避免单点故障
②存储HBase的寻址机制
③存储RegionServer的存活状态,HMaster通过zookeeper获取每一个HRegionServer的状态
④存储表的schema表结构
2 HBase中的核心概念
(1)Region
每一个表的数据,都需要进行划分成多个Region,Region对HBase表的划分是在行的方向上的。一个Region代表一个表中的多行数据,一定是行键范围内的数据。
Region是HBase进行分布式存储的最小单位,负载均衡的的存储单位,注意并不是物理存储的最小单位。一个Region是不能再进行分割的,在分布式存储的时候一个Region只能存储在一个HRegionserver即一个节点上,一个HRegionserver是可以存储多个Region的。
一个HBase表刚创建的时候只有一个Region,进行数据插入的时候,写入的都是这一个Region,随着数据插入的,数据量不断的增加当数据量达到一定值的时候(默认为10G)就会进行切分,将一个Region切分为2个Region,一旦切分完成,就会面临Region的重新分配,由HMaster分配每一个Region存储在哪个HRegionServer,原来的那个Region就没用了,进行下线。
配置文件中对切分大小的配置如下:
<property><name>hbase.hregion.max.filesize</name><value>10737418240</value><description>Maximum HStoreFile size. If any one of a column families' HStoreFiles hasgrown to exceed this value, the hosting HRegion is split in two.</description>
</property>
每一个Region中一个列族的物理文件达到10G才进行切分,切分的时候是按照rowkey的中间值进行切分的。
一个表是对应多个Region,多个Region对应多个物理文件的(一个列族一个物理文件)。每一个Region 都有一个全局唯一的一个编号,hdfs位置为:/user/hbase/data/default(namespace)/table_hbase(table)/313c7bb8491d6207ac0153c0392cc392(region编号)
刚开始对Hbase中表数据进行插入操作时候,操作只有一个Region,在这个Region分裂之前 操作的都是这一个Region 操作的只有一个Regionserver ,会出现 数据热点,anemia如何解决呢,下面表设计时再进行介绍。
(2)Store
一个Store对应一个列族,Store是HBase物理存储的最小单位,1个Region对应多个Store,多个Store对应多个列族。一个Store中包含一个MemStore和多个StoreFile。
(3)MemStore
每一个Store中,都有一位于内存中的MemStore存储空间,写入数据的时候,每一个Store中的数据先写入到MemStore,读取数据的时候先从内存中读取MemStore。
(4)StoreFile
每一个Store有多个位于硬盘的StoreFile磁盘文件,当MemStore达到一定阈值(默认为128M)的时候,就会将数据flush,形成一个个的StoreFile,配置文件中的参数如下
<property><name>hbase.hregion.memstore.flush.size</name><value>134217728</value><description>Memstore will be flushed to disk if size of the memstoreexceeds this number of bytes. Value is checked by a thread that runsevery hbase.server.thread.wakefrequency.</description>
</property>
(5)HFile
最终StoreFile文件以HFile格式存储在HDFS上。
(6)WAL(HLog)
WAL:write-ahead-log预写日志文件,为了防止在MemStore中数据丢失,在写入数据之前,会先将对数据的操作写入到WAL文件中。
一个HRegionServer中只会存储一个WAL文件,一个HREgionServer中的所有的Region共用一个WAL文件,便于管理日志。
3 HBase的存储机制
(1)0.96版本之前的存储机制
①原始表:存储原始数据的
②.meta表:存储原始数据的索引的,按照rowkey创建原始表索引
③-root-表:存储.meta表的索引的表,这个是最终索引,无论多大,只有一个Region不可分割了。-root-表的最终Region的存储位置存储在了zookeeper中,所以zookeeper中存储的是HBase的寻址路径。
(2)0.96版本之后的存储机制
①原始表:存储原始数据的
②.meta表:存储原始数据的索引的,按照rowkey创建原始表索引。.meta无论多大,只存储在一个Region,不可再进行分割了。.meta表的Region的存储位置存储在了zookeeper中。
.meta表中的一条数据相当于原始表至少10个G的数据,所以.meta表很难达到分割范围,除非数据量超级大才能达到。
4 HBase的寻址机制
HBase表最终会被拆分成一个个的Region,每一个Region可能会存储在不同的HRegionServer,每一个Region都是有独立编号的,无论读还是写操作,首先都要定位到在哪一个Region中,到对应的存储了该Region的HRegionServer上找到这个Region进行操作。
(1)0.96版本之前的寻址过程:
①客户端首先访问zookeeper,获取存储了-root-表的RegionServer的位置,以及Region的编号
②访问-root-表的Region,获取存储了.meta表的RegionServer的位置,以及Region的编号
③访问.meta表,获取可需要查询的rk所在的Region位置,获取原始数据的RegionServer的位置,以及Region的编号
④开始真正的访问对应RegionServer上的Region的的表数据
(2)0.96版本之后的寻址过程
①客户端首先访问zookeeper,获取存储.meta表的HRegionServer的位置,以及Region编号
②访问.meta表的Region,获取原始数据表中对应的HRegionServer的Region编号
③开始真正的访问对应RegionServer上的Region的的表数据
5 HBase的读写流程
(1)写流程(put|delete)
①客户端根据rowkey经常3次往返(寻址机制)找到对应的Region所在的RegionServer;
②客户端向RegionServer提交写请求;
③RegionServer找到目标Region
④Region检查数据是否与schema表结构的表名,列簇是否一致,一致则允许写入,不一致则报错直接返回。
⑤如果客户端没有指定版本,则获取当前的时间作为数据版本
⑥将更新操作写入到WAL文件中
⑦将更新写入到对应的Store中的MemSore
⑧判断MemStore是否需要flush刷新为StoreFile文件,默认当阈值MemStore文件大小达到128M的时候开始flush,形成StoreFile文件。
⑨当一个StoreFile的个数达到一定的阈值的时候就会触发compact合并,compact合并有minor compact和major compact:
minor join小合并,触发条件默认为StoreFile文件的个数达到3个的时候触发,将3个StoreFile合并为1个StoreFile文件,这个合并是没有任何逻辑操作的,只是物理操作,简单的将文件进行累加合并,只是在文件的个数上减少了,不会对真正需要删除的数据进行删除,只是打了标记,客户端看不到。
major join:当达到阈值(默认为7天)后,会将7天的多个HFile文件进行合并为1个HFile,这个合并是执行逻辑操作的,进行真正的数据删除,将所有需要删除的数据进行真正的合并删除。需要删除的数据有以下几种:
1)执行delete操作的数据;
2)版本超过给定的需要保存的版本的数据,将过期的数据删除
3)TTL过期的数据
⑩判断一个Store中的所有文件的总大小是否达到Region的切分标准,默认为128M,达到切分标准就会对Region进行切分,HMaster对新的Region进行重新的分配,丢弃旧的Region。
(2)读流程(get|scan)
①客户端根据rowkey经过3次往返(寻址机制)找到对应的Region所在的RegionServer
②客户端向对应的RegionServer的·Region发送读数据请求
③客户端先在Region的对应的Store的MemStore(Blockcache)中进行读取
④MemStore有数据则直接返回,没有数据则到HFile文件进行读取。
6 HBase的设计
(1)表设计
①防止数据热点问题,建表的时候最好进行表的预分区,插入数据的时候,rowkey不要顺序递增。
HBase中你的数据热点:进行读写操作的时候,频繁操作某一个Region造成这个Region所在的RegionServer热点,根源是经常访问的数据集中分配到了个别Region
②列族不建议过多
(2)列族设计
①将具有相同io属性的列放在同一个列簇中;
②列簇不宜过多,不要超过3个,因为不同的列簇需要跨文件访问
(3)行键设计
思路:首先根据业务,需要安装哪一个字段查询,然后要避免热点产生
①保证唯一性
②不宜过长,0-100byte,最好不要超过16byte,最好是8的倍数。原因如下
1)行键存储在每一个列簇文件中的StoreFile,如果太大会造成磁盘空间的浪费
2)行键信息也会写入到每一个Store的MemStore中,如果过长,会造成内存空间的极大浪费
2)大部分计算机的底层存储时8通道的
③散列性
rowkey是按照字典顺序排序的,如果rowkey过于集中,会造成数据操作集中在个别的Region上,造成数据热点
有效措施如下:
1)采用hash
2)加盐:在原生的rowkey前面加上随机数
3)反转:将字符串或时间戳进行反转
4)使用uuid或md5等方法
7 HBase和Hive的整合
HBase:是NoSQL分布式数据库,表结构是四维表。擅长做实时随机查询,没有分析函数join等
Hive:数据仓库,擅长做数据分许,有大量函数可以使用
HBase语法是不支持分析的,想要对HBase中的数据做数据分析就要将HBase和Hive整合,便于对HBase数据做统计分析。Hive读取HBase中的数据,将HBase中的数据转换二维表数据,需要hive-hbase-handler-2.3.2.jar包(整合的核心包)中的HBaseStorageHandler类,将HBase中的数据进行压平。
整个步骤如下:
①设置HBase的zookeeper访问路径:
set hbase.zookeeper.quorum=bigdata01:2181,bigdata02:2181,bigdata03:2181;
②设置HBase在zookeeper的访问路径,存储节点
set zookeeper.znode.parent=/hbase;
③将Hive的解析HBase的jar包添加到Hive的classpath下
add jar /home/refuel/opt/modules/apache-hive-2.3.2-bin/lib/hive-hbase-handler-2.3.2.jar;
整合完后在Hive中 读取HBase的表,在Hive建表语句指定解析类,全关联如下
create external table Hive_HBase(rowkey string, base_info map<string, string>, extra_info map<string, string>)
row format delimited fields terminated by '\t'
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties ("hbase.columns.mapping" = ":key,base_info:,extra_info:")
tblproperties ("hbase.table.name" = "Hive_HBase");with serdeproperties 指定hbase表和hive的对应关系的
hbase.columns.mapping 指定hbase表和hive表映射 和hive中的建表语句一一对应的指定hbase 对应值的时候 k(列族名):v(列族下的对应的列和值) key:base_infovalue: name:zs age:12 :key 获取rowkey 的值
hbase.table.name: 指定对应的表名
部分关联如下
create external table Hive_HBase02(rowkey string,name string,age int,math int)
row format delimited fields terminated by '\t'
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties ("hbase.columns.mapping" = ":key,base_info:name,base_info:age,extra_info:math")
tblproperties ("hbase.table.name" = "Hive_HBase02");
8 HBase的BulkLoad
HBase的数据导入有3种方式:①put方式一条条插入;②MapReduce方式并发导入;③BulkLoad方式
MapReduce与put方式导入数据的过程:文本数据---》HRegionServer---》WAL---》MemStore---》StoreFile---》HFile
BulkLoad方式将文本数据(结构化或半结构化)直接转换为HFile格式的数据,转换完成之后再将这个HFile数据放置在HBase的对应的表的存储目录下
所以BulkLoad的优势是省去了中间的写入数据的复杂的过程,直接得到最终的结果,效率极高。
BulkLoad如何进行海量数据的导入的呢?,如下两个重要的类
(1)HBase中提供了一个进行数据装换的类PutSortReducer将数据封装为HFile需要的格式
ImmutableBytesWritable:行键 Put:需要插入的数据对象 KeyValue:单元格
(2)HBase中还提供了一个输出格式的类HFileOutputFormat2 ,数据的为HFile格式数据,而不是文本。
所以实现如下:
Map端:
读取每一行文本数据,并封装PutSortReducer需要的数据
输出的key为ImmutaByteaWritable,输出的value为Put
Reduce端:
使用PutSortReducer类,进行准备Hfile需要的数据
驱动类:
文件输出格式采用HFileOutputFormat2
注意:上面的这些操作仅仅是将文本数据转换为HFile格式的数据。转换完成之后,还要将这个HFile数据放置在HBase的对应的表的存储目录下面。
具体实现代码如下:
import java.io.IOException;import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.hbase.KeyValue;
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.client.HTable;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.mapreduce.HFileOutputFormat2;
import org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;public class BulkLoadMR {static class MyMapper extends Mapper<LongWritable, Text, ImmutableBytesWritable, Put> {ImmutableBytesWritable mk = new ImmutableBytesWritable();@Overrideprotected void map(LongWritable key, Text value,Mapper<LongWritable, Text, ImmutableBytesWritable, Put>.Context context)throws IOException, InterruptedException {String[] datas = value.toString().split("\t");if (datas.length == 3) {mk.set(datas[0].getBytes());Put p = new Put(datas[0].getBytes());p.addColumn("column_faily1".getBytes(), "name".getBytes(), datas[1].getBytes());p.addColumn("column_faily2".getBytes(), "age".getBytes(), datas[2].getBytes());context.write(mk, p);}}}public static void main(String[] args) throws Exception {Configuration conf = new Configuration();conf.set("fs.defaultFS", "hdfs://bigdatagroup/");conf.set("hbase.zookeeper.quorum", "bigdata01:2181,bigdata02:2181,bigdata03:2181");Job job = Job.getInstance(conf);job.setJarByClass(BulkLoadMR.class);job.setMapperClass(MyMapper.class);job.setMapOutputKeyClass(ImmutableBytesWritable.class);job.setMapOutputValueClass(Put.class);job.setOutputKeyClass(ImmutableBytesWritable.class);job.setOutputValueClass(KeyValue.class);FileInputFormat.addInputPath(job, new Path("/data/student"));// 指定输出HFileOutputFormat2job.setOutputFormatClass(HFileOutputFormat2.class);// 进行输出HFileOutputFormat2.setOutputPath(job, new Path("/user/hbase/bulkload"));Connection conn = ConnectionFactory.createConnection(conf);HTable table = (HTable) conn.getTable(TableName.valueOf("table_bulkload"));// 设置对应表 参数1 job 参数2 表对象 参数3 加载region相关参数的// 准备需要的操作HFileOutputFormat2.configureIncrementalLoad(job, table, table.getRegionLocator());job.waitForCompletion(true);// 上面的这些操作仅仅是将文本数据转换为HFile格式的数据。// 转换完成之后,还要将这个HFile数据放置在HBase的对应的表的存储目录下面。LoadIncrementalHFiles loadH = new LoadIncrementalHFiles(conf);Admin admin = conn.getAdmin();// 进行加载// 参数1 hdfs 输出hfile 路径 参数2 admin 参数3 table 参数4:regioin信息对象loadH.doBulkLoad(new Path("/user/hbase/bulkload"), admin, table, table.getRegionLocator());}
}