Apache Hbase:
概述:
HBase 基于 Google的BigTable论文而来,是一个分布式海量列式非关系型数据库系统,可以提供超大规模数据集的实时随机读写。
列时存储的优点:
1)减少存储空间占用。
2)支持好多列
特点:
海量存储: 底层基于HDFS存储海量数据
列式存储:HBase表的数据是基于列族进行存储的,一个列族包含若干列
极易扩展:底层依赖HDFS,当磁盘空间不足的时候,只需要动态增加DataNode服务节点就可以
高并发:支持高并发的读写请求
稀疏:稀疏主要是针对HBase列的灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的。
数据的多版本:HBase表中的数据可以有多个版本值,默认情况下是根据版本号去区分,版本号就是插入数据的时间戳
数据类型单一:所有的数据在HBase中是以字节数组进行存储
应用:
交通方面:船舶GPS信息,每天有上千万左右的数据存储。
金融方面:消费信息、贷款信息、信用卡还款信息等
电商方面:电商⽹网站的交易信息、物流信息、游览信息等
电信方面:通话信息
总结:HBase适合海量明细数据的存储,并且后期需要有很好的查询性能(单表超千万、上亿,且并发要求高)
数据模型:
namespace:
命名空间,类似于关系型数据库的database概念,每个命名空间下有多个表。HBase两个自带的命名空间,分别是hbase和default,hbase中存放的是HBase内置的表,
default表是用户默认使用的命名空间。一个表可以自由选择是否有命名空间,如果创建表的时候加上了命名空间后,这个表名字以:作为区分!
Table:
类似于关系型数据库的表概念。不同的是,HBase定义表时只需要声明列族即可,数据属性,比如超时时间(TTL),压缩算法(COMPRESSION)等,
都在列族的定义中定义,不需要声明具体的列。
Row:
HBase表中的每行数据都由一个RowKey和多个Column(列)组成。一个行包含了多个列,这些列通过列族来分类,行中的数据所属列族只能从该表所定义的列族中选取,
不能定义这个表中不存在的列族,否则报错NoSuchColumnFamilyException。
RowKey:
Rowkey由用户指定的一串不重复的字符串定义,是一行的唯一标识!数据是按照RowKey的字典顺序存储的,并且查询数据时只能根据RowKey进行检索,所以RowKey的设计十分重要。
如果使用了之前已经定义的RowKey,那么会将之前的数据更新掉!
Column Family(列族):
列族是多个列的集合。一个列族可以动态地灵活定义多个列。表的相关属性大部分都定义在列族上,同一个表里的不同列族可以有完全不同的属性配置,
但是同一个列族内的所有列都会有相同的属性。列族存在的意义是HBase会把相同列族的列尽量放在同一台机器上,
所以说,如果想让某几个列被放到一起,你就给他们定义相同的列族。
Column Qualifier(列):
Hbase中的列是可以随意定义的,一个行中的列不限名字、不限数量,只限定列族。因此列必须依赖于列族存在!列的名称前必须带着其所属的列族!
例如info:name,info:age
TimeStamp(时间戳--》版本:
用于标识数据的不同版本(version)。时间戳默认由系统指定,也可以由用户显式指定。在读取单元格的数据时,版本号可以省略,
如果不指定,Hbase默认会获取最后一个版本的数据返回!
Cell:
一个列中可以存储多个版本的数据。而每个版本就称为一个单元格(Cell)。
Region(表的分区):
Region由一个表的若干行组成!在Region中行的排序按照行键(rowkey)字典排序。Region不能跨RegionSever,且当数据量大的时候,HBase会拆分Region。
架构:
Zookeeper:
实现了HMaster的高可用
保存了HBase的元数据信息,是所有HBase表的寻址入⼝口
对HMaster和HRegionServer实现了监控
HMaster(Master):
为HRegionServer分配Region
维护整个集群HRegionServer的负载均衡
维护集群的元数据信息,查询zookeeper找到对应地址来修改。(元数据是一张Hbase的表)(元数据:记录region以及所在的regionServer信息)
发现失效的Region,并将失效的Region分配到正常的HRegionServer上
HRegionServer(RegionServer):
负责管理Region
接受客户端的读写数据请求
切分在运行过程中变大的Region,合并region
Region:
每个HRegion由多个Store构成,
每个Store保存一个列族(Columns Family),表有几个列族,则有几个Store,
每个Store由一个MemStore和多个StoreFile组成,MemStore是Store在内存中的内容,写到文件后就是StoreFile。StoreFile底层是以HFile的格式保存。
安装部署:
tar -zxvf hbase-1.3.1-bin.tar.gz -C /opt/lagou/servers
需要把hadoop中的配置core-site.xml 、hdfs-site.xml拷贝到hbase安装目录下的conf文件夹中
ln -s /opt/lagou/servers/hadoop-2.9.2/etc/hadoop/core-site.xml /opt/lagou/servers/hbase-1.3.1/conf/core-site.xml
ln -s /opt/lagou/servers/hadoop-2.9.2/etc/hadoop/hdfs-site.xml /opt/lagou/servers/hbase-1.3.1/conf/hdfs-site.xml
修改 hbase-env.sh
export JAVA_HOME=/opt/module/jdk1.8.0_231
export HBASE_MANAGES_ZK=FALSE
修改 hbase-site.xml
hbase.rootdir指定hdfs的路径。
thrift服务:
hbase.regionserver.thrift.port
hbase.thrift.info.port
修改regionservers文件
hbase的conf目录下创建文件backup-masters (Standby Master)
配置hbase的环境变量
export HBASE_HOME=/opt/lagou/servers/hbase-1.3.1
export PATH=$PATH:$HBASE_HOME/bin
分发hbase目录和环境变量到其他节点
让所有节点的hbase环境变量生效
HBase集群的启动和停止
前提条件:先启动hadoop和zk集群
启动HBase:start-hbase.sh
停止HBase:stop-hbase.sh
访问:
启动好HBase集群之后,可以访问地址:HMaster的主机名:16010
shell基本操作:
1、进入Hbase客户端命令操作界面
hbase shell
2、查看帮助命令
help
3、查看命名空间:
list_namespace
查看当前数据库中有哪些表
list
查看表结构:
desc 'table_name'
修改:
alter 'talbe' drop 'column_family'
查看表的region:
scan 'hbase:meta',{FILTER=>"PrefixFilter('table_name')"}
list_regions 'table_name' 其中REQ为rowkey的数量。
create 'lagou', 'base_info', 'extra_info'
create 'lagou', {NAME => 'base_info', VERSIONS => '3'},{NAME => 'extra_info',VERSIONS => '3'} VERSIONS 是指此单元格内的数据可以保留最近的 3 个版本
5、添加数据操作
put 'lagou', 'rk1', 'base_info:name', 'wang'
put 'lagou', 'rk1', 'base_info:age', 30
6、查询数据
get 'lagou', 'rk1'
get 'lagou', 'rk1', 'base_info'
get 'lagou', 'rk1', 'base_info:name', 'base_info:age'
get 'lagou', 'rk1', 'base_info', 'extra_info'
get 'lagou', 'rk1', {COLUMN => ['base_info', 'extra_info']}
get 'lagou', 'rk1', {COLUMN => ['base_info:name', 'extra_info:address']}
get 'lagou', 'rk1', {FILTER => "ValueFilter(=, 'binary:wang')"} 筛选
get 'lagou', 'rk1', {FILTER => "(QualifierFilter(=,'substring:a'))"} 指定rowkey与列值模糊查询
scan 'lagou' 查询lagou表中的所有记录
scan 'lagou', {'LIMIT' => 5} 查询5个rowkey
scan 'lagou', {COLUMNS => 'base_info', RAW => true, VERSIONS => 3}
scan 'lagou', {COLUMNS => ['base_info', 'extra_info'], FILTER => "(QualifierFilter(=,'substring:a'))"}
scan 'lagou', {COLUMNS => 'base_info', STARTROW => 'rk1', ENDROW => 'rk3'} rowkey的范围值查询(非常重要)
scan 'lagou',{FILTER=>"PrefixFilter('rk')",LIMIT=>5}
count 'table'
7、更新数据
put 'lagou', 'rk1', 'base_info:name', 'liang'
8、删除数据和列
delete 'lagou', 'rk1', 'base_info:name'
alter 'lagou', 'delete' => 'base_info'
truncate 'lagou'
9、删除表
disable 'lagou'
drop 'lagou'
HBase原理:
HBase读数据流程:
2)根据要查询的namespace、表名和rowkey信息。找到写入数据对应的region信息
3)找到这个region对应的regionServer,然后发送请求
4)查找对应的region
5)先从memstore查找数据,如果没有,再从BlockCache上读取
HBase上Regionserver的内存分为两个部分
一部分作为Memstore,主要用来写;
另外一部分作为BlockCache,主要用于读数据;
6)如果BlockCache中也没有找到,再到StoreFile上进行读取
从storeFile中读取到数据之后,不是直接把结果数据返回给客户端, 而是把数据先写入到BlockCache中,目的是为了加快后续的查询;然后在返回结果给客户端。
HBase写数据流程:
2)根据namespace、表名和rowkey信息。找到写入数据对应的region信息
3)找到这个region对应的regionServer,然后发送请求
4)把数据先写到HLog(write ahead log),然后根据TableName和RowKey找到对应的HRegion,根据Column Family找到HStore,存入memstore
5)memstore达到阈值后把数据刷到磁盘,生成storeFile文件
6)删除HLog中的历史数据
flush(刷写):
触发:
(1)当memstore的大小超过这个值的时候,会flush到磁盘,默认为128M
hbase.hregion.memstore.flush.size
(2)当memstore中的数据时间超过1小时,会flush到磁盘
hbase.regionserver.optionalcacheflushinterval
(3)HregionServer的全局memstore的大小,超过该大小会触发flush到磁盘的操作,默认是堆大小的40%
hbase.regionserver.global.memstore.size
(4)手动flush
阻塞机制:
Hbase中是周期性的检查(10s)是否满足以上标准满足则进行刷写,但是如果在下次检查到来之前,数据疯狂写入Memstore中,会触发阻塞机制
此时无法写入数据到Memstore,数据无法写入Hbase集群。
触发:
memstore中数据达到512MB
计算公式:hbase.hregion.memstore.flush.size*hbase.hregion.memstore..block.multiplier
hbase.hregion.memstore.flush.size刷写的阀值,默认是 134217728,即128MB。
hbase.hregion.memstore.block.multiplier是一个倍数,默认 是4。
RegionServer全部memstore达到规定值
hbase.regionserver.global.memstore.size.lower.limit是0.95,
hbase.regionserver.global.memstore.size是0.4,
堆内存总共是 16G,
触发刷写的阈值是:6.08GB 触发阻塞的阈值是:6.4GB
compact(合并)机制:
概述:
在hbase中主要存在两种类型的compac合并
minor compact 小合并:
在将Store中多个HFile(StoreFile)合并为一个HFile
这个过程中,删除和更新的数据仅仅只是做了标记,并没有物理移除,这种合并的触发频率很高。
minor compact文件选择标准由以下几个参数共同决定:
hbase.hstore.compaction.min
hbase.hstore.compaction.max
hbase.hstore.compaction.min.size 表示文件大小小于该值的store file 一定会加入到minor compaction的store file中
hbase.hstore.compaction.max.size 表示文件大小大于该值的store file 一定会被minor compaction排除
触发条件
memstore flush
在进行memstore flush前后都会进行判断是否触发compact
定期检查线程
周期性检查是否需要进行compaction操作,由参数:hbase.server.thread.wakefrequency决定,默认值是10000 millseconds
major compact 大合并:
合并Store中所有的HFile为一个HFile
这个过程有删除标记的数据会被真正移除,同时超过单元格maxVersion的版本记录也会被删除。
合并频率比较低,默认7天执行一次,并且性能消耗非常大,建议生产关闭(设置为0),在应用空闲时间手动触发。一般可以是手动控制进行合并,防止出现在业务高峰期。
触发时间条件:
自动触发
hbase.hregion.majorcompaction
手动触发
major_compact tableName
Region 拆分机制:
概述:
Region中存储的是大量的rowkey数据 ,当Region中的数据条数过多的时候,直接影响查询效率.当Region过大的时候.HBase会拆分Region , 这也是Hbase的一个优点.
推荐关闭自动拆分,不然高峰期进行拆分会影响业务。
拆分策略:
1)ConstantSizeRegionSplitPolicy
0.94版本前默认切分策略
当region大小大于某个阈值(hbase.hregion.max.filesize=10G)之后就会触发切分,一个region等分为2个region。
但是在生产线上这种切分策略却有相当大的弊端:切分策略对于大表和小表没有明显的区分。阈值(hbase.hregion.max.filesize)设置较大对大表比较友好,
但是小表就有可能不会触发分裂,极端情况下可能就1个,这对业务来说并不是什什么好事。如果设置较小则对小表友好,但一个大表就会在整个集群产生大量的region,
这对于集群的管理、资源使用、failover来说都不是一件好事。
2)IncreasingToUpperBoundRegionSplitPolicy
0.94版本~2.0版本默认切分策略
切分策略稍微有点复杂,总体看和ConstantSizeRegionSplitPolicy思路相同,一个region大小大于设置阈值就会触发切分。
但是这个阈值并不像ConstantSizeRegionSplitPolicy是一个固定的值,而是会在一定条件下不断调整,调整规则和region所属表在当前regionserver上的region个数有关系.
region split的计算公式是:regioncount^3 * 128M * 2,当region达到该size的时候进行split
例如:
第一次split:1^3 * 256 = 256MB
第二次split:2^3 * 256 = 2048MB
第三次split:3^3 * 256 = 6912MB
第四次split:4^3 * 256 = 16384MB > 10GB,因此取较小的值10GB
后面每次split的size都是10GB了
3)SteppingSplitPolicy
2.0版本默认切分策略
这种切分策略的切分阈值⼜又发生了变化,相比 IncreasingToUpperBoundRegionSplitPolicy 简单了一些,依然和待分裂region所属表在当前regionserver上的region个数有关系,
如果region个数等于1,切分阈值为flush size * 2,否则为MaxRegionFileSize。
这种切分策略对于大集群中的大表、小表会比IncreasingToUpperBoundRegionSplitPolicy 更加友好,小表不会再产生大量的小region,而是适可而止。
4)KeyPrefixRegionSplitPolicy
根据rowKey的前缀对数据进行分组,这里是指定rowKey的前多少位作为前缀,比如rowKey都是16位的,指定前5位是前缀,
那么前5位相同的rowKey在进行region split的时候会分到相同的region中。
5)DelimitedKeyPrefixRegionSplitPolicy
保证相同前缀的数据在同一个region中,例如rowKey的格式为:userid_eventtype_eventid,指定的delimiter为 _ ,则split的的时候会确保userid相同的数据在同一个region中。
6)DisabledRegionSplitPolicy
不启用自动拆分, 需要指定手动拆分
配置:
1)通过hbase-site.xml全局统一配置(对hbase所有表生效)
hbase.regionserver.region.split.policy
2)通过Java API为单独的表指定Region拆分策略
tableDesc.setValue(HTableDescriptor.SPLIT_POLICY, IncreasingToUpperBoundRegionSplitPolicy.class.getName());
3)通过HBase Shell为单个表指定Region拆分策略
create 'test2', {METADATA => {'SPLIT_POLICY' =>'org.apache.hadoop.hbase.regionserver.IncreasingToUpperBoundRegionSplitPolicy'}},{NAME => 'cf1'}
HBase表的预分区(region):
概述:
当一个table刚被创建的时候,Hbase默认的分配一个region给table。也就是说这个时候,所有的读写请求都会访问到同一个regionServer的同一个region中,
这个时候就达不到负载均衡的效果了,集群中的其他regionServer就可能会处于比较空闲的状态。解决这个问题可以用pre-splitting,在创建table的时候就配置好,生成多个region。
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护
好处:
增加数据读写效率
负载均衡,防止数据倾斜
方便集群容灾调度region
手动指定预分区:
create 'person','info1','info2',SPLITS => ['1000','2000','3000']
create 'student','info',SPLITS_FILE => '/root/hbase/split.txt'
Region 合并:
概述:
Region的合并不是为了性能,而是出于维护的目的。
方式:
通过Merge类冷合并Region
需要先关闭hbase集群
hbase org.apache.hadoop.hbase.util.Merge student student,,1595256696737.fc3eff4765709e66a8524d3c3ab42d59. \
student,aaa,1595256696737.1d53d6c1ce0c1bed269b16b6514131d0.
通过online_merge热合并Region
不需要关闭hbase集群,在线进行合并
与冷合并不同的是,online_merge的传参是Region的hash值,而Region的hash值就是Region名称的最后那段在两个.之间的字符串部分。
merge_region 'c8bc666507d9e45523aebaffa88ffdd6','02a9dfdf6ff42ae9f0524a3d8f4c7777'
HBase API应用和优化:
创建连接:
conf = HBaseConfiguration.create();
conf.set("hbase.zookeeper.quorum","linux121,linux122");
conf.set("hbase.zookeeper.property.clientPort","2181");
conn = ConnectionFactory.createConnection(conf);
创建表:
admin = (HBaseAdmin) conn.getAdmin();
HTableDescriptor teacher = new HTableDescriptor(TableName.valueOf("teacher"));
teacher.addFamily(new HColumnDescriptor("info"));
admin.createTable(teacher);
插入数据:
Table t = conn.getTable(TableName.valueOf("teacher"));
Put put = new Put(Bytes.toBytes("110"));
put.addColumn(Bytes.toBytes("info"), Bytes.toBytes("addr"), Bytes.toBytes("beijing"));
t.put(put);
t.close();
删除数据:
Delete delete = new Delete(Bytes.toBytes("110"));
worker.delete(delete);
worker.close();
Delete otherUserDelete = new Delete(rk);
otherUserDelete.addColumn(Bytes.toBytes("friends"),qualifier);
查询某个列族数据:
Get get = new Get(Bytes.toBytes("110"));
get.addFamily(Bytes.toBytes("info"));
Result res = teacher.get(get);
Cell[] cells = res.rawCells();
for (Cell cell:cells) {
String cf = Bytes.toString(CellUtil.cloneFamily(cell));
String column = Bytes.toString(CellUtil.cloneQualifier(cell));
String value = Bytes.toString(CellUtil.cloneValue(cell));
String rowkey = Bytes.toString(CellUtil.cloneRow(cell));
System.out.println(rowkey + "----" + cf + "---" + column + "---" + value);
}
通过Scan全表扫描:
Scan scan = new Scan();
scan.setStartRow("0001".getBytes());
scan.setStopRow("2".getBytes());
ResultScanner resultScanner = teacher.getScanner(scan);
for (Result result : resultScanner) {
Cell[] cells = result.rawCells();
for (Cell cell : cells) {
拿到cell获取属性
}
Hbase 协处理器:
概述:
访问HBase的方式是使用scan或get获取数据,在获取到的数据上进行业务运算。但是在数据量非常大的时候,比如一个有上亿行及十万个列的数据集,
再按常用的方式移动获取数据就会遇到性能问题。客户端也需要有强大的计算能力以及足够的内存来处理这么多的数据。
此时就可以考虑使用Coprocessor(协处理器)。将业务运算代码封装到Coprocessor中并在RegionServer上运行,即在数据实际存储位置执行,最后将运算结果返回到客户端。
利用协处理器,用户可以编写运行在 HBase Server 端的代码。
Hbase Coprocessor类似以下概念
1.触发器和存储过程:一个Observer Coprocessor有些类似于关系型数据库中的触发器,通过它我们可以在一些事件(如Get或是Scan)发生前后执行特定的代码。
Endpoint Coprocessor则类似于关系型数据库中的存储过程,因为它允许我们在RegionServer上直接对它存储的数据进行运算,而非是在客户端完成运算。
2.MapReduce:MapReduce的原则就是将运算移动到数据所处的节点。Coprocessor也是按照相同的原则去工作的。
3.AOP:如果熟悉AOP的概念的话,可以将Coprocessor的执行过程视为在传递请求的过程中对请求进行了拦截,并执行了一些自定义代码。
协处理器类型:
Observer:
协处理器与触发器(trigger)类似:在一些特定事件发生时回调函数(也被称作钩子函数,hook)被执行。这些事件包括一些用户产生的事件,也包括服务器端内部自
动产生的事件。
协处理器框架提供的接⼝口如下
RegionObserver:用户可以用这种的处理器处理数据修改事件,它们与表的region联系紧密。
MasterObserver:可以被用作管理或DDL类型的操作,这些是集群级事件。
WALObserver:提供控制WAL的钩子函数
示例:
public class MyProcessor extends BaseRegionObserver{
public void prePut(ObserverContext<RegionCoprocessorEnvironment> ce, Put put, WALEdit edit, Durability durability) throws IOException{
Table table = (HTableWrapper)e.getEnvironment().getTable(TableName.valueOf("relations"));
拿到put对象处理即可
}
}
打成Jar包,上传HDFS
cd /opt/lagou/softwares
mv original-hbaseStudy-1.0-SNAPSHOT.jar processor.jar
hdfs dfs -mkdir -p /processor
hdfs dfs -put processor.jar /processor
注意:
如果遇到tools依赖,到maven仓库下载放到jdk/lib目录下,然后添加maven system依赖即可
jar包的class file version需要和hbase的java version对应,8对应52,11对应55
挂载协处理器
alter 'relationship',METHOD =>'table_att','Coprocessor'=>'hdfs://worker1:9000/processor/processor.jar|com.czl.hbase.processor.MyProcessor|1001|'
卸载处理器
disable 'relationship'
alter 'relationship',METHOD=>'table_att_unset',NAME=>'coprocessor$1'
enable 'relationship'
Endpoint:
类似传统数据库中的存储过程,客户端可以调用这些 Endpoint 协处理器在Regionserver中执行一段代码,并将 RegionServer 端执行结果返回给客户端进一步处理
常见用途
聚合操作
假设需要找出一张表中的最大数据,即 max 聚合操作,普通做法就是必须进行全表扫描,然后Client代码内遍历扫描结果,并执行求最大值的操作。
这种方式存在的弊端是无法利用底层集群的并发运算能力,把所有计算都集中到 Client 端执行,效率低下。
使用Endpoint Coprocessor,用户可以将求最大值的代码部署到 HBase RegionServer 端,HBase 会利用集群中多个节点的优势来并发执行求最大值的操作。
也就是在每个 Region 范围内执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出,仅仅将该 max 值返回给Client。
在Client进一步将多个 Region 的最大值汇总进一步找到全局的最大值。
Endpoint Coprocessor的应用我们后续可以借助于Phoenix非常容易就能实现。针对Hbase数据集进行聚合运算直接使用SQL语句就能搞定。
RowKey设计:
RowKey长度原则:
rowkey是一个二进制码流,可以是任意字符串,最大长度64kb,实际应用中一般为10-100bytes,以byte[]形式保存,一般设计成定长。
建议越短越好,不要超过16个字节。设计过长会降低memstore内存的利用率和HFile存储数据的效率。
RowKey散列原则:
建议将rowkey的高位作为散列字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。
示例:
geohash由于前缀不常变动,可以反转后存储
RowKey唯一原则:
必须在设计上保证其唯一性
排序原则:
HBase的Rowkey是按照ASCII有序设计的,我们在设计Rowkey时要充分利用这点.
热点:
概述:
检索habse的记录首先要通过row key来定位数据行。当大量的client访问hbase集群的一个或少数几个节点,造成少数region server的读/写请求过多、负载过大,
而其他region server负载却很小,就造成了“热点”现象
解决方案:
预分区:
预分区的目的让表的数据可以均衡的分散在集群中,而不是默认只有一个region分布在集群的一个节点上。
加盐:
这里所说的加盐不是密码学中的加盐,而是在rowkey的前面增加随机数,具体就是给rowkey分配一个随机前缀以使得它和之前的rowkey的开头不同。
原始数据:abc1,abc2,abc3.
加盐后的rowkey:a-abc1,b-abc2,c-abc3
哈希:
哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。
使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据。
反转:
反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。
15X,13X,
二级索引:
概述:
HBase表按照rowkey查询性能是最高的。rowkey就相当于hbase表的一级索引!!
为了HBase的数据查询更高效、适应更多的场景,诸如使用非rowkey字段检索也能做到秒级响应,或者支持各个字段进行模糊查询和多字段组合查询等,
因此需要在HBase上面构建二级索引, 以满足现实中更复杂多样的业务需求。
hbase的二级索引其本质就是建立hbase表中列与行键之间的映射关系。
常见的二级索引我们一般可以借助各种其他的方式来实现,例如Phoenix或者solr或者ES等
hbase在flink的使用:
读取:
方式一:(每次查询)
编写HBaseAsyncLookupTableSource类,通过tEnv.registerTableSource注册为table,与其他source注册的table进行join
每一条流数据都会调用查询方法查询指定rowkey进行join
方式二:(适合小表)
继承RichSourceFunction类,run方法scan扫描hbase的表,通过env.addSource和tableEnv.createTemporaryView注册为表
然后与业务增量的表进行join
方式三:
hbase client get查询。
写入:
继承RichSinkFunction,invoke方法负责写入
hbase在spark的使用:
读取:
方式一:RDD的形式(成功执行,速度和hive sql一样,但资源占用少)
通过TableInputFormat,newAPIHadoopRDD,把HBase表里的数据读出来,转变成RDD,再做后续处理。每个region一个partition(可能会OOM,可以考虑手动拆分region)
val hBaseRDD = sc.newAPIHadoopRDD(conf, classOf[TableInputFormat], classOf[ImmutableBytesWritable], classOf[Result])
resultRDD.foreach{
case (rowkey, result) =>
for (cell <- result.rawCells()) {
val cf = Bytes.toString(CellUtil.cloneFamily(cell))
val colum = Bytes.toString(CellUtil.cloneQualifier(cell))
val value = Bytes.toString(CellUtil.cloneValue(cell))
val rowKey = Bytes.toString(key.get())
}
}
或者:
val ipDF: DataFrame = hbaseRDD.map(r => {
val province = Bytes.toString(r._2.getValue(Bytes.toBytes("i"), Bytes.toBytes("province")))
val city = Bytes.toString(r._2.getValue(Bytes.toBytes("i"), Bytes.toBytes("city")))
(Bytes.toString(r._2.getRow), province, city)
}).toDF("ip", "province", "city")
配置:
Scan scan = new Scan();
scan.addFamily(Bytes.toBytes("ba"));
scan.setMaxVersions(max_versions);
scan.setLimit(100);
hconf.set(TableInputFormat.SCAN, convertScanToString(scan));
hbaseConfig.set(TableInputFormat.SCAN_COLUMNS, "o:fname o:email"); 指定column,详细文档参考https:
依赖:
hbase-client和hbase-mapreduce(只支持2.x版本,新版本2.1.X版本的HBASE又把mapreduce.TableInputFormat单独抽取出来)
1.4版本可以用hbase-server,里面有HfileInputFormat
问题:
生产环境是1.4.9,hbase-mapreduce只有2.x版本,会报错:
/10.0.0.6:6002 is unable to read call parameter from client 10.0.0.16; java.lang.UnsupportedOperationException: GetRegionLoad
方式二:(Spark-HBase Connector)
sparkConf.set("spark.hbase.host", "<YourHostnameOnly>")
val sc = new SparkContext(sparkConf)
val docRdd = sc.hbaseTable[(Option[String], Option[String])]("Document").select("DocID", "Title").inColumnFamily("SMPL")
依赖:
it.nerdammer.bigdata spark-hbase-connector_2.10 1.0.3版本
问题:
1.会与其他依赖冲突
java.lang.IllegalAccessError: tried to access method com.google.common.base.Stopwatch.<init>
2.表找不到,可能是版本的问题
TableNotFoundException: Table 'gps_geo' was not found, got: apache_atlas_janus.
方式三:(Spark-HBase Connector,maven只有1.0.0版本,不兼容其他版本)
val sql = spark.sqlContext
val df = sql.read.format("org.apache.hadoop.hbase.spark")
.option("hbase.columns.mapping",
"name STRING :key, email STRING c:email, " + "birthDate DATE p:birthDate, height FLOAT p:height")
.option("hbase.table", "person")
.option("hbase.spark.use.hbasecontext", false)
.load()
依赖:
org.apache.hbase hbase-spark 2.0.0-alpha4
问题:
1.java运行提示org.apache.hadoop.fs.UnsupportedFileSystemException: No FileSystem for scheme "file"
解决:spark-submit提交
2.提示java.lang.ClassNotFoundException: org.apache.spark.Logging was removed in Spark 2.0. Please check if your library is compatible with Spark 2.0
解决:在自己的工程下创建了org.apache.spark.Logging,复制自org.apache.spark.internal.Logging
新的问题:
java.lang.NoSuchMethodError: org.json4s.jackson.JsonMethods$.parse
其他依赖:
org.apache.hbase.connectors.spark hbase-spark 1.0.0
报错User class threw exception: java.lang.NoSuchMethodError: scala.Predef$.refArrayOps([Ljava/lang/Object;)Lscala/collection/mutable/ArrayOps
方式四:shc版本
val df: DataFrame = sqlContext
.read
.options(Map(HBaseTableCatalog.tableCatalog -> Catalog.schema))
.format("org.apache.spark.sql.execution.datasources.hbase")
.load()
依赖:
org.apache.hbase hbase-client
org.apache.hbase hbase-spark
com.hortonworks shc-core
问题:
1. java.lang.NoSuchMethodError: org.json4s.jackson.JsonMethods
maven引入依赖后仍然报这个错。
方式五:
hbase-client
HBaseAdmin和HTable等操作,进行scan或者get都可以.
缺点:
容易OOM,时间久
方式六:(执行卡顿,还没成功读取)
hive映射表,读取hive jdbc即可。
写入:
方式一:spark-hbase-connector版本
personDS.write.format("org.apache.hadoop.hbase.spark")
.option("hbase.columns.mapping",
"name STRING :key, email STRING c:email, " + "birthDate DATE p:birthDate, height FLOAT p:height")
.option("hbase.table", "person")
.option("hbase.spark.use.hbasecontext", false)
.save()
方式二:
基于HBase API批量写入foreachPartition
table.put()
方式三: shc版本
df.write
.mode(SaveMode.Overwrite)
.options(Map(HBaseTableCatalog.tableCatalog -> catalog))
.format("org.apache.spark.sql.execution.datasources.hbase")
.save()
方式四:
可以用rdd.hbaseForeachPartition,HBaseContext 接收 HBase 配置并将它们推送到 Spark 执行器。这允许我们在静态位置为每个 Spark Executor 建立一个 HBase 连接。
底层优化:
Spark Executor 可以与 Region Servers 在同一个节点上,也可以在不同的节点上,不依赖于 co-location。将每个 Spark Executor 视为一个多线程客户端应用程序。这允许在执行器上运行的任何 Spark 任务访问共享的 Connection 对象。
rdd.hbaseForeachPartition(hbaseContext, (it, conn) => {})通过bufferedMutator将其他源RDD写入到hbase
hbaseContext.foreachPartition(rdd,new func
func里面创建BufferedMutator mutator = t._2().getBufferedMutator(TableName.valueOf(tableName));
然后mutator.mutate(new Put(b));
方式五:
官方bulk load,但maven依赖没找到(推测是org.apache.hbase hbase-spark)
val hbaseContext = new HBaseContext(sc, config)
两种方法:
hbaseBulkLoad适用于行有数百万列的情况,以及列在 Spark 批量加载过程的映射端之前未合并和分区的情况。
hbaseBulkLoadThinRows为每行少于 10k 列的表设计的。这第二个选项的优点是更高的吞吐量和更少的 Spark shuffle 操作的总体负载。
方式六:
其他源数据通过MR任务,编写mapper将每行记录转为hbase的一条记录,context.write(rowkey, kv)输出;
job.setJarByClass(GemeratorHFile2.class);
job.setInputFormatClass(TextInputFormat.class);
job.setMapperClass(HFileImportMapper2.class);
FileInputFormat.setInputPaths(job, input);
FileOutputFormat.setOutputPath(job, new Path(output));
设置HfileInputFormat2
HFileOutputFormat2.configureIncrementalLoad(job, table);
执行后HFile输出到outpath路径下
导入hbase:
代码实现导入(https://www.cnblogs.com/smartloli/p/9501887.html)
LoadIncrementalHFiles loader = new LoadIncrementalHFiles(conf);
loader.doBulkLoad(new Path(output), table);
现成jar导入:
hbase org.apache.hadoop.hbase.mapreduce.LoadIncrementalHFiles /tmp/pres person