Hbase理论

离线与实时

  1. 离线项目为例

    • 数据生成:用户访问咨询数据、意向用户报名信息、考勤信息
    • 数据采集
      • Flume:实时数据采集:采集文件或者网络端口
      • Sqoop:离线数据同步:采集数据库的数据
    • 数据存储
      • HDFS:分布式离线文件存储系统
      • Hive:离线数据仓库
        • 将HDFS上的文件映射成了表的结构,让用户可以通过数据库和表的形式来管理大数据
    • 数据计算
      • MapReduce+YARN:分布式离线数据计算
      • Hive:通过SQL进行分布式计算
        • 将SQL语句转换为MapReduce程序,提交给YARN运行
    • 数据应用:通过对数据进行分析
      • 提高转化率:访问与咨询转化率、报名转换率
      • 考勤分析:通过考勤分析来把握所有人学习的情况
  2. 离线与实时

    • 离线:数据从产生到最后被使用,时效性比较低,时间比较长
    • 实时:数据从产生到最后被使用,时效性比较高,时间比较短
    • 方向:所有数据的价值会随着时间的流逝,价值会越来越低,希望所有数据都能被实时的计算以及处理
    • 实现实时:所有环节都必须是实时环节
      • 数据生成:实时的
      • 数据采集:实时的,Flume、Canal……
      • 数据存储:实时的,Hbase、Kafka、Redis……
      • 数据计算:实时的,Spark、Flink……
      • 数据应用:实时应用:风控系统、实时推荐、精准分析
  3. 学习知识的逻辑

    • 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

在这里插入图片描述

  • 对比

    分布式概念HDFSHbase
    对象目录 + 文件Namespace + Table
    分布式BlockRegion
    划分规则按照大小划分:128M按照范围划分

6、概念对比

概念MySQLHbase
数据库DataBaseNameSpace
数据表TableTable【分布式的】
数据分区-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
  • 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内存中的数据写入磁盘

存储设计:Table、Region、RegionServer的关系

在这里插入图片描述

  • 问题:客户端操作的是表,数据最终存在RegionServer中,表和RegionServer的关系是什么?

  • 分析

    • Table:是一个逻辑对象,物理上不存在,供用户实现逻辑操作,存储在元数据的一个概念
      • 类似于HDFS中文件
    • RegionServer:是一个物理对象,Hbase中的一个进程,管理一台机器的存储
      • 类似于HDFS中DataNode
    • Region:Hbase中数据存储的最小单元
      • 类似于HDFS中Block
      • 就是分区的概念,每张表都可以划分为多个Region,实现分布式存储
        • 默认一张表只有一个分区
      • 每个Region由一台RegionServer所管理,Region存储在RegionServer
        • 一台RegionServer可以管理多个Region
  • 观察监控
    在这里插入图片描述

存储设计: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
    • 数据分配的规则:根据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
  • 观察监控

    • 只有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中的存储
      • 一个MemStore:Region中的内存区域,会为每个Store分配一部分
        • 数据先读写MemStore
      • 0个或者多个StoreFile文件:Store中的数据文件,如果Memstore存储达到阈值,就会将内存数据写入HDFS
        • StoreFile:逻辑上属于Store的
          • 物理上存储在HDFS上:本质上存储的是HFILE:有序的二进制文件
  • 总结

    • RegionServer:Region存储在Regionserver中
      • Region:一张表有多个Region,根据Rowkey判断写入哪个region
        • 一个表中有多个列族,每个列族对应一个Store,一个region中有多个store
        • Store:根据列族判断写入哪个Store
          • memstore:内存区域
          • storefile:HDFS上的文件

存储设计: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是谁,正在执行的操作
    • 表的元数据不在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是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位
  • 总结

    • 业务原则:保证前缀是最常用的查询字段
    • 唯一原则:每条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格式的文件
  • 实现

    • 使用方式一:直接使用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中的方法
      • 实现:固定一个代码逻辑,可以随时根据需求调用代码逻辑
  • 总结

    • 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
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值