一.前言(整体聊聊hbase,hdfs,Phoenix)

一.从HDFS角度理解HBase写:
     1.HBase的数据存储在HDFS之上,HDFS不支持随机写.因此HBase做一系列优化.
     2.因为HDFS不支持随机写,所以HBase只需要将数据写入内存即可.内存不稳定需要经WAL.
     3.内存不稳定且有限,因此HBase在合适时机将数据写入到HDFS.那什么时候存呢?由此衍生了HBase的刷写机制.
     4.刷写会带来文件数量过多问题,又衍生了文件的小合并,大合并.
     5.大合并会将数据合并为一个文件
     6.合并为一个大文件,数据量大了的话.又要根据Rowkey进行切分.
     7.切分又会带来数据热点问题.热点数据进入到同一个Region,导致单台RegionServer压力大
     8.为了解决热点数据问题,由此衍生了预分区和Rowkey设计
     
二.从HDFS角度理解HBase的读:
     1.因为HDFS不支持随机写,导致数据存在内存,HDFS等多个地方
     2.为了保证数据的正确性,需要从多个地方读数据,并进行merge,因此hbase的读比写慢
     3.读磁盘,merge导致读效率慢.因此HBase做一系列优化.
     4.使用Block Cache对数据缓存,被缓存在Block Cache的Store File在读的时候不会被扫描.只会扫描HDFS中没被缓存的Store File.
     5.扫描Store File也有优化,布隆过滤器.根据Rowkey/时间筛选 Store File
     6.读数据多了,导致Block Cache内存不够.Block Cache肯定需要选择性缓存.由此又进行了优化.即LRU算法,最近最少使用.
 
三.从Phoenix角度理解Hbase:
    1.Phoenix是HBase的开源SQL皮肤,可以使用标准的JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据。 
    2.Phoenix让Hbase的Sql化.让支持sql语句.但是hbase需要根据rowkey来筛选数据,但是Phoenix却可以实现select ... from table where 非rowkey='某某' 来查询.为了避免全表扫描. 需要建立二级索引.
    但即使建立二级索引,还是有限制.
    3.以前存在Hbase中数据,Phoenix要想对应,需要进行映射.

四.理解好hbase.需要理解好hbase的数据模型,

五.理解Block Cache:
    1.Block Cache 读缓存
    2.Hfile是Hbase自身的一种文件格式.Hflie会将会将划分为多个Block.
    3.从Block被读取的数据会被放入到缓存中.所以叫Block Cache
    4.但是Block Cache是有大小限制的,终究会被放满.-->会将Block Cache中的一些数据删除,那么删除哪些数据呢?LRU,最近最少使用:最近一段时间内最少使用的数据会被删除.

六.理解RegionServer架构:
    1.一个Master维护着多个Region Server
    2.一个Region Server维护着多个Region,而每个Region下又维护着多个Store.
    3.一个Store下维护着多个StoreFile和一个MemStore.每个Store都有自己的Memstore
    4.每个Region在一个机器上,每个Store书写数据存在同一个文件
    5.Store对应着Hbase中的一个列族.
    6.StoreFile对应着每次MemStore的Flush形成的文件

七.理解Rowkey
Hbase按rowkey字典序排序,hbase最好按rowkey检索。最好别用别的字段检索。因为hbase没有索引

八.理解HBase中删除,修改.
    1.删除修改只是先假删除和修改.假修改-->打上标签,删除标签有3种
    2.在合并实时的时候真删除
    3.总结:先假后真
    
总结:Hbase中有四大组件:
1.zookeeper:维护Hmaster高可用,存放记录Meta表的RegionServer
2.Master:维护RegionServer
    a.分配regions到每个RegionServer
    b.监控RegionServer转态
    c.Table的操作
3.RegionServer:维护Region
	a.操作数据
	b.切分Region
	c.合并StoreFile
4.HDFS
	数据实际存储的位置

二.Hbase理解篇

1.hbase简介

定义: HBase是一种分布式、可扩展、支持海量数据存储的NoSQL数据库
-- 1. HBase是什么?
    1. 分布式(可以部署到多台机器)
    2. 可扩展
    3. 支持海量数据的存储
    4. NoSQL的数据库。

-- 2. 说明:
   a、NoSQL: Not only SQL,不仅仅是一个数据库
   b、是基于谷歌的三篇论文之bigtable生成的。
   c、HBase:理解为Hadoop base
   
-- 3. 大数据框架:
   a、数据的存储:hdfs / hive / hbase
   b、数据的传输:flume / sqoop
   c、数据的计算:tez / mr / spark / flink

-- 4. 和传统数据库的差别:
   传统数据库的结构:数据库 --> 表 --> 行和列
   HBase的结构 : namespace(命名空间) --> table --> 列族 --> 行和列 --> orgion --> store
   HBase可以理解为多维的map,嵌套的map结构。

2.HBase数据模型

逻辑上,HBase的数据模型同关系型数据库很类似,数据存储在一张表中,有行有列。
但从HBase的底层物理存储结构(K-V)来看,HBase更像是一个multi-dimensional map(多维地图)。

如何理解多为地图?
底层实际存储是cell  ==>  <k,v>  ===>  <Row key-列族-列名-TimeStamp-Type,value>

2.1HBase逻辑结构

image-20200623181231019

逻辑结构:HBase切两刀
一刀:根据RowKey横着切,得到一个个Region
二刀:根据列族竖着切,得到一个个Store

明确: 
region存在同一个机器-->hdfs上对应一个文件夹
store存在同一个文件-->对应一个文件夹(嵌套在Region下的文件夹)-->数据就存到store这个文件夹里边

2.2 HBase物理存储结构

image-20200810140718245

首先明确HBase是NOSQL非关系型数据库,即k,v结构.因此底层实际存储时一个个cell.
cell  ==>  <k,v>  ===>  <Row key-列族-列名-TimeStamp-Type,value>

虽然HBase是NOSQL非关系型数据库的k,v结构,但是可以通过数学建模的思想将其kv类型建模成一个个表格
1.按照Row key范围横着切 得到Region-->hdfs上对应一个文件夹
2.按着列族竖着切  得到store-->对应一个文件夹(嵌套在Region下的文件夹)-->数据就存到store这个文件夹里边
3.store的实际存储格式是k,v:<Row key-列族-列名-TimeStamp-Type,value>

思考:
Hbase的数据存储是建立在HDFS之上,但是HDFS是不支持数据随机修改,删除的.但是HBase却支持.这是因为什么呢?
答:从图片上可以看出每个数据实际存储时,有TimeStamp和Type.
对于已经修改/标签的数据会被打上标签==>表名该数据被修改/删除.
读取数据时,删除/被修改的数据不会被读取,读取最大TimeStamp的数据.从而保证读取数据的正确性

2.3数据模型

--1. HBase表的几个概念
    1. 'namespace':命名空间,类似mysql的数据库,在HBase中默认有两个namespace:default/HBase
    hbase中存放的是HBase内置的表,default表是用户默认使用的命名空间。
    2. '列族'3. 'column'   :列,在使用时,格式为:'列族名:列名'
    4. 'row'      :行,在HBase中,行是逻辑概念上的,在物理内存中,同一行的数据很可能不在一起的。
                    "那么我们通过什么参数来判断两个数据是不是属于同一行呢"?
                    就是下面的rowkey,rowkey相同,就表示是同一行的数据。
    5. 'rowKey'   :行的标签,唯一定位行的标识             
    6. 'region'   :区域,表示多行数据,在HBase中,一个table默认是一个region
    7. 'store'    :在同一个region中,列族的个数 = store的个数,store有两种:memstore/storefile
    8. 'timeStemp':时间戳,表示数据执行的时间,每执行一次操作就会生成一个版本。
    9. 'table'    :表,可以理解为多维的map,在创建表的时候,只需要声明表名和列族就可以.
                    "非常适用于非结构化数据,不需要指定数据的格式"
    10. 'cell'    : 单元格,在表中的同一个位置'某一行的某一列位置',会有多个cell,相同的位置每修改一次,就会生成一个cell。
                    由{rowkey,列族:列名,time stamp},进行唯一标识。

-- 2. HBase集群的几个概念
    1. 'master'(操作表的):可以理解为hadoop的NM,专门负责管理小弟'regionServer',实现类为:Hmaster
    2. 'regionServer'(操作数据的):可以理解为hadoop的DM,专门负责管理region中的数据,还有两个组件:WAL'可理解为NM的内存区域的元数
    				   据'和balckcache,至于具体是干嘛的,后面写操作的时候讲,实现类为:HRegionServer
    3. 'meta'  : 元数据,zookeeper有它的地址,某一个regionServer保存着这个数据。主要有table和region所在的位置。 (regionServer中有多个region)
    4. 'zookeeper':① master的高可用 ② RegionServer的监控 ③  Region的元数据管理。
-- 3. 说明:
   在底层存储时,数据按照rowkey的字典顺序从小到大进行排列。

3.Hbase中四大进程(高视角理解)

Snipaste_2022-02-21_17-18-05````

架构角色:
1)Region Server
    Region Server为 Region的管理者,其实现类为HRegionServer,主要作用如下:
    对于数据的操作:get, put, delete;
    对于Region的操作:splitRegion、compactRegion。
2)Master
    Master是所有Region Server的管理者,其实现类为HMaster,主要作用如下:
        对于表的操作:create, delete, alter
    对于RegionServer的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,负载均衡和故障转移。
3)Zookeeper
	HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。
4)HDFS 
	HDFS为Hbase提供最终的底层数据存储服务,同时为HBase提供高容错的支持。
	
明确:Region Server,HMaster,Zookeeper,HDFS 都可以对应到集群中的进行 可以通过jps查看

从上图看出:
    1.一个Master维护着多个Region Server.
    2.一个Region Server维护着多个Region,而每个Region下又维护着多个Store.
    3.一个Store下维护着多个StoreFile和一个MemStore.每个Store都有自己的Memstore
    4.每个Region在一个机器上,每个Store书写数据存在同一个文件
    5.Store对应着Hbase中的一个列族.
    6.StoreFile对应着每次MemStore的Flush形成的文件
    7.一个RegionServer维护一个 Block cache

4.理解RegionServer

image-20220221172959722

--1. 说明:
  a、1个Region Server =  1个WAL + 1个 Block Cache + N个regoin 
  b、1个Store =  1个MemStore + N个StoreFile 
  
-- 2. Region Server
  a、是一个进程,是HBase分布式的一个节点。
  
-- 3. WAL
  a、预防日志,存储在hdfs上,向RegionServer发送请求时,首先会经过WAL,将具体的操作保存下来,然后再进行具体的操作;
  b、作用:当regionServer出现故障时,因为WAL保留了具体的操作,所以数据不会丢失。
  
-- 4. Block Cache
   a、读缓存,每次查询出的数据会缓存在BlockCache中,方便下次查询
 
-- 5. Region
   a、是一张表中的多行数据,默认是一个Region。
   
-- 6. Store
   a、对应一张表中的列族的个数。
   b、有两种,分别是MemStore 和StoreFile
   
-- 7. memstore
   a、数据是先存储在MemStore中,排好序后,等到达刷写时机才会刷写到HFile,每次刷写都会形成一个新的HFile

-- 8. StoreFile
   a、StoreFile以Hfile的形式存储在HDFS上
   b.MemStore会将内部数据进行排序,因此刷写到StoreFile的数据都是有序的
  
  
    1.一个Master维护着多个Region Server.
    2.一个Region Server维护着多个Region,而每个Region下又维护着多个Store.
    3.一个Store下维护着多个StoreFile和一个MemStore.每个Store都有自己的Memstore
    4.每个Region在一个机器上,每个Store书写数据存在同一个文件
    5.Store对应着Hbase中的一个列族.
    6.StoreFile对应着每次MemStore的Flush形成的文件
    7.一个RegionServer维护一个 Block cache

3.从读/写流程角度理解Hbase架构

3.1写流程

image-20220221173245528

先理解:
Hbase中元数据是Hbase维护的,本质也是数据->region->regionServer维护
RegionServer:维护Region的
HMaster:维护RegionServer的
RowKey按字典序排序:Region按照RowKey切--->每个Region内部是RowKey有序的

思考1:
客户端往HBase中写数据,肯定的找Store吧?去哪找Store呢?去Region中找.
去哪找Region呢?去RegionServer中找.问题是去哪找RegionServer呢?
还记HBase的默认两个namespace吧! namespace为hbase中有一张meta表.通过meta表可以找到RegionServer
那么问题又来了,Meta表是啥?他也是张表啊,他也在RegionServer中维护.我怎么知道Meta表在哪个RegionServer中呢?
你不知道但是Zookeeper知道啊.因此你需要找zookeeper啊.
那么问题又来了,访问zookeeper需要建立通信连接,总不能我每次写数据都链接zookeeper吧.
那怎么办呢?我可以在客户端建立Meta cache啊,meta表信息在cache有就用cache,没有我再去找zookeeper.

思考二:
客户端向HBase中写数据,可以随机写.但是HDFS不支持啊?那我怎么随机写呢?
肯定写到内存啊.所以MemStore(写缓存)就出来了.
但是写缓存不稳定啊,怎么办呢?我可以添加预写日志(WAL)啊.


至此已经引入了所有写流程中的组件.总结下来就是Client往客户端写数据,只需要写入到内存,因此Hbase的写比读快.

思考三:虽然我们有Wal但是我们不能一直将数据保存到MemStore中把?
肯定不可能啊.Hbase会在适当时机将MemStore中刷写到HDFS中.至于什么时候刷写我们下边聊.
总结下来:
--1. 具体流程:
    1. Client先访问zookeeper,请求hbase:meta元数据位于哪个Region Server中。
    2. zookeeper返回hbase:meta所在的Region Server地址
    3. client访问对应的Region Server,请求hbase:meta元数据信息
    4. Region Server返回meta元数据信息
    5. client根据写请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。
       并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
    6. 与目标Region Server进行通讯;
    7. 将数据顺序写入(追加)到WAL;
    8. WAL将数据写入对应的MemStore,数据会在MemStore进行排序;
    9. 完成写数据操作以后,向客户端发送ack;
    10. 等达到MemStore的刷写时机后,将数据刷写到HFile。

-- 2. hbase:meta包含哪些信息呢?
   命令:scan ‘hbase:meta’
   a、rowkey: test,,1592911245995.c42a3b247c7ed78f071
               1) test:namespace,命名空间
               2) ,,:startkey endkey,起止的rowkey
               3) 1592911245995:time stamp ,时间戳
               4) .c42a3b247c7ed78f071:前面3个参数的MD5值。
   b、column=info:regioninfo : region的信息,
                               value={ENCODED => c42a3b247c7ed78f071f60721bad78ad, NAME => 'test,,
 f60721bad78ad.1592911245995.c42a3b247c7ed78f071f60721bad78ad.', STARTKEY => '', ENDKEY => ''}
   c、column=info:seqnumDuringOpen :序列号,value=\x00\x00\x00\x00\x00\x00\x00\x02 
   d、column=info:server : 该region所在的server,value=hadoop106:16020
   e、column=info:serverstartcode : 该region所在的server创建的时间戳,value=1592887276902
   f、column=info:sn    : value=hadoop106,16020,1592887276902
   g、column=info:state :该region的状态,value=OPEN 

image-20200623205526788

3.2MemStore flush的4个时机

思考下边问题先清楚:RegionServer结构:
一个RegionServer中有多个Region.一个Region中有多个Store.
每个Store有一个MemStore(写缓存),多个StoreFIle.

且store对应着 Hdfs上的列族.体现在hdfs上是不同的文件
不同的Store 刷写的StoreFile.放在不同的文件夹下==>即实现了隔离存储==>这样好处是避免全表扫描.
因为:get 只需要找到列族对应的列,即可减少扫描


思考:都知道HBase有Flush操作:但将MemStore中数据flush到HDFS中是怎么刷写的呢?
是将MemStore中数据追加写到HDFS中,还是将写一个新的文件?
答:每次刷写都会有一个新的文件产生===>因此StoreFlie是有多个的.

思考:随着flush导致StoreFlie个数增多.难道让HFDS中保存这么多文件吗?
肯定不会啊.肯定需要将StoreFile进行合并.====>因此就出现了HBase的两次合并
StoreFile会变多也会变少.==>变少是因为合并

3.2.1Flush分类.

1.手动刷写

命令行演示flush

Hbase数据在HDFS上位置:/hbase/data/default/stu/61d3bfb286574ffa15b7c205ffd404f8/info
/hbase/data/namespace/tableName/region/列族/
put 'stu','1001','info:name','lds'
put 'stu','1001','info:sex','man'
put 'stu','1001','info:address','beijing'
此时去hdfs上看没有文件,然后手动刷写
hbase(main):006:0> flush 'stu'
会看到一个文件

put 'stu','1002','info:name','lisi'
put 'stu','1002','info:sex','female'
put 'stu','1002','info:address','shanghai'
flush 'stu'
又会多一个文件

时刻记住:一个RegionServer中有个多个Region,每个Region中有多个Store.每个Store有一个MemStore和多个StoreFile(因为MemStore多次刷写)
store对应一个列族.
2.Hbase自身刷写(4个级别)
-- MemStore刷写时机:
  	如下一共有4种刷写实时机,任何一个时机点到达后均会进行刷写,刷写分为:
 	a、刷写:memStore向storeFile中写数据。
 	b、阻止写:阻止客户端往region中写数据。(什么情况下会出现,阻止写呢?client往Hbase写入数据速度,远大于MemStore往StoreFile中写入速度)
2.1 Region级别
-- 时机1:region级别,刷写方式:当前region中所有memstore都刷写
    a、'刷写':当某个memstroe的大小达到了'hbase.hregion.memstore.flush.size'(默认值128M),
               时,该region中所有memstore都会刷写。()
               '每一个region有多个store,一个store包含了一个memStore和多个storeFile'
    b、'阻止写':当memstore的大小达到了'hbase.hregion.memstore.flush.size(默认值128M)
                                    * hbase.hregion.memstore.block.multiplier(默认值4)'
               时512MB,会阻止客户端继续往该memstore写数据。
2.1RegionServer级别
-- 时机2: regionServer级别,刷写方式:按照所有memstore的数据大小顺序(由大到小)依次进行刷写
    a、'刷写':当regionserver中memstore的总大小达到
              'java_heapsize*hbase.regionserver.global.memstore.size(默认值0.4)
              *hbase.regionserver.global.memstore.size.lower.limit(默认值0.95)',
              即 0.38 * JVM堆内存
              
              region会按照其所有memstore的大小顺序(由大到小)依次进行刷写。
              直到region server中所有memstore的总大小减小到上述值以下。
              
    b、'阻止写':当region server中memstore的总大小达到'java_heapsize*hbase.regionserver.global.memstore.size'
               (默认值0.4)时,会阻止继续往所有的memstore写数据。
2.3时间级别
-- 时机3:时间级别:刷写方式:默认1H进行刷写               
    a、'刷写':到达自动刷写的时间,也会触发memstore flush。自动刷新的时间间隔由该属性进行配置
            'hbase.regionserver.optionalcacheflushinterval(最后一条数据到达后的1个小时).
2.4关闭Hbase
 -- 时机4:关闭hbase时,刷写方式:进行刷写
    a、'刷写':关闭hbase即进行刷写。

3.3 StoreFile的两次合并(Compaction)

image-20200623225525470

为什么需要合并

思考:随着flush导致StoreFlie个数增多.难道让HFDS中保存这么多文件吗?
肯定不会啊.肯定需要将StoreFile进行合并.====>因此就出现了HBase的两次合并
storeFile越多,读的时候扫描的越多.也就越慢==>因此需要合并
StoreFile会变多也会变少.==>变少是因为合并
  
--1. StoreFile文件合并的原因  
1. memstore每次刷写都会生成一个新的HFile。--小文件 
2. 同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile中  
3. 为了减少HFile的个数  
4. 清理掉过期和删除的数据

由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。
为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction。

合并时候都干些什么?

清理掉过期和删除的数据

说明:  
a、什么是过期的数据?     
    table中每一个位置有保留的版本数,同一位置进行多次数据修改时,就会有多个版本,在flush之前,数据不会丢失,在落盘时,只会保留对应版本数量的最新的数据,超过版本数量的数据就属于过期数据。 
b、什么是删除的数据?     
	使用delete删除数据时,数据并不是真正的删除,而是给数据打一个标识,当落盘时,这些数据就会被过滤和删除。  

合并分类

1.小合并:compact 'tableName'
    Minor Compaction,小合并,临近的若干个较小的HFile合并成一个较大的HFile,并清理掉部分过期和删除的数据。默认是3个文件  
    为什么是部分:因为小合并的时候,合并的是部分文件==>Hbase不确定有些数据是否没用了,所以只会清零部分数据
    什么时候小合并呢?每个flush的时候都会有可能小合并.(具体没研究)


2.大合并:major_compact 'tableName'
Major Compaction,大合并,将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉所有过期和删除的数据。
大合并什么时候发生呢?周期性进行,默认7天. 
有个抖动参数:hbase.hregion.majorcompactor.jitter.在7天左右都有可能.但是实际生产中不会让他们大合并.因为不确定什么时候会发生大合并,占用集群资源.
但是数据越堆越多,总归需要大合并啊,怎么办呢?自己手动大合并.

3.4 读流程

image-20200623223543002

Hbase的读比写慢,因为读的时候,一定要扫描磁盘.

思考:读数据
读数据首先的找到Region所对应的RegionServer.找RegionServer就得找Meta表->找Zookeeper

思考因为1.HDFS不支持随机写和2.Hbase写入数据时候,可以指定时间戳的原因,导致HBase中存放数据的位置有很多,那我们读哪些位置呢?
答:全部都读.

全部都读的话,同一个RowKey会有很多版本,怎么呢?
答:merge合并

还真么多读的位置,还要merge,HBase的读取效率的多慢啊?
HBase肯定做了优化啊.做了哪些优化呢?
1.Block Cache
2.读HDFS时.会根据时间范围,RowKey范围,以及布隆过滤器进行优化.

读的位置有很多:
1.并不是先读内存,在读磁盘.因为hbase写入数据时候,可以指定时间戳.
例如:先写入数据A,已经被flush到磁盘.后写入数据B并指定时间戳小. 假如先读内存,导致读出来B====>错误数据
2.因此读MemStore,Hfile(Block cache没扫描过的Hfile),Block cache.3个都读
3.HDFS时.有很多优化.被读的HFile,都是Block Cache没缓存的数据.

读流程

-- 整体的流程和写的流程大致一致,主要的区别在于:
1. 读的位置有很多位置
      a、block cache
      b、memStore
      c、StoreFile
2. 不同的位置,均会有数据,但是数据的版本可能不一样,所以当客户端读取多个版本数据时,可能每个位置都需要读取。(存放数据时,可以指定时间戳.有可能memstore中数据时间戳比storeFile中小,所以需要全部)
3. 读取到每个位置的数据以后,然后进行merge,将数据进行合并,最后发送给客户端。
4. 然后把StoreFile中读取的数据缓存到block cache中.下次block file只会去缓存没有新刷写进hbase的数据.之前的不会读,因为之前刷写的都在一个文件里边了,这个文件被读取过,而且不会改变了.所以不读了. 缓冲内存的大小'默认大小为64kb'

3.5Merge优化(布隆过滤器)

image-20200623223736145

布隆过滤器

Snipaste_2022-02-21_19-09-52

优化:
1.Hfile存储的是一定时间范围.一定RoWkey范围的.所以读数据时候会根据RowKey/时间范围过滤,通过时间范围/rowkey范围不在里边的storefile不会被扫描.
2.布隆过滤器:告诉Hbase,这个文件中没有你想要的数据.怎么实现的呢?
布隆过滤器:只能告诉你这个文件中不存在你想要的数据,不能告诉你是否存在.
数据在往文件里边存储时.会经过几次算法计算.将维护的数组位置表示为1;
读数据时,就不需要扫描一遍全文件.只需要在经过相同算法计算,看对应数组为位置是否全部为1.
如果全部为1==>可能存在这个数据 否则:一定不存在
(MemetoreHfile被刷写前,会进行排序.所以,RowKey有序,但是并不一定连续)
4.merge:返回版本大的数据

4.Region Split

4.1为什么split
为什么要Region split
默认情况下,每个Table起初只有一个Region,随着数据的不断写入,Region会自动进行拆分。刚拆分时,两个子Region都位于当前的Region Server,但处于负载均衡的考虑,HMaster有可能会将某个Region转移给其他的Region Server。
Hbase可以支持超级大表,但是随着数据增多,需要进行切分.这样负载性更好.

热点问题:就是我们有许多Region,但是数据只往某一个Region中写.从而导致热点Region问题.
BusyRegionSplitPolicy.

--1. Region基本介绍
    1. 默认一个Table,只有一个Region
    2. 随着数据的不断写入,Region会自动进行拆分,'如果设定了region分区的规则,那么这个默认切分规则不会有效'
    3. 拆分以后,HMaster有可能会将某个Region转移到其他的Region Server,涉及到数据的迁移,IO


    hbase.hregion.max.filesize('默认是10G')分裂

4.2 Region的split时机

--2. Region切分的时机
   '情况1':0.94版本之前
   当1个region中的某个Store下所有StoreFile的总大小超过hbase.hregion.max.filesize,该Region就会进行拆分。
   '情况2':0.94版本之后
    2.当1个region中的某个Store下所有StoreFile的总大小超过Min(initialSize*R^3 ,hbase.hregion.max.filesize),该Region就会进行拆分。其中initialSize的默认值为2*hbase.hregion.memstore.flush.size,R为当前Region Server中属于该Table的Region个数
    -- 10G以前,按照取最小值进行切分,10G以后,就每10G切分一次。
    具体的切分策略为:
    第一次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':Hbase 2.0
    如果当前RegionServer上该表只有一个Region,按照2 * hbase.hregion.memstore.flush.size('默认是128M')分裂,否则按照

5.HBase优化

5.1预分区

问什么预分区
Region数据量大之后,会自动进行切分,但是容易导致数据热点问题.
那么我为什么不一开始就去指定这个table有多少Region,每个Region的RowKey范围是多少呢?我将RowKey均分给每个Region,就不会导致数据热点问题了.一个Region对应一个机器
理想很丰满,现实很骨干.我怎么去设计多个Region,以及每个Region中RowKey范围呢?
因此引入了RowKey的设计.

1. 通过预分区的方式,使添加的数据进入指定的region中,提前进行数据分区。

2. 当指定预分区的规则以后,那么默认的region split分区原则则不会有效。

3. 每一个region维护着一个startkey和endkey,新增的数据,根据数据的rowkey判断进入哪个region,比较的方式:按照字典的顺序。

4. 在实际开发中,基本上所有的表都会进行预分区,且经常是第一个或者是最后一个region中是没有数据。
5.2如何预分区
5.2.31手动设定预分区
hbase> create 'staff1','info','partition1',SPLITS => ['1000','2000','3000','4000']
				表       列族				 每个Region范围[-∞,1000],[1000,2000],[2000,3000],[3000,4000][4000,+∞]''
				预分区只跟Rowkey有关,为什么还需要指定列族呢?因为创建table的时候,必须指定列族
5.2.2生成16进制序列预分区
create 'staff2','info','partition2',{NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
5.2.3 按照文件设置预分区
#文件内容
aaaa
bbbb
cccc
dddd
create 'staff3','partition3',SPLITS_FILE => 'splits.txt'
文件地址:1.相对于启动命令行(hbase shell) 2.写liunx绝对路径

5.2RowKey设计

5.2.1 Rowkey设计原则
散列,唯一,长度小
RowKey结合预分区是最好的

-- 1. 现状
    在hbase中,rowkey是唯一区分数据进入哪个region中,如果region设计合理,那么有可能导致数据冗余和数据倾斜。

-- 2. 设计规则
 设计:数据量大,大几十个分区.
    1. 长度:
        a、rowkey理论字节数在10-100字节之间最好,一是可以表述较多的数据内容,其次是数据不会过多;
        b、数据长度最好是2的n次幂;
        c、rowkey的数据长度最好相同;
        能满足业务需求情况下,RowKey越短越好.为什么呢?因为数据在底层是K-v存储的.每个cell都包含rawkey,冗余存储.因此不要太长.
    2. 散列:
        a、对rowkey进行散列,采用md5,hash等方式,以防止数据倾斜;
    3. 唯一性:防止rowkey相同。
    
-- 3. 实现方式:
      a、字符串反转。如:
      20170524000001转成10000042507102
      20170524000002转成20000042507102
      b、字符串连接:将本来放进列的数据,放到rowkey中,主要是将经常需要使用到的字段/不发生改变的数据拼接到rowkey中。
       如:rowkey:id_name_age
      c.加盐
      20170524000001_a12e
	  20170524000001_93i7     
5.2.2Rowkey设计演示
 场景:
 大量运营商的通话数据,每行数据都这样.我们怎样设计RowKey方便我们快速查询?
 1388888888(主叫)  139999999(主叫) 2019-07-26 12:12:12 360....
 业务:查询某个用户,某天,某月,某年的通话记录
 分区:预计规划50个分区,
 分析:
 如果我把某个用户,某天的数据作为Rawkey放到一个分区中==>当我们查某个用户,某天的数据则查一个分区就可以
 但是我要查某月的数据,那么就需要扫描所有分区.
  假如我把某个用户,某月的数据作为Rawkey放到一个分区中==>当我们查某个用户,某月,某天的数据则查一个分区就可以 可取
那么我该如何设计RowKey和预分区来实现上述方案呢?

RowKey: 1388888888_2019-07-timestamp-->1388888888_2019-07 % 分区数 =01
		1377777777_2019-07-timestamp-->1377777777_2021-07 % 分区数 =03
		01_1388888888_2019-07-timestamp
		03_1377777777_2019-07-timestamp
 预分区:
-∞,01|
01|,02|
02|,03|
...
验证:
查询 1388888888 用户 2020年08月的通话记录
1.先计算分区号 1388888888_2020-08 % 50 =04
2.RowKey 04_1388888888_2020-08-timestamp
3. Hbase执行查询时会scan
scan 'tableName',{STARTROW=>'04_1388888888_2020-08' STOPROW=>'04_1388888888_2020-08|'}

5.3 内存优化

HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16~36G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死

5.4 基础优化

1.Zookeeper会话超时时间

hbase-site.xml

属性:zookeeper.session.timeout
解释:默认值为90000毫秒(90s)。当某个RegionServer挂掉,90s之后Master才能察觉到。可适当减小此值,以加快Master响应,可调整至60000毫秒。

**2.**设置RPC监听数量(读写操作都需要经过RegionServer,读写多时,需要调大该值)

hbase-site.xml
属性:hbase.regionserver.handler.count  
解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。  

**3.**手动控制Major Compaction(手动大合并)

hbase-site.xml

属性:hbase.hregion.majorcompaction

解释:默认值:604800000秒(7天), Major Compaction的周期,若关闭自动Major Compaction,可将其设为0

**4.**优化HStore文件大小(默认最大是10G,因为Region在10G会切分.)

hbase-site.xml

  属性:hbase.hregion.max.filesize  解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。

5.优化HBase客户端缓存(每次获取数据,返回的大小.比如scan50M,这里设置2M,则每次只能返回2M)

hbase-site.xml

  属性:hbase.client.write.buffer  解释:默认值2097152bytes(2M)用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。  

6.指定scan.next扫描HBase所获取的行数(scan数据返回时,并不是一次性把结果返回.因为要scan的结果要放到内存,scan的结果集过大会oom.因此是扫描部分,返回部分,然后继续扫描)

hbase-site.xml

  属性:hbase.client.scanner.caching  解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。

7.BlockCache占用RegionServer堆内存的比例

hbase-site.xml

属性:hfile.block.cache.size

解释:默认0.4,读请求比较多的情况下,可适当调大

8.MemStore占用RegionServer堆内存的比例

hbase-site.xml

属性:hbase.regionserver.global.memstore.size

解释:默认0.4,写请求较多的情况下,可适当调大

三.HBase命令行操作

1.启动/进入

1.启动/关闭HBase集群

start-hbase.sh
stop-hbase.sh
前提是;配置了环境变量

2.进入hbase的客户端

hbase shell

3.特殊命令

--1. 刷写: flush 'tableName'
--2. 防止退出hbase客户端:q
--3. 大合并:major_compact 'tableName'
--4. 小合并:compact 'tableName'
--5. scan查询的时候:scan 'student',{COLUMNS => 'info:age:toLong'}
     ROW                             COLUMN+CELL                                                                          1002                           column=info:age, timestamp=1593192755078, value=10  

2.操作NameSpace

1.创建namespace

hbase(main):003:0> create_namespace 'lds'

2.查询namespace

hbase(main):039:0> list_namespace
默认有2个:default,hbase

3.删除namespace

hbase(main):049:0> drop_namespace 'lds'

如果namespace下有表,删除不掉
 Only empty namespaces can be removed. Namespace GMALL_210726 has 17 tables
-- 说明:在删除namespace之前,需要先删除namespace的表,删除表时,需要先将表置于不可用的状态。

4.查询指定namespace的所有表

hbase(main):044:0> list_namespace_tables 'lds'

3.操作Table

default的namespace可以不需要指定namespace.

自定义namespace下操作,需要指定namespace

1.创建表(HBase中创建表,也就是创建列族)

hbase(main):012:0> create 'lds:test','first'

create 'namespace:table' ,'列族1','列族2'
建表需要先把namespace创建好
没有MySQL那种use database操作.建表需要先指定namespace.
在default的namespace下,可以不用指定namespace

可以指定版本个数,版本个数:告诉hbase在清理过期数据的时候,保留最新的几个版本数据,默认是1

2.查看Hbase中所有表

hbase(main):001:0> list

结果如下:
TABLE   
lds:test  #lds是一个namespace
user       #没有写命名空间(namespace)的,则默认是default
2 row(s)
Took 0.4410 seconds 

3.查看指定表信息

hbase(main):019:0> describe 'lds:test3'
简写:
hbase(main):020:0> desc 'lds:test3'

显示结果:
{NAME => 'f1', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CEL
LS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0'
, REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOO
MS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE 
=> '65536'}                                                                                                       
{NAME => 'f2', VERSIONS => '1', EVICT_BLOCKS_ON_CLOSE => 'false', NEW_VERSION_BEHAVIOR => 'false', KEEP_DELETED_CEL
LS => 'FALSE', CACHE_DATA_ON_WRITE => 'false', DATA_BLOCK_ENCODING => 'NONE', TTL => 'FOREVER', MIN_VERSIONS => '0'
, REPLICATION_SCOPE => '0', BLOOMFILTER => 'ROW', CACHE_INDEX_ON_WRITE => 'false', IN_MEMORY => 'false', CACHE_BLOO
MS_ON_WRITE => 'false', PREFETCH_BLOCKS_ON_OPEN => 'false', COMPRESSION => 'NONE', BLOCKCACHE => 'true', BLOCKSIZE 
=> '65536'}                                                                                                        
2 row(s)
Took 0.0261 seconds 
解析:NAME是列族名 Version是建表时候指定可以保存几个版本.默认一个版本
{NAME => 'f1', VERSIONS => '1'...>
{NAME => 'f2', VERSIONS => '1'...>

3.修改/添加/删除表的列族

hbase(main):024:0> alter 'lds:test',NAME=>'first',VERSIONS=>'3'
将lds:test的first列的版本改为3
hbase(main):025:0>alter 'lds:test3',{NAME=>'f1',VERSIONS=>'3'},{NAME=>'f2',VERSIONS=>'3'}
将lds:test3的f1,f2列的版本都改为3
hbase(main):026:0> alter 'lds:test3',NAME=>'f3',VERSIONS=>'6'
在lds:test3中添加f3列,并将其版本设置为6
hbase(main):027:0> alter 'lds:test3','delete'=>'f3'
删除lds:test3中f3列
返回结果:
Updating all regions with the new schema...
1/1 regions updated.
Done.
Took 1.9594 seconds 

4.删除表

-- 1. 语法: drop 'tablename'
-- 2. 实例: hbase(main):028:0> drop 'lds:test'

显示结果:
ERROR: Table lds:test is enabled. Disable it first.

Drop the named table. Table must first be disabled:
  hbase> drop 't1'
  hbase> drop 'ns1:t1'

Took 0.0103 seconds 
-- 3. 说明:
     如果直接drop表,会报错:ERROR: Table student is enabled. Disable it first
      a、在删除表时,需要手动将表设置为:disable 'tablename'
      b、然后再删除,drop 'tablename'
      
正确做法:
hbase(main):030:0> disable 'lds:test'
Took 0.4836 seconds                                                                                                
hbase(main):031:0> drop 'lds:test'
Took 0.2593 seconds 
假如我diable 'tablename'之后,不想删除.可以enable 'tablename'

4.操作数据(DML)

4.1添加/修改

1.添加/修改数据(修改数据其实就是put,并没有真正修改,只是加了一条数据.老版本数据Hbase会在合适时机清空)

-- 1. 语法: put 'tablename','rowkey','列族1:列名''value'      说明:shell操作每次只能添加一个值
也可以put时候指定时间戳,一般不这么用,列可以随便指定
-- 2. 实例:  
put 'stu','1001','info:name','lds'
put 'stu','1001','info:sex','man'
put 'stu','1001','info:address','北京'

修改

hbase(main):066:0> put 'stu','1001','info:address','beijing'

4.2查询

1.获取逻辑结构一行数据(只能根据row key获取某行数据,因此row key设计非常重要)

-- 1. 语法: put 'tablename','rowkey','列族1:列名''value'      说明:shell操作每次只能添加一个值
也可以put时候指定时间戳,一般不这么用,列可以随便指定
-- 2. 实例:  
put 'stu','1001','info:name','lds'
put 'stu','1001','info:sex','man'
put 'stu','1001','info:address','北京'

2.全局扫描数据(显示最新的版本)

-- 1. 语法: scan 'tablename'
hbase(main):054:0> scan 'stu'
ROW                           COLUMN+CELL                                                                          
 1001                         column=info:address, timestamp=1645338960239, value=\xE5\x8C\x97\xE4\xBA\xAC         
 1001                         column=info:name, timestamp=1645338628088, value=lds                                 
 1001                         column=info:sex, timestamp=1645338955160, value=man                                  
 1002                         column=info:address, timestamp=1645339221957, value=shanghai                         
 1002                         column=info:name, timestamp=1645339221893, value=lisi                                
 1002                         column=info:sex, timestamp=1645339221930, value=female                               
 1003                         column=info:address, timestamp=1645339222044, value=shengzhen                        
 1003                         column=info:name, timestamp=1645339221995, value=wangwu                              
 1003                         column=info:sex, timestamp=1645339222018, value=man                                  
 1004                         column=info:address, timestamp=1645339290058, value=shengzhen                        
 1004                         column=info:age, timestamp=1645339291845, value=30                                   
 1004                         column=info:name, timestamp=1645339290010, value=wangwu                              
 1004                         column=info:sex, timestamp=1645339290033, value=man 

3.扫描指定行的数据(显示最新的)

-- 1. 语法:scan 'tablename',{STARTROW=>'rowkey',STOPROW=>'rowkey'}
-- 2. 说明:
           a、区间为左闭右开
           b、如果STARTROW没有写,则表示开始的rowkey为负无穷,即没有下限,只有上限
              如果STOPROW没有写,则表示结束的rowkey为正无穷,即没有上限,只有下限
           c.字典序中:!为最小(空格最小,但是你看不见) |临界最大值(倒数第三)
           
-- 3. 实例:hbase(main):012:0> scan 'stu',{STARTROW=>'1001',STOPROW=>'1003'}
            hbase(main):014:0> scan 'stu',{STARTROW=>'1000'}
            hbase(main):015:0> scan 'stu',{STOPROW=>'1001'}
            
hbase(main):055:0> scan 'stu',{STARTROW=>'1001',STOPROW=>'1003'}
ROW                           COLUMN+CELL                                                                          
 1001                         column=info:address, timestamp=1645338960239, value=\xE5\x8C\x97\xE4\xBA\xAC         
 1001                         column=info:name, timestamp=1645338628088, value=lds                                 
 1001                         column=info:sex, timestamp=1645338955160, value=man                                  
 1002                         column=info:address, timestamp=1645339221957, value=shanghai                         
 1002                         column=info:name, timestamp=1645339221893, value=lisi                                
 1002                         column=info:sex, timestamp=1645339221930, value=female                               
2 row(s)
Took 0.0425 seconds 

3.查看多个版本数据

scan 'tablename',{RAW=>true,VERSIONS=>个数}
scan 'stu',{RAW=>true,VERSIONS=>5}

 1001                         column=info:address, timestamp=1645340381289, value=beijing                          
 1001                         column=info:address, timestamp=1645338960239, value=\xE5\x8C\x97\xE4\xBA\xAC (北京)

4.获取行的数量(rowkey个数)

-- 1. 语法: count 'tablename'
-- 2. 实例: hbase(main):001:0> count 'stu'

4.3删除

4.3.1delete

删除某行某列的某个版本数据(不指定删除,最新版本数据,删除最新版数据之后,查询会显示还存在的旧的数据)

-- 1. 语法: delete 'namespace:tablename','rowkey''列族1:列名',ts(时间戳->可以定位版本)

实例:
 1001                         column=info:address, timestamp=1645340381289, value=beijing                          
 1001                         column=info:address, timestamp=1645338960239, value=\xE5\x8C\x97\xE4\xBA\xAC  
 删除1645338960239版本的数据.
 delete 'stu','1001','info:address' ,1645338960239
 结果(打上delete标记)
  1001                         column=info:address, timestamp=1645338960239, type=Delete                            
 1001                         column=info:address, timestamp=1645338960239, value=\xE5\x8C\x97\xE4\xBA\xAC

删除某一行某一列(最新版本)的值

-- 1. 语法: delete 'tablename','rowkey''列族1:列名'
-- 2. 实例: hbase(main):002:0> hbase(main):005:0> delete 'user','1001','info1:age'
-- 3. 说明:
      a、一个位置默认保留一个版本,如果一个位置被多次修改时,删除当前的数据,再进行查询时,在未flush的情况下,上一个版本的数据
         可以被查询出来。
      b、此处的删除并不是真正的删除,只是给这个删除的数据打上了一个标记,只有当落盘flush的时候,才会真正的被清理掉。
4.3.2 deleteall

删除某行某列的所有版本数据(打上DeleteColumn标记)

deleteall 'tablename','rowkey','列族:列'
deleteall 'stu','1002','info:name'

 1002                         column=info:name, timestamp=1645342419632, type=DeleteColumn

删除某一行的全部数据的全部版本(打上DeleteFamily标记)(deleteall的shell脚本不能删除列族)

-- 1. 语法: deleteall 'tablename','rowkey'
-- 2. 实例: hbase(main):020:0> deleteall 'stu','1004'

 1004                         column=info:, timestamp=1645342823375, type=DeleteFamily                             
 1004                         column=info:address, timestamp=1645339290058, value=shengzhen                        
 1004                         column=info:age, timestamp=1645339291845, value=30                                   
 1004                         column=info:name, timestamp=1645339290010, value=wangwu                              
 1004                         column=info:sex, timestamp=1645339290033, value=man 
4.3.3 truncate

清除表数据(直接到hdfs上删除,真正的立即删除)

-- 1. 语法: truncate 'tablename'
-- 2. 实例:hbase(main):020:0> truncate 'stu'
-- 3. 说明:
      a、在执行的过程中,首先会自动disable 'stu'
      b、然后再清空表数据,truncate 'stu'
 -- 4. 打印结果
Truncating 'stu' table (it may take a while):
Disabling table...
Truncating table...
Took 1.2399 seconds  

四.Phoenix

4.1.Phoenix简介

为什么使用Phoenix:
OLTP:
hbase没有事务,但是使用Phoenix就会有事务.
二级索引
-- 1. Phoenix是什么?   可以理解是HBase的开源SQL皮肤,可以使用标准的JDBC API代替HBase客户端API来创建表,插入数据和查询HBase数据。   
-- 2. Phoenix中创建的表存储在HBase中。

4.2Phoenix特点

1. 容易集成:如Spark 、 Hive 、 Pig 、 Flume 、 MAPReduce
2. 操作容易:DML命令以及通过DDL命令来对表进行操作3. 支持HBase的二级索引创建。

4.3 Phoenix架构

image-20200628213131835

4.4 Phoenix命令行操作

--说明:  1、 在Phoenix创建的表,都是存储在hbase中; 
2、 Phoenix表的主键,在hbase表中,相当于rowkey,所以在Phoenix建表时,至少设置一个主键,且数据类型最好设置为varchar; 
3、 在Phoenix创建表时,表名和字段自动会变成大写,如果想要设置为小写,那么需要使用'双引号';  
4、 '单引号'表示字符串;  
5、 在HBase创建的表,默认在Phoenix是查询不到的,需要通过映射的方式可以查询。 
6、 Phoenix中不支持insert语法,使用了'upsert'代替;  
7、 Phoenix的shell操作,很多和sql语法相似,但是也有些语法时不支持的;  
8、 Phoenix创建的表,在hbase默认只有一个列族'0';

0.进入客户端

/opt/module/phoenix/bin/sqlline.py hadoop102,hadoop103,hadoop104:2181

1.创建schema(对应mysql中数据库)

在phoenix中,表名,schema,字段名等会自动转换为大写,若要小写,使用双引号,如"us_population"。

create schema if not exists mydb;

2.删除schema

drop schema if exists mydb

3.显示所有schema下所有表

!table 或 !tables

4.创建表

create table lds (id varchar primary key , age varchar )
必须的有主键
思考:hbase中创建table,必须指定列族,那这个地方,我没有指定列族.那hbase中怎么处理的呢?
Phoenix会在hbase中通过0,1,2来表示你的列族.没写默认会有一个0号列族.
  • 联合主键
指定多个列的联合作为RowKey
CREATE TABLE IF NOT EXISTS us_population (
    State CHAR(2) NOT NULL,
    City VARCHAR NOT NULL,
    Population BIGINT
    CONSTRAINT my_pk PRIMARY KEY (state, city)
);

5.插入/修改 数据

upsert into lds values('1001','26');

假如我在Phoenix中放一条数据,只有主键(RowKey),没有字段值.在hbase中怎么处理呢?
hbase中不需要只有rowkey,
答:在hbase中,显示value为X,这个值没有啥意义,就是表示hbase中有数据,对应到phoneix中只有主键的行数据

6.查询记录

select * from lds where id='1001';
select * from lds where age=26;//hbase只能基于RowKey查询,我这个地方按照age查询.只能全表扫描.效率低.因此hbase支持二级索引

7. 删除记录

delete from lds where id='1001';
Phoenix中删表会把hbase中表也删除掉.

8.删除表

drop table lds;

9.退出命令行

!quit

4.5Phoenix与hbase表映射(有点乱回头补)

-- 1. 说明:       
a. 在Phoenix创建的表可以在hbase中直接查询到,因为Phoenix创建的表就是在hbase上;       
b. 在hbase的表,Phoenix默认不能直接看到,需要使用映射的方式才能看到。 
-- 2. 序列化问题:     
a、Phoenix数据的序列化器和hbase数据序列化器不一致;      
b、Phoenix使用自身的序列化器,而hbase使用的是bytes.toBtes()对数据进行序列化,则导致从Phoenix读取hbase和从hbase读取Phoenix数据时,会出现读出的数据和原表中的数据不一致。
现象为:     
-- 1). Hbase读取Phoenix表      
a、'列名格式问题':列名将Phoenix的字段转换为16进制显示      
b、'value格式问题':值类型数据也被转换成了16进制显示            
-- 2). Phoenix读取hbase表      
a、'列名格式问题':在Phoenix创建的字段和hbase表中的字段一样,但是没有数据     
b、'value格式问题':查询的数据和hbase的数据完全不等,描述见下图:

image-20220221022017917

-- 3. 解决方案如下:

image-20200629003822649

4.5.1 Phoenix创建的表
-- 1. 在Phoenix创建的表,在hbase中可以查询到,但是会发现多了每行数据中,会多如下一列数据,'称之为空/虚的keyvalue':      1001     column=0:_0, timestamp=1593071947795, value=x  
-- 2. 为什么Phoenix在进行upsert时会添加一个空/虚拟KeyValue?   
    在hbase表中,rowkey对应Phoenix表中的主键,如果Phoenix中表只有主键,没有其他列,那么在habse的表中,就只有rowkey,没有列族了。所有通过增加这样一列空的列,确保这行数据即有rowkey,也有列族.
-- 3. 官网说明:

image-20200628234338247

4.5.2 HBase创建的表
-- 1. 在HBase表映射方式有两种:视图映射和表映射
-- 2. 在Phoenix实现映射的方式:      
    a、创建的表名和hbase的表名相同,注意大小写;      
    b、创建一个主键,用来接收hbase表中的rowkey;      
    c、其余的字段,声明方式为:hbase表中的:列族.列名      
    d、最后需要加上:column_encoded_bytes=0,使的Phoenix反序列器为bytes.toBytes(),与hbase序列化器一致,则Phoenix可以找到hbase表中的列。 
-- 3. 说明:      
    a、创建时表名和字段名一定要相同;      
    b、如果创建的字段在hbase表不存在,也是可以的。相当于空列
4.5.3 视图映射
-- 1. 创建的视图是只读,只能用来进行查询,无法通过视图对原数据进行修改等操作。
  1. 在hbase准备数据
create 'lianzp' ,'info''info1'put 'lianzp','1001','info:name','zs'
  1. Phoenix端操操作
-- 创建视图create view "lianzp" (id varchar primary key , "info"."name" varchar , "info1"."address" varchar ) column_encoded_bytes=0;-- 删除视图drop view "lianzp";
4.5.4 表映射
-- 只需要将将create view 改成create table 即可。

数值问题问题

现象
数值在Phoenix存,Phoenix查,没问题
数值在phoenix存,hbase查,有问题
数值在hbase存,hbase查,没问题
数值在hbase存,Phoenix,有问题
原因:编解码原因
因此:用什么存,就用什么查
或者

create table test(
id varchar primary key,
name varchar,
salary integer
)
COLUMN_ENCODED_BYTES =NONE;
upsert into test values('1001','zhagnsan',123456);
Phoenix中正常显示123456,但是在hbase中scan后显示数据看不懂,因为编解码问题
hbase中我们put 'TEST','1002','列族:列名','1234'默认放的都是字符串
想要放数字:的put 'TEST','1003','列族:列名',Bytes.toBytes(1234)

4.6 二级索引

0.额外配置和理解

 hbase中只能基于rowkey检索数据,但是我们想通过除了rowkey以外别的数据检索数据,就需要为其建立二级索引.否则按别的字段检索就只能scan全变扫描
添加如下配置到HBase的HRegionserver节点的hbase-site.xml
<!-- phoenix regionserver 配置参数-->    <property>        <name>hbase.regionserver.wal.codec</name>        <value>org.apache.hadoop.hbase.regionserver.wal.IndexedWALEditCodec</value>    </property>    <property>        <name>hbase.region.server.rpc.scheduler.factory.class</name>        <value>org.apache.hadoop.hbase.ipc.PhoenixRpcSchedulerFactory</value>        <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>    </property>    <property>        <name>hbase.rpc.controllerfactory.class</name>        <value>org.apache.hadoop.hbase.ipc.controller.ServerRpcControllerFactory</value>        <description>Factory to create the Phoenix RPC Scheduler that uses separate queues for index and metadata updates</description>    </property>

4.6.1 全局二级索引

所谓的全局二级索引,就是将 索引字段1+索引字段2...+原表的rowkey 当成新表(索引表)的rowkey,从而实现基于非rowkey字段的索引

-- 1. 创建全局索引时,会在HBase中建立一张新表
索引表的使用:你想要按照什么字段过滤(where),就去建立什么索引只是想要获取结果:select,就去include(索引表的value是要查的值)
点查找必须唯一,所以是RANGE SCAN
Hbase将为二级索引字段建立新表:
二级索引字段和原表RowKey做拼接作为改表的RowKey

  1. 创建单个字段的全局索引

    image-20220113163853151

CREATE INDEX my_index ON my_table (my_col);
分析上图:没有添加二级索引以前:select id from person where name ='zs';
通过查看执行计划,full scan over person.进行了全表扫描.
但是添加全局索引之后,查看执行计划,range scan over person_index_name ['zs'];

思考如下:hbase的rowkey只有一个,这个地方是id.那name肯定不是rowkey,那它是什么呢?它怎么实现索引呢?
答:person_index_name又是一个新表,并且(name+id)作为这个表的rowkey,将他们存在一起.
但是:如果select id,name,addr from person where name='zs';
仍然是全表扫描,因为addr并不在person_index_name表中'如果想查询的字段不是索引字段的话,索引表不会被使用,也就是说不会带来查询速度的提升

image-20200629010950631

  1. 创建多个字段的复合全局索引
create index my_index on mytable(name,addr)
select id,name,addr from mytable where name='zs' and addr='sh';//使用索引,不会全局扫描
select id,name,addr from mytable where name='zs';//使用索引,不会全局扫描
select id,name,addr from mytable where  addr='sh';//全局扫描

思考:为什么我建立了addr的索引,任然会全局扫描呢?
因为:索引表的rowkey是:索引字段1+索引字段2...+原表的rowkey.基于此表就是name+addr+id.
只使用addr进行查找,不能通过name先缩小范围.只能进项全表扫描.

总结:带头大哥不能死,中间兄弟不能断.

这种有缺点:就是索引表的rowkey太长了.select id,name,addr from person where name='zs';
我并不想按照addr建立索引,我只是想使用name查询时,带上addr的值.但是我又不得不为了addr建立索引.就会导致索引表的索引过长.
解决方案:就是包含索引

4.6.2.包含索引

CREATE INDEX my_index ON my_table (name) INCLUDE (addr);
给name建立索引,包含addr列
这种方式的索引表的rowkey=name+原表rowkey(id).
select id,name,addr from my_table where name ='lds';//range
为什么?
把addr当成索引表的值.这样就可以避免rowkey过长.
去索引表查找id,name.然后取索引表中value(此时value就是addr),从而避免全局扫描

image-20200629011141184

4.6.3 本地二级索引

原理:
1.不是建立索引表
2.在hbase中不仅按照rowkey添加数据,而且添加新的数据,
4.将(索引列和rowkey)拼接起来作为rowkey,写入到原表.这样过滤时只要找到这个新的rowkey就可以过滤出该数据

好处 1. 索引数据和数据表的数据是存放在同一张表中(且是同一个Region),避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销。
  • 创建局部索引
CREATE LOCAL INDEX my_index ON my_table (my_column);

image-20200629011154264

4.6…4 局部和全局的选择

-- 1. 两种索引的介绍
    全局索引:会单独创建一个新的文件,默认是一个region,同时会采用默认的region split的切分规则;
    局部索引:在原数据表的插入数据,索引数据和数据表的数据是存放在同一张表中(且是同一个Region)。
-- 2. 在需要创建索引时,我们是选择创建哪种索引呢?
    创建索引以后,每次数据的改动都需要更新索引表。
两种索引选择的规则如下:
    '情况1':写操作频繁,则选择本地索引.
    因为数据和索引在同一张表的同一个region中(也就是在一个regionServer中),所以更新索引的数据就不需要跨节点.(要是使用全局索引,需要到两个region中进行写)避免了在写操作的时候往不同服务器的索引表中写索引带来的额外开销;
    '情况2': 读操作频繁时,则选择全局索引,因为全局索引中可以直接定位到数据,效率高。

image-20220113171551734

五.我的疑问

没有预分区的情况怎么实现?在底层存储时,数据按照rowkey的字典顺序从小到大进行排列。
问题:我没有设置预分区,数据来了该进入那个Region呢?==>Meta表怎么知道数据进入哪个region
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐