离线与实时
-
离线项目为例
- 数据生成:用户访问咨询数据、意向用户报名信息、考勤信息
- 数据采集
- Flume:实时数据采集:采集文件或者网络端口
- Sqoop:离线数据同步:采集数据库的数据
- 数据存储
- HDFS:分布式离线文件存储系统
- Hive:离线数据仓库
- 将HDFS上的文件映射成了表的结构,让用户可以通过数据库和表的形式来管理大数据
- 数据计算
- MapReduce+YARN:分布式离线数据计算
- Hive:通过SQL进行分布式计算
- 将SQL语句转换为MapReduce程序,提交给YARN运行
- 数据应用:通过对数据进行分析
- 提高转化率:访问与咨询转化率、报名转换率
- 考勤分析:通过考勤分析来把握所有人学习的情况
-
离线与实时
- 离线:数据从产生到最后被使用,时效性比较低,时间比较长
- 实时:数据从产生到最后被使用,时效性比较高,时间比较短
- 方向:所有数据的价值会随着时间的流逝,价值会越来越低,希望所有数据都能被实时的计算以及处理
- 实现实时:所有环节都必须是实时环节
- 数据生成:实时的
- 数据采集:实时的,Flume、Canal……
- 数据存储:实时的,Hbase、Kafka、Redis……
- 数据计算:实时的,Spark、Flink……
- 数据应用:实时应用:风控系统、实时推荐、精准分析
-
学习知识的逻辑
-
step1:先了解基本的功能与应用场景
-
step2:基本的使用
-
step3:深入了解原理
-
HBASE诞生
1、问题
- 随着大数据的发展,大数据的应用场景越来越多,有了实时性的需求
- HDFS、MapReduce:都只能实现离线的处理以及计算
- 想做实时推荐
- 实时的采集用户的数据,实时分析,根据用户画像给用户推荐合适的商品
- 实时采集:Flume
- 实时存储:存储读写的性能在毫秒级别
- 实时计算:计算处理的性能在毫秒级别
2、需求
- 需要一项技术能实现大量的数据实时数据读写
- HDFS已经满足不了:HDFS解决离线大数据存储读写
- 设计:为了满足怎么存储大数据的问题
3、解决
- 谷歌的三篇论文
- GFS:基于文件系统的离线大数据存储平台HDFS
- MapReduce:基于离线大数据批处理分布式计算平台
- BigTable:分布式实时随机读写的NoSQL数据库【Chubby】
- Hbase + Zookeeper
- 设计:怎么高效的进行大数据读写存储的问题
4、总结
- Hbase能实现基于海量数据的随机实时的数据存储及读写
- 与MySQL区别:Hbase实现大数据存储
- 与HDFS区别:Hbase性能更好,更快
Hbase功能及应用场景
1、功能
-
Hbase是一个分布式的NoSQL数据库,能实现随机实时的大量数据的读写
-
大数据存储:分布式 + HDFS
-
实时数据读写
2、应用场景
- 电商:实时推荐
- 金融:实时风控、实时征信统计
- 交通:实时车辆监控
- 游戏:实时记录所有操作
- ……
HBASE设计思想
1、问题
- 对于计算机而言,数据的存储只会在两个地方
- 内存:读写比较快
- 硬盘:读写相对慢一些
- 为什么Hbase可以实现大量数据的实时的读写?
- 问题1:为什么Hbase能存储大量数据?
- 问题2:为什么Hbase能读写很快?
2、需求
- 需求1:必须实现分布式存储,利用多台机器的硬件资源从逻辑上整合为一个整体
- 类似于HDFS设计思想
- 需求2:必须实现将数据存储在内存中,读写速度才能很快
- 为什么HDFS也是分布式,但是慢:HDFS所有的数据是存储在磁盘中
3、实现
- 实现1:Hbase做了分布式结构,利用多台机器组合成一个整体,多台机器的内存和磁盘进行逻辑合并了
- 实现2:Hbase优先将数据写入内存,读取数据时,如果数据在内存中,可以被直接读取
- 新的问题:内存的空间是比较小的,能存储的数据量不大,违背了Hbase能存储大数据吗?
- 内存的特点:内存容量小、数据易丢失、读写速度快
- 磁盘的特点:容量空间大、数据相对安全、速度相对慢
- Hbase如何能实现容量大和速度快的问题?
- 设计思想:冷热数据分离,实时计算中,对当前最新的数据进行读取处理应用
- 冷:大概率不会被用到的数据
- 在实时架构中,已经产生很久的数据
- 热:大概率会被用到的数据
- 在实时架构中,刚产生的数据
- 冷:大概率不会被用到的数据
- 解决:Hbase将数据写入内存中,如果内存中存储的数据满了,就将内存的数据写入磁盘
- 热:写内存,大概率的情况下,可以直接从内存中读取
- 冷:将内存中产生很久的数据写入磁盘中
4、总结
-
为什么Hbase既能存储大数据,也能很高的性能:冷热数据分离
-
最新的数据写入内存,大概率也是读内存
- 构建分布式内存:数据优先写入分布式内存
- Hbase集群
-
将老的数据写入磁盘,被读的概率相对较低
- 构建分布式磁盘:老的数据写入分布式磁盘
- HDFS集群
-
新的问题:Hbase数据如何能保证安全?
- HDFS保证数据安全机制:副本机制,每个数据块存储3份,存储在不同的机器上
- 内存:本身就是容易丢失的,如果断电
- 一般内存中数据不能通过副本机制来保证
- 因为内存空间小、内存都是易丢失的
- 一般内存的数据安全都选择写WAL的方式来实现的【记住这是保证内存数据安全的方式】
- 磁盘:Hbase将数据从内存写入HDFS,由HDFS的保障机制来保证磁盘数据安全
HBASE中的对象概念
1、NoSQL数据库与RDBMS数据库
-
RDBMS:一般是为了解决数据管理问题
- 数据安全性高、支持事务特性、数据量比较小、数据相对比较差
- MySQL、Oracle……
- 都支持SQL语句,存储固定的行列数据
- 数据库、表、行、列
-
NoSQL:一般是为了解决性能问题
-
数据安全性相对没有那么高、不支持完善是事务,数据量比较大、性能比较高
-
Hbase、Redis、MongoDB……
-
都不支持SQL语句,存储的数据也有固定的格式
-
每种数据库都有自己的API方式
-
2、数据库设计
-
NameSpace:命名空间,类似于MySQL中的数据库概念,Namespace中有多张表,Hbase中可以有多个Namespace
-
直接把它当做数据库来看
-
Hbase自带了两个Namespace
- default:默认自带的namespace
- hbase:存储系统自带的数据表
-
注意
-
Hbase中的每张表都必须属于某一个Namespace,将namespace当做表名的一部分
-
如果在对表进行读写时,必须加上namespace:tbname方式来引用
-
例如:namespace叫做itcast,里面有一张表叫做heima
itcast:heima
-
-
如果不加namesp引用表的操作,这张表就默认为default的namespace下的表
heima
-
-
3、数据表设计
-
Table:表,Hbase中的每张表都必须属于某一个Namespace
-
注意:在访问表时,如果这张表不在default的namespace下面,必须加上namespace:表名的方式来引用
- Hbase中的表时分布式结构,写入Hbase表的数据,会分布式存储到多台机器上
-
HBASE中的存储概念
1、数据行设计
- Rowkey:行健,这个概念是整个Hbase的核心,类似于MySQL主键的概念
- MySQL主键:可以没有,唯一标记一行、作为主键索引
- Hbase行健:自带行健这一列【行健这一列的值由用户自己设计】,唯一标识一行,作为Hbase表中的唯一索引
- Hbase整个数据存储都是按照Rowkey实现数据存储的
2、列族设计
-
ColumnFamily:列族,对除了Rowkey以外的列进行分组,将列划分不同的组中
-
注意:任何一张Hbase的表,都至少要有一个列族,除了Rowkey以外的任何一列,都必须属于某个列族,Rowkey不属于任何一个列族
-
分组:将拥有相似IO属性的列放入同一个列族【要读一起读,要写一起写】
-
原因:划分列族,读取数据时可以加快读取的性能
-
如果没有列族:找一个人,告诉你这个人就在这栋楼
-
如果有了列族:找一个人,告诉你这个人在这栋楼某个房间
-
-
3、数据列设计
-
Column:列,与MySQL中的列是一样
-
注意
-
Hbase除了rowkey以外的任何一列都必须属于某个列族,引用列的时候,必须加上列族
- 如果有一个列族:basic
- 如果basic列族中有两列:name,age
basic:name basic:age
-
Hbase中每一行拥有的列是可以不一样的
- 每个Rowkey可以拥有不同的列
-
-
4、版本设计
-
功能:某一行的任何一列存储时,只能存储一个值,Hbase可以允许某一行的某一列存储多个版本的值的
-
级别:列族级别,指定列族中的每一列最多存储几个版本的值,来记录值的变化的
-
区分:每一列的每个值都会自带一个时间戳,用于区分不同的版本
- 默认情况下查询,根据时间戳返回最新版本的值
5、分布式设计
-
Hbase的表如何实现分布式设计
-
Region:分区,Hbase中任何一张都可以有多个分区,数据存储在表的分区中,每个分区存储在不同的机器上
- 非常类似于HDFS中Block的概念
- 划分规则:范围分区
-
HDFS设计
- 文件夹
- 文件
- 划分Block:根据每128M划分一个块
- 每个Block存储在不同的机器上
-
Hbase设计
- Namespace
- Table:分布式表
- 划分Region/Part
- 存储在不同的机器上:RegionServer
-
对比
分布式概念 HDFS Hbase 对象 目录 + 文件 Namespace + Table 分布式 Block Region 划分规则 按照大小划分:128M 按照范围划分
6、概念对比
概念 | MySQL | Hbase |
---|---|---|
数据库 | DataBase | NameSpace |
数据表 | Table | Table【分布式的】 |
数据分区 | - | Region |
数据行 | 主键+其他列 | Rowkey+其他列 |
列族 | - | ColumnFamily |
数据列 | 普通列与对应的值 | 列【timestamp】与对应的值【支持多版本】 |
HBASE中的按列存储
1、功能
- Hbase的最小操作单元是列,不是行,可以实现对每一行的每一列进行读写
2、问题
- Hbase性能很好原因
- 读写内存
- 思考问题:依旧存在一定的概率会读HDFS文件,怎么能让读文件依旧很快?
- 列族的设计:加快数据的读取性能
- Rowkey构建索引,基于有序的文件数据
- 按列存储
3、设计
- MySQL:按行存储,最小的操作单元是行
- insert:插入一行
- delete:删除一行
- ……
- Hbase:按列存储,最小操作单元是列
- 插入:为某一行插入一列
- 读取:只读某一行的某一列的
- 删除:只删除这一行的某一列
4、举例
- MySQL中读取数据
- 查询【id,name,age,addr,phone……100列,每一列10M】:select id from table ;
- 先找到所有符合条件的行,将整行的数据所有列全部读取:1000M数据
- 再过滤id这一列:10M
- 查询【id,name,age,addr,phone……100列,每一列10M】:select id from table ;
- Hbase中读取数据
- 查询【id,name,age,addr,phone……100列,每一列10M】:select id from table ;
- 直接对每一行读取这一列的数据:10M
5、总结
-
思想:通过细化了操作的颗粒度,来提高读的性能
-
如果按行存储:找一个人,告诉你这个人就在这栋楼某个房间的某一排
-
如果按列存储:找一个人,告诉你这个人在这栋楼某个房间的某一排的某一列
HBASE集群架构
1、分布式主从架构
-
Hbase集群:分布式架构集群,主从架构
-
HMaster:主节点:管理节点
- 负责所有从节点的管理
- 负责元数据的管理
-
HRegionServer:从节点:存储节点
- 负责存储每张表的数据:Region
- Region存储在RegionServer中
- 对外提供Region的读写请求
- 用于构建分布式内存:每台RegionServer有10GB内存存储空间,100台RegionServer
- Hbase可以存储的总内存空间:1000G
- 数据写入Hbase,先进入分布式内存
2、HDFS的设计
-
如果HRegionServer的内存达到一定阈值,就会将内存中的数据写入HDFS,实现数据持久化存储
-
用于存储冷数据的:大部分的数据都在HDFS中
3、Zookeeper的设计
- Zookeeper在大数据工具中的作用
- 功能一:用于存储元数据:Hbase、Kafka……
- 功能二:用于解决主节点的单点故障问题HA,辅助选举Active的Master
存储设计:存储架构
-
问题:Hbase整体如何实现数据的存储?
-
分析
- Client:负责连接服务端
- 提供开发接口,将用户的命令或者代码提交给服务端执行
- 将服务端执行的结果返回给用户
- Zookeeper:存储Hbase部分元数据
- 所有Hbase客户端,都需要连接Zookeeper获取元数据
- Hbase:分布式内存
- Master:管理类功能
- RegionServer:负责数据的存储,对外提供客户端读写
- 分布式内存
- HDFS:分布式磁盘
- DataNode:负责将Hbase内存中的数据写入磁盘
- Client:负责连接服务端
存储设计:Table、Region、RegionServer的关系
-
问题:客户端操作的是表,数据最终存在RegionServer中,表和RegionServer的关系是什么?
-
分析
- Table:是一个逻辑对象,物理上不存在,供用户实现逻辑操作,存储在元数据的一个概念
- 类似于HDFS中文件
- RegionServer:是一个物理对象,Hbase中的一个进程,管理一台机器的存储
- 类似于HDFS中DataNode
- Region:Hbase中数据存储的最小单元
- 类似于HDFS中Block
- 就是分区的概念,每张表都可以划分为多个Region,实现分布式存储
- 默认一张表只有一个分区
- 每个Region由一台RegionServer所管理,Region存储在RegionServer
- 一台RegionServer可以管理多个Region
- Table:是一个逻辑对象,物理上不存在,供用户实现逻辑操作,存储在元数据的一个概念
-
观察监控
存储设计:Region的划分规则
-
问题:一张表划分为多个Region,划分的规则是什么?写一条数据到表中,这条数据会写入哪个Region,分配规则是什么?
-
分析
-
回顾:HDFS划分规则
- 划分分区的规则:按照大小划分,文件按照每128M划分一个Block
-
Hbase分区划分规则:范围划分【根据Rowkey范围】
-
范围划分:从整个-oo ~ +oo区间上进行范围划分
-
每个分区都会有一个范围:根据Rowkey属于哪个范围就写入哪个分区
[startKey,stopKey)
- 前闭后开区间
-
-
默认:一张表创建时,只有一个Region
- 范围:-oo ~ +oo
-
自定义:创建表时,指定有多少个分区,每个分区的范围
- 举个栗子:创建一张表,有2个分区Region
- region0:-oo ~ 50
- region1: 50 ~ +oo
- 举个栗子:创建一张表,有4个分区Region
- region0:-oo ~ 30
- region1: 30 ~ 60
- region2: 60 ~ 90
- region3: 90 ~ +oo
- 举个栗子:创建一张表,有2个分区Region
-
-
数据分配的规则:根据Rowkey属于哪个范围就写入哪个分区
- 举个栗子:创建一张表,有4个分区Region
- region0:-oo ~ 30
- region1: 30 ~ 60
- region2: 60 ~ 90
- region3: 90 ~ +oo
- 写入数据的rowkey:比较是按照ASC码比较的,不是数值比较
- 10 => Region0
- 1000 => Region0
- 033 => Region0
- 789999999 => Region2
- 91 => Region3
- 举个栗子:创建一张表,有4个分区Region
-
-
观察监控
- 只有1个分区
- 多个分区的情况
- 只有1个分区
create 'itcast:t3','cf',SPLITS => ['20', '40', '60', '80']
put 'itcast:t3','0300000','cf:name','laoda' => -oo ~ 20
put 'itcast:t3','7890000','cf:name','laoda' => 60 ~ 80
存储设计:Region内部存储结构
-
问题:数据在Region的内部是如何存储的?
-
分析
- Table【逻辑】/ RegionServer【物理】
- Region:分区层,根据rowkey判断属于哪个分区,就写入哪个Region
- Store:存储层,对每个分区内部数据存储的划分,根据列族划分,一个列族就对应一个Store
- 每个列族对应一个Store,不同列族的数据存储在不同的Store中
- 如果一张表,有2个列族,这张表的region中就会有两个Store
- 优点:划分不同的数据存储
- 假设有100列,如果没有列族,100列存储在一起,想查询其中1列,最多会比较100次
- 假设有100列,如果有两个列族,50列存储在一起,想查询某个列族中的某1列,最多比较51次
- 先比较我要查询哪个Store,比较1次
- Store:存储层,对每个分区内部数据存储的划分,根据列族划分,一个列族就对应一个Store
- Region:分区层,根据rowkey判断属于哪个分区,就写入哪个Region
- Store中的存储
- 一个MemStore:Region中的内存区域,会为每个Store分配一部分
- 数据先读写MemStore
- 0个或者多个StoreFile文件:Store中的数据文件,如果Memstore存储达到阈值,就会将内存数据写入HDFS
- StoreFile:逻辑上属于Store的
- 物理上存储在HDFS上:本质上存储的是HFILE:有序的二进制文件
- StoreFile:逻辑上属于Store的
- 一个MemStore:Region中的内存区域,会为每个Store分配一部分
- Table【逻辑】/ RegionServer【物理】
-
总结
- RegionServer:Region存储在Regionserver中
- Region:一张表有多个Region,根据Rowkey判断写入哪个region
- 一个表中有多个列族,每个列族对应一个Store,一个region中有多个store
- Store:根据列族判断写入哪个Store
- memstore:内存区域
- storefile:HDFS上的文件
- Region:一张表有多个Region,根据Rowkey判断写入哪个region
- RegionServer:Region存储在Regionserver中
存储设计:HDFS中的存储结构
-
问题:Hbase的数据在HDFS中是如何存储的?
-
分析
-
整个Hbase在HDFS中的存储目录:由配置决定
hbase.rootdir=hdfs://node1:8020/hbase
-
- Namespace的目录
- 表的目录
- Region的目录
- Store的目录:按照列族划分的
- 观察数据
hbase> flush 'TABLENAME'
hbase> flush 'REGIONNAME'
hbase> flush 'ENCODED_REGIONNAME'
hbase> flush 'REGION_SERVER_NAME'
#强制将内存的数据写入HDFS变成StoreFile
flush 'itcast:t3'
集群角色功能:Master
-
管理所有从节点:RegionServer
-
监听Regionserver的状态,如果RegionServer发生故障,会实现这台RegionServer的数据恢复
-
基本原理:所有RegionServer会在ZK中注册一个临时节点,Master会监听Zookeeper中的这些节点
- 类似于HA选举的原理
-
数据是如何恢复的?
-
数据存储的位置
-
内存:RegionServer的内存中
-
如果RegionServer断电故障,内存中的数据丢失
-
解决:通过持久化日志来实现
-
WAL:HLog,write ahead log:预写日志
- 数据在写入内存之前,会将这个操作记录在WAL中
- 如果内存数据丢失,可以通过WAL进行恢复
- 存储在HDFS上
-
-
-
磁盘:HDFS中
- 如果RegionServer故障,不影响HDFS的数据
-
-
-
管理元数据
- Master会接受所有DDL请求
- Master启动时会加载meta表和namespace的数据,获取元数据记录在ZK中
- Master会将所有管理类的元数据存储在ZK中
-
管理Region的分配
- 创建表、分配region
- regionserver故障,重新分配region到别的regionserver上
- region分裂,将新的region分配到regionserver上
集群角色功能:RegionServer
-
负责管理所有Region的数据读写,接受客户端对于region的读写请求
-
维护所有存储角色
-
WAL:write ahead log:预写日志
-
Region:存储分区
-
Store:列族,存储单元
-
Memstore:写缓存,数据先写入MemStore
-
BlockCache:读缓存,如果第一次从HDFS中读取,可以配置将读取的结果放入读缓存,下次直接从读缓存中读取
-
读的顺序
-
先读memstore
-
如果没有,数据在HDFS的文件中:怎么能让Hbase读HDFS文件能很快?
- Rowkey是有序的
- 列族的设计
- 二进制文件
-
可以指定对列族的数据是否开启读缓存,如果开启了读缓存
-
第一次从HDFS读取完成以后,将结果放入读缓存BlockCache:内存区域
-
第二次开始
-
先读memstore
-
再读Blockcache
-
再读HDFS
-
-
-
-
-
StoreFile:如果MemStore存储的容量达到一定条件,将MEMStore中的数据写入HDFS
-
-
实现分布式内存存储
集群角色功能:HDFS与ZK
-
HDFS功能
- 用于实现HBASE大量数据的持久化存储
- 当MemStore中的数据过多,就会进行Flush【Spill】:将数据从内存溢写到HDFS上
-
ZooKeeper功能
- 用于实现HMaster的HA,辅助选举
- 用于存储核心元数据:管理元数据
- 供Master实现管理操作
- Hbase中有哪些ns、哪些表、哪些rs、master是谁,正在执行的操作
- 供Master实现管理操作
- 表的元数据不在Zookeeper中
- 表的元数据在meta表中
- 表的元数据:表有几个region,每个region的范围、region所在的地址
热点问题:现象及原因
- 现象:在某个时间段内,大量的读写请求全部集中在某个Region中,导致这台RegionServer的负载比较高,其他的Region和RegionServer比较空闲
- 问题:这台RegionServer故障的概率就会增加,整体性能降低,效率比较差
- 原因:本质上的原因,数据分配不均衡
- 情况一:如果这张表只有一个分区
- region0:-oo ~ +oo
- 所有数据都是读写这一个分区
- 肯定会出现热点
- 情况二:如果这张表有多个分区,如果你的Rowkey写入时是连续的
- region0:-oo ~ 30
- region1:30 ~ 70
- region2:70 ~ +oo
- 写入的rowkey
- 00000001
- 00000002
- 00000003
- ……
- 29999999:都在往Region0中写入
- 30000000:开始写入region1
- ……
- 全部写region1
- ……
- 原因:region的设计是按照范围有序的,Rowkey也是有序的,连续的rowkey写入同一个region
- 情况一:如果这张表只有一个分区
- 解决
- 创建表的时候给定多个分区
- Rowkey写入时不能是连续的
分布式设计:预分区
-
需求:在创建表的时候,指定一张表拥有多个Region分区
-
分析
-
划分多个分区,实现分布式并行读写,将无穷区间划分为几段
-
段的划分的依据:Rowkey或者Rowkey的前缀来划分
-
举个栗子:如果分区都是按照数值划分的
-
region0:-oo ~ 3
-
region1:3 ~ 7
-
region2:7 ~ +oo
-
Rowkey:都是以字母开头的
a143434343 ydfjdkfjd4 ……
-
出现了热点问题
-
-
-
实现
-
方式一:指定分隔段,实现预分区
create 'ns1:t1', 'f1', SPLITS => ['10', '20', '30', '40'] #将每个分割的段写在文件中,一行一个 create 't1', 'f1', SPLITS_FILE => 'splits.txt'
-
方式二:指定Region个数,自动进行Hash划分:字母和数字的组合
#你的rowkey的前缀可能是字母可能是数字 create 'itcast:t4', 'f1', {NUMREGIONS => 15, SPLITALGO => 'HexStringSplit'}
-
方式三:Java API
HBASEAdmin admin = conn.getAdmin admin.create(表的描述器对象,byte[][] splitsKey)
-
-
总结
- 原则:必须根据rowkey或者rowkey前缀来设计分区的划分
- 方式:三种方式任何一种只要能满足需求都可以
Hbase表设计:Rowkey设计
-
问题
- Rowkey功能
- 唯一标识符
- 作为Hbase唯一索引
- 划分Region的标识,不能连续
- 问题
- Rowkey不重复
- 尽量保证按照rowkey查询数据是最快的
- Rowkey是散列的,无序的
- Rowkey功能
-
需求:根据业务需求,来合理的设计rowkey,实现高性能的数据存储
-
分析:不同的业务需求的表,Rowkey设计都不一样
-
设计规则
-
业务原则:Rowkey设计必须贴合业务需求,尽量实现基于Rowkey数据检索,走索引
- Rowkey是Hbase中的唯一索引,按照Rowkey的查询是按照前缀匹配实现的
- 如果不按照Rowkey检索,就是全表扫描
- 尽量将最常用的查询条件作为Rowkey的前缀
- 地区
- 时间
- ID:订单ID、用户ID
-
唯一原则:Rowkey必须唯一标识一条数据,Rowkey是不能重复的
-
组合原则:尽量将最常用的几个查询条件组合作为Rowkey,最常用的那一个作为前缀
-
常见的条件:时间、订单id、用户id
-
最常用的:时间
-
Rowkey
1616124723000_user001_order001
- 以下的查询都是走索引
- 查询某个时间的所有订单数据
- 查询某个时间某个人的所有订单数据
- 查询某个时间某个人的某个订单数据
- 以下的查询不走索引
- 查询某个人的所有订单
- 只能走全表扫描:慢
- 问题:学习二级索引来解决
- 以下的查询都是走索引
-
-
散列原则:Rowkey不能是按照有序生成的,由于Region范围划分的,如果rowkey是有序的,会导致热点,必须构建无序的Rowkey
-
问题:有序的rowkey,用时间作为前缀,时间是有序的
1616124723000_user001_order001 1616124723000_user002_order002 1616124723000_user003_order003 1616124723001_user001_order004 1616124723002_user001_order005 1616124723000_user001_order001 ……
-
解决
-
方案一:不将连续的字段作为前缀,例如:将用户id作为前缀
user001_1616124723000_order001 user999_1616124723000_order002
- 一个用户不可能在同一秒下多个订单
- 所有用户在同一秒也不可能的连续的
- 缺点:前缀不是最常用的查询条件
-
方案二:基于有序的进行反转,将时间进行反转构建Rowkey
0003274216161_user001_order001 0003274216161_user002_order002 0003274216161_user003_order003 1003274216161_user001_order004 1003274216161_user001_order005 2003274216161_user001_order001 3003274216161 4003274216161 ……
- 缺点:每次查询时,也必须先反转,再查询
-
方案三:编码,构建随机
1616124723000_user001_order001 1616124723000_user002_order002 1616124723000_user003_order003 1616124723001_user001_order004 1616124723002_user001_order005 1616124723000_user001_order001 …… | 通过对原来的rowkey进行加密,得到一个加密码 12345678_1616124723000_user001_order001 37483784_1616124723000_user002_order002 ……
- 缺点:每次查询时,也必须先编码
-
方案四:加盐,给Rowkey前缀添加一个随机值
1616124723000_user001_order001 1616124723000_user002_order002 1616124723000_user003_order003 1616124723001_user001_order004 1616124723002_user001_order005 1616124723000_user001_order001 …… | 新的rowkey前缀是个随机值【0-9】 0_1616124723000_user001_order001 4_1616124723000_user002_order002 9_1616124723000_user003_order003 0_1616124723001_user001_order004 0_1616124723002_user001_order005 1_1616124723000_user001_order001
- 缺点:每次查询时,挨个试,读的性能降低
-
-
-
长度原则:Rowkey的长度不建议过长
- 原则:能满足业务需求的情况下越短越好
- 问题:如果rowkey越长,索引占用的空间越大,比较rowkey就越慢,性能就越差
- rowkey在底层是冗余存储的
- 建议:不超过100个字节
- 如果超过100个字节,建议进行编码
- 100位 =》 MD5 => 32位 、16位
- 如果超过100个字节,建议进行编码
-
-
总结
- 业务原则:保证前缀是最常用的查询字段
- 唯一原则:每条rowkey表示一条数据
- 组合原则:常用的查询条件作为Rowkey
- 散列原则:rowkey构建不连续
- 长度原则:满足业务需求越短越好
Hbase设计:列族的设计
-
设计目的
-
Hbase特点:按列存储,允许存储一行有非常多的列
-
工作中主要处理的场景:对列进行处理
select name,count(*) from table group by name;
-
列族的设计:将列进行分组
-
列族的规则:将拥有相似IO属性的列划分为同一列族
- 要读一起读,要写一起写
-
-
底层实现
- Store:每个Region中根据列族将不同列族的列存储在不同的Store
-
设计规则
- 个数原则:Hbase列族的个数有一定的要求,大部分情况下建议给2个
- 如果列的个数比较多:30列以上
- 2 ~ 3
- 如果列的个数比较少,数据量不大
- 1个
- 长度原则 :能满足业务需求的情况下,越短越好
- 名称没有其他功能,只要有标示性即可
-
Hbase底层数据结构:KV结构,按列存储
- K:rowkey+列族+列名+Timestamp
- V:value
二级索引
-
目标
- 基于一级索引之上,构建一层索引,通过走两次索引来代替全表扫描
- Hbase中的唯一索引是Rowkey
- 目标:得到所有符合条件的数据
- 思考:能不能先得到所有符合条件的rowkey,再通过rowkey得到所有符合条件的数据
-
路径
- step1:先构建原始数据表
- step2:根据查询需求,构建索引表
- step3:先查询索引表,再查询原始数据表
-
实现
-
原始数据表
rowkey:name_id id name age sex addr zhangsan_001 001 zhangsan 18 male shanghai lisi_002 002 lisi 18 female beijing zhangsan_003 003 zhangsan 20 male ……
-
哪些查询走索引?
- name
- name + id
-
哪些查询不走索引?
- id
- age
- sex
- addr
-
需求:根据id查询对应id的所有数据
- 不走索引
-
解决方案:构建二级索引
-
原始数据表:记录所有原始数据
rowkey:name_id id name age sex addr zhangsan_001 001 zhangsan 18 male shanghai lisi_002 002 lisi 18 female beijing zhangsan_003 003 zhangsan 20 male
-
数据索引表:记录查询条件与原表Rowkey的映射关系
rowkey:id_name col:原表的rowkey 001_zhangsan zhangsan_001 002_lisi lisi_002 003_zhangsan zhangsan_003 ……
-
查询过程:查询id = 003
-
step1:先根据条件查询索引表,获取所有符合条件的原表的rowkey
-
根据索引表的前缀匹配
003_zhangsan zhangsan_003
-
-
step2:根据原表的rowkey获取数据
zhangsan_003 003 zhangsan 20 male
-
-
-
-
总结
- 在原表之上构建索引表
- 先查询索引表,再查询原表
-
Hive的问题
-
Hive中能否执行create index
- 可以
- 要求:0.7 ~ 3.x
- 原因:Hive中索引表不会自动同步原始数据表,必须手动通过MapReduce同步索引表
-
-
Hbase问题:索引表如何与原表保持一致的问题
-
方案一:当客户端往Hbase原表写入时,在客户端中也往索引表写一份
- 不会用的
- 优点:最简单
- 缺点:Hbase无法保证不同表不同的rowkey的事务性,不能实现同时成功或者同时失败,性能很差
-
方案二:协处理器,类似于Hive中的UDF
- 自定义开发代码,让Hbase实现监听原表,如果原表的数据发生变化,索引自动发生变化
- 优点:Hbase原生发生,可以满足原子性包括性能
- 缺点:开发比较麻烦
-
方案三:第三方工具
-
Phoenix:专门为Hbase所设计的工具,底层通过大量协处理器来实现,提供SQL接口
create index
- 自动创建索引表,维护索引同步
-
ES:ElasticSearch / Solr
-
-
Maven 依赖
<repositories>
<repository>
<id>aliyun</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</repository>
</repositories>
<properties>
<hbase.version>2.1.2</hbase.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/org.apache.hbase/hbase-client -->
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>${hbase.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>${hbase.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<!-- Xml操作相关 -->
<dependency>
<groupId>com.github.cloudecho</groupId>
<artifactId>xmlbean</artifactId>
<version>1.5.5</version>
</dependency>
<!-- 操作Office库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 操作Office库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 操作Office库 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 操作JSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- phoenix core -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-core</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
<!-- phoenix 客户端 -->
<dependency>
<groupId>org.apache.phoenix</groupId>
<artifactId>phoenix-queryserver-client</artifactId>
<version>5.0.0-HBase-2.0</version>
</dependency>
</dependencies>
BulkLoad的介绍
-
目标
- 了解BulkLoad的功能及应用场景
-
分析
- 问题:有一批大数据量的数据,要写入Hbase中,如果按照传统的方案来写入Hbase,必须先写入内存,然后内存溢写到HDFS,导致Hbase的内存负载和HDFS的磁盘负载过高,影响业务
- 解决
- 写入Hbase方式
- 方式一:构建Put对象,先写内存
- 方式二:BulkLoad,直接将数据变成StoreFile文件,放入Hbase对应的HDFS目录中
- 数据不经过内存,读取数据时可以直接读取到
-
实现
- step1:先将要写入的数据转换为HFILE文件
- step2:将HFILE文件加载到Hbase的表中【直接将文件放入了Hbase表对应的HDFS目录中】
-
总结
- 应用场景:Hbase提供BulkLoad来实现大数据量不经过内存直接写入Hbase
-
特点
- 优点:不经过内存,降低了内存和磁盘的IO吞吐
- 缺点:性能上相对来说要慢一些,所有数据都不会在内存中被读取
BulkLoad的实现
-
目标
- 实现BulkLoad方式加载数据到Hbase的表中
-
分析
- step1:先将要写入的数据转换为HFILE文件
- step2:将HFILE文件加载到Hbase的表中【直接将文件放入了Hbase表对应的HDFS目录中】
-
实现
-
开发代码
-
创建表
create 'mrhbase','info'
-
上传测试文件
hdfs dfs -mkdir -p /bulkload/input hdfs dfs -put writeHbase.txt /bulkload/input/
-
上传jar包到Linux上
-
step1:转换为HFILE
yarn jar bulkload.jar bigdata.itcast.cn.hbase.bulkload.TransHfileMR /bulkload/input/ /bulkload/output
-
运行找不到Hbase的jar包,手动申明HADOOP的环境变量即可,只在当前窗口有效
export HADOOP_CLASSPATH=$HADOOP_CLASSPATH:/export/server/hbase-2.1.0/lib/shaded-clients/hbase-shaded-mapreduce-2.1.0.jar:/export/server/hbase-2.1.0/lib/client-facing-thirdparty/audience-annotations-0.5.0.jar:/export/server/hbase-2.1.0/lib/client-facing-thirdparty/commons-logging-1.2.jar:/export/server/hbase-2.1.0/lib/client-facing-thirdparty/findbugs-annotations-1.3.9-1.jar:/export/server/hbase-2.1.0/lib/client-facing-thirdparty/htrace-core4-4.2.0-incubating.jar:/export/server/hbase-2.1.0/lib/client-facing-thirdparty/log4j-1.2.17.jar:/export/server/hbase-2.1.0/lib/client-facing-thirdparty/slf4j-api-1.7.25.jar
-
step2:加载到Hbase表中
yarn jar bulkload.jar bigdata.itcast.cn.hbase.bulkload.BulkLoadToHbase /bulkload/output
-
-
总结
- step1:先将数据转换为HFILE文件
- step2:将HFILE加载到Hbase表中
ImportTSV的使用
-
目标
- 了解ImportTSV工具的功能及使用
- 字面意思:导入tsv格式的数据文件
- tsv:以制表符分隔每一列的文件
- csv:以逗号分隔每一列的文件
-
分析
- importtsv功能:将可以将任何一种结构化的文件导入Hbase的表中,【默认是使用Put方式来导入的】
- 默认导入tsv格式的文件
- importtsv功能:将可以将任何一种结构化的文件导入Hbase的表中,【默认是使用Put方式来导入的】
-
实现
-
使用方式一:直接使用Put方式导入
-
使用
yarn jar /export/server/hbase-2.1.0/lib/hbase-mapreduce-2.1.0.jar importtsv -Dimporttsv.columns=HBASE_ROW_KEY,cf1:name,cf1:age,cf2:sex <你要写入哪张表> <读取文件的文件地址>
-
手动指定分隔符
'-Dimporttsv.separator=,',自己指定分隔符,默认分隔符为\t
-
举例
-
现在是数据
1 zhangsan 18 male 2 lisi 20 female
-
导入Hbase中
yarn jar /export/server/hbase-2.1.0/lib/hbase-mapreduce-2.1.0.jar \ importtsv \ -Dimporttsv.columns=a,b,c \ --指定表中的每一列与文件中的每一列的对应关系 <tablename> \--指定导入哪张表 <inputdir> \--指定导入哪个文件
yarn jar /export/server/hbase-2.1.0/lib/hbase-mapreduce-2.1.0.jar \ importtsv \ -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:age,info:sex mrhbase \ /bulkload/input
-
-
-
使用方式二:结合BulkLoad的方式来实现
-
step1:将普通文件转换为HFILE文件
yarn jar /export/server/hbase-2.1.0/lib/hbase-mapreduce-2.1.0.jar importtsv -Dimporttsv.columns=HBASE_ROW_KEY,cf1:name,cf1:age,cf2:sex -Dimporttsv.bulk.output=HFILE文件的存储地址 <你要写入哪张表> <读取文件的文件地址>
yarn jar /export/server/hbase-2.1.0/lib/hbase-mapreduce-2.1.0.jar \ importtsv \ -Dimporttsv.columns=HBASE_ROW_KEY,info:name,info:age,info:sex \ -Dimporttsv.bulk.output=/bulkload/output \ mrhbase \ /bulkload/input
-
step2:使用bulkload加载数据
yarn jar /export/server/hbase-2.1.0/lib/hbase-mapreduce-2.1.0.jar completebulkload HFILE文件的地址 表的名称
yarn jar /export/server/hbase-2.1.0/lib/hbase-mapreduce-2.1.0.jar completebulkload /bulkload/output mrhbase
-
-
-
总结
-
importTSV:默认读取tsv格式
- 可以将数据直接写入Hbase
- 也可以将数据转换为HFILE文件
-
注意:importTSV可以读取别的格式:如果要读取csv格式
'-Dimporttsv.separator=,'
-
协处理器的介绍
-
目标
- 了解协处理器的功能、分类和应用场景
-
分析
- 什么是协处理器?
- 协处理器指的是可以自定义开发一些功能集成到Hbase中
- 类似于Hive中的UDF,当没有这个功能时,可以使用协处理器来自定义开发,让Hbase支持对应的功能
- 什么是协处理器?
-
实现
- observer类:观察者类,类似于监听机制,MySQL中的触发器、Zookeeper中的监听
- 实现:监听A,如果A触发了,就执行B
- 监听对象
- Region
- Table
- RegionServer
- Master
- 触发:监听A,如果A触发了,执行B
- pre:阻塞A,先执行B,再执行A
- post:A先执行,B在A执行完成之后再执行
- endpoint类:终端者类,类似于MySQL中的存储过程,Java中的方法
- 实现:固定一个代码逻辑,可以随时根据需求调用代码逻辑
- observer类:观察者类,类似于监听机制,MySQL中的触发器、Zookeeper中的监听
-
总结
- Hbase通过协处理器来弥补一些用户自定义功能的实现,例如二级索引等,但开发难度较高,一般通过第三方工具来实现
协处理器的实现
-
目标
- 利用协处理器模拟实现二级索引同步原表与索引表数据
-
分析
- step1:开发协处理器,监听原表的put请求
- step2:拦截原表put请求,获取put操作,获取rowkey以及值
- step3:构建索引表的rowkey,往索引表写入数据
- step4:释放原表请求,往原表写入数据
-
实现
-
创建两张表
#rowkey:time_id create 'proc1','info' #rowkey:id_time create 'proc2','info'
-
将开发好的协处理器jar包上传到hdfs上
hdfs dfs -mkdir -p /coprocessor/jar mv bulkload.jar cop.jar hdfs dfs -put cop.jar /coprocessor/jar/
-
添加协处理器到proc1中,用于监听proc1的操作
disable 'proc1' alter 'proc1',METHOD => 'table_att','Coprocessor'=>'hdfs://node1:8020/coprocessor/jar/cop.jar|bigdata.itcast.cn.hbase.coprocessor.SyncCoprocessor|1001|' enable 'proc1'
-
测试
put 'proc1','20191211_001','info:name','zhangsan' scan 'proc1' scan 'proc2'
-
卸载协处理器
disable 'proc1' alter 'proc1',METHOD=>'table_att_unset',NAME=>'coprocessor$1' enable 'proc1'
-
-
总结
- 协处理器API过于繁琐,基于不同的需求需要开发多个协处理器共同实现,不建议使用
- 建议使用Phoenix