首页标签分类
09HBase
2024-06-03 · 更新 2026-03-03约 7 分钟 · 1942 字
大数据杂文记
000

目录

hbase
hbase的表设计
hbase的基本架构和读写流程
基础架构
HBase存储在Hdfs上的实际目录结构
读数据流程
写数据流程
region的切分
storefile的compact
数据刷写
预分区
数据热点问题和数据倾斜问题
数据热点
数据倾斜
二级索引
flink写数据hbase,如何保证数据一致性?
hbase的读请求优化点
实时项目中使用hbase的原因
HBase的二级索引问题
HBase的水平扩展问题

hbase

hbase的表设计

hbase的基本架构和读写流程

基础架构

  1. hbase依赖于hdfs,和zookeeper
  2. Zookeeper为整个HBase集群提供协助的服务,比如存储region和namespace的元数据信息
  3. HMaster主要用于监控和操作集群的所有RegionServer,table的create,delete,alter
  4. RegionServer主要用于服务和管理分区(Regions),数据的get,put,delete,region的split[[09_Hbase#520be5|切分详情]],compact[[09_Hbase#3192ef|合并详情]] RegionServer组成
  5. 多个Region
  6. 每个regionserver对应着一个BlockCache,用作读HBbase时数据的读缓存 Region组成
  7. 多个memstore,一个列族对应一个memstore,用于对写缓存的数据排序,当达到hbase数据刷写时机[[09_Hbase#^e1761e|刷写详情]],从内存写出为Hfile并上传到hdfs上落盘
  8. 每个region都对应着一个Hlog
  9. HFile为memstore中刷写后的文件

HBase存储在Hdfs上的实际目录结构

java
自动换行:关
放大阅读
展开代码
/hbase /data /<Namespace> (Namespaces in the cluster) /<Table> (Tables in the cluster) /<Region> (Regions for the table) /<ColumnFamily> (ColumnFamilies for the Region for the table) /<StoreFile> (StoreFiles for the ColumnFamily for the Regions for the table)

读数据流程

[!example|noborder grid]+ 读数据


shell
自动换行:关
放大阅读
展开代码
## 命令行读数据的格式: 1. 单独行 get 表名,rowkey,{COLUMN => 列族:列名} 2. 多行 scan
  1. clien于zookeeper建立连接,并读取hbase节点下的meta-region-server节点信息,以获取meta表所在的regionserver
  2. 根据反馈的regionserver信息,结合请求的namespace,table,rowkey从meta表中获得待查表的
    具体分布(即table所在的regionserver下的那个region),并把待查表的元数据缓存在 client中meta cache中,以供下次读取
  3. 向region发起读数据请求,依次读取(图示中的标记顺序有误!!)MemStore,BloackCache,Hfile以上三个位置均可能存在
    要读数据,且读取顺序不变,依照从内存到磁盘的读取顺序,最大程度的提高读取效率
  4. 将所有查到的数据进行合并,因为同一个rowkey可能有多个版本,并把合并结果缓存到BlockCache中,
    以供下次重复读取
  5. 把合并后的数据发送给client

写数据流程

shell
自动换行:关
放大阅读
展开代码
## 命令行写数据的格式: put 表名,rowkey,列族:列名,value值 hbase> put 't1', 'r1', 'c1', 'value' hbase(main):018:0> put 't1','1000','cf1:name','KIKI'

  1. 向zookeeper请求,获取hbase:meta表所在的regionserver,并向meta表所在的regionserver请求meta表数据,并缓存在client的meta cache中
  2. 向目标表所在的Region的Wal文件中写入数据,之后再MemStore中排序,随后就给client响应数据写入
    成功
  3. 当达到数据刷写磁盘时机[[09_Hbase#^e1761e|刷写详情]],hbase自动写入hfile(storefile)

如何读取刚刚写入hbase的最新数据?
可以使用flush 表名方式手动刷写memstore中的新数据到hfile,但是没有必要,因为读数据时,第一个读取的位置就是memstore

region的切分

  1. region切分的原因
  • region使用需要占用regionserver的内存的,当过大,占用就越高,就会因为一个或者几个region而影响regionserver下的其他region的正常工作
  • region数量也不宜过多,否则会因为数据分布在过多的region中,降低了读写的能力 ^520be5 0.94: 固定按照 10G切
    0.94-2.0: min(10G, 128M * R^3 * 2) R
    ,这张表的 Region数量
    2.0: 第一次按照 256M切,后面都按照 10G切

storefile的compact

stroefile需要合并的原因

  1. 基于HDFS的小文件问题,过多将占用namenode过多的内存
  2. 每个memstore刷写都会产生一个storefile,况且HBase总每条记录存在版本的概念,相同rowkey的数据在value变更,或者删除都会产生版本,造成读取效率下降 ^3192ef

数据刷写

写数据时的刷写时机

^e1761e

  1. 当region内的某个memstore大小达到l128M,会进行该region内所有memstore的flush
  2. 当数据的写操作速率高于flush的速率,就会延迟触发memestore级别,此时若region内的所有ms数据达到
    了512M,就会停止接受写请求,等待该region内的所有memstore刷写完成
  3. 当某个regionserver所有region大小达到了hbase堆内存 * 0.4 * 0.95,就会触发hbase集群所有
    regionserver的刷写,当写速率高于flush的速率,当数据达到hbase堆内存 * 0.4,该regionserver就会停止接受写请求,直到所有regionserver大小低于hbase堆内存 * 0.4 * 0.95,取消阻塞

预分区

  1. 预分区原因
    减少region进行自动split时带来的性能开销
  2. 如何预分区
    建hbase表使用关键字splts时指定分区,可以时分区规划文件,后者直接指定
  3. 项目没有使用预分区

数据热点问题和数据倾斜问题

数据热点

当client频繁地访问一个或者几个region,可能超过了单台regionserver负载能力,导致该RS上的其他region也不可用的情况

  1. rowkey的设计原则
    • 唯一性原则 表级别的唯一性
    • 长度原则 rowkey的大小尽量小
    • 散列原则 足够散列地分布再region中,视业务情况选用 当需要进行大量的连续查询,则rowkey可以设计得集中连续地分布再region中,提高查询效率
      当需要进行大量的散列插叙,则可以设计得足够散列地分布在region中,实现负载均衡
  2. rowkey具体设计
    • rowkey取hash
      这样可以将数据较随机散列的分布在regionserver中
    • rowkey反转
      比如rowkey中出现连续时间戳开头,那么可能会出现数据热点问题,此时,可以对rowkey字符串进行一个反转
    • rowkey加盐
      区别于密码学中的加盐,HBase中加盐是指在rowkey前加上随机的字符串,使得打乱rowkey的集中分布
  3. 数仓实时项目中设计
    利用了phoenix进行hbase映射,建表使用phoenix的加盐表,具体原理为:
java
自动换行:关
放大阅读
展开代码
// 使用phoenix在建维度表时指定该表的预分区 StringBuilder bu = new StringBuilder(); bu.append("create table if not exists ") .append(tp.getSinkTable()+ "(") .append(tp.getSinkColumns().replaceAll("[^,]+","$0 varchar")) .append(",constraint pk primary key (" + (tp.getSinkPk() == null? "id": tp.getSinkPk())) .append("))") .append(tp.getSinkExtend() == null? "": tp.getSinkExtend()); System.out.println("建表sql为:" + bu); /** phoenix加盐表原理: ①需要保证加盐表需要预先分区,比如将DIM_SKU_INFO预分为4个区, ② hash(rowkey)% salt_bucket 的得到新rowkey的前缀,并拼接,如当使用phoenix向hbae插入数据时,每条rowkey前加入ox00 ~ 0x03的字节,保证数据均匀分布在regionserver中 create table DIM_SKU_INFO( id varchar,,price varchar ,sku_name varchar, constraint pk primary key (id) ) SALT_BUCKETS = 4 */

数据倾斜

分布再一个或者几个region上的数据比其他region超大,导致的数据倾斜
本质还是rowkey的不均匀分布造成的,需要重新设置rowkey

二级索引

往往在读hbase数据时,不会只按照rowkey的一级索引查询,此时,就会进行hbase的全表扫描,效率低 1.全局索引(默认建表索引,也是电商实时项目中的选择)

sql
自动换行:关
放大阅读
展开代码
-- 对studen表的address字段建立本地索引 create index global_index on table STUDENT(address);

全局索引会建立一张新的索引表,新表的rowkey为 原表的rowkey + address
此时表数量增加,对比本地索引写速率下降(更新数据时需要额外维护一张或者多张索引表),对比原表没有索引时的全表扫描,读速率提升,适合读多写少的场景 2.本地索引

sql
自动换行:关
放大阅读
展开代码
-- 对studen表的name字段建立本地索引 create local index local_index on table STUDENT(name);

本地索引会为原表的每一条数据加入一条数据 数据 rowkey为 原row_key + name
但此时数据量增加,对比原表读效率提升,对比全局索引写速率提升 3.包含索引

sql
自动换行:关
放大阅读
展开代码
-- 对studen表的address字段建立包含索引 create index include_index on table STUENDT(name) INCLUDE(address);

包含索引include的字段在索引表中为普通字段,在读取数据时在磁盘中,减少加载索引表的内存占用
对比默认的全局索引,索引表需要全部加载入内存中,内存占用较包含索引表占用高

flink写数据hbase,如何保证数据一致性?

hbase利用rowkey的唯一性,实现幂等写入

hbase的读请求优化点

  1. hfile存储使用类似LSM的结构存储,查询高效
  2. 读取Hfile时使用BoolFilter,高效定位数据位置

实时项目中使用hbase的原因

  1. hbase用来存储dim层维度数据,hbase读写效率高效
  2. 维度数据字段比较多,而hbase是一个拥有可以存储百万列的实时组件
  3. 项目常用来查询单条数据,对于hbase表的k-v型设计比较符合

HBase的二级索引问题

项目中使用了Phoenix On HBase的模式,可以利用Phoenix提供的二级索引功能,实现HBase的二级索引

HBase的水平扩展问题

本文作者:hedeoer

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!