09HBase
hbase
hbase的表设计
hbase的基本架构和读写流程
基础架构
- hbase依赖于hdfs,和zookeeper
- Zookeeper为整个HBase集群提供协助的服务,比如存储region和namespace的元数据信息
- HMaster主要用于监控和操作集群的所有RegionServer,table的create,delete,alter
- RegionServer主要用于服务和管理分区(Regions),数据的get,put,delete,region的split[[09_Hbase#520be5|切分详情]],compact[[09_Hbase#3192ef|合并详情]] RegionServer组成
- 多个Region
- 每个regionserver对应着一个BlockCache,用作读HBbase时数据的读缓存 Region组成
- 多个memstore,一个列族对应一个memstore,用于对
写缓存的数据排序,当达到hbase数据刷写时机[[09_Hbase#^e1761e|刷写详情]],从内存写出为Hfile并上传到hdfs上落盘 - 每个region都对应着一个Hlog
- 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
- clien于zookeeper建立连接,并读取hbase节点下的meta-region-server节点信息,以获取meta表所在的regionserver
- 根据反馈的regionserver信息,结合请求的namespace,table,rowkey从meta表中获得待查表的
具体分布(即table所在的regionserver下的那个region),并把待查表的元数据缓存在 client中meta cache中,以供下次读取 - 向region发起读数据请求,依次读取(图示中的标记顺序有误!!)MemStore,BloackCache,Hfile以上三个位置均可能存在
要读数据,且读取顺序不变,依照从内存到磁盘的读取顺序,最大程度的提高读取效率 - 将所有查到的数据进行合并,因为同一个rowkey可能有多个版本,并把合并结果缓存到BlockCache中,
以供下次重复读取 - 把合并后的数据发送给client
写数据流程
shell## 命令行写数据的格式: put 表名,rowkey,列族:列名,value值 hbase> put 't1', 'r1', 'c1', 'value' hbase(main):018:0> put 't1','1000','cf1:name','KIKI'
- 向zookeeper请求,获取hbase:meta表所在的regionserver,并向meta表所在的regionserver请求meta表数据,并缓存在client的meta cache中
- 向目标表所在的Region的Wal文件中写入数据,之后再MemStore中排序,随后就给client响应数据写入
成功 - 当达到数据刷写磁盘时机[[09_Hbase#^e1761e|刷写详情]],hbase自动写入hfile(storefile)
如何读取刚刚写入hbase的最新数据?
可以使用flush 表名方式手动刷写memstore中的新数据到hfile,但是没有必要,因为读数据时,第一个读取的位置就是memstore
region的切分
- 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需要合并的原因
- 基于HDFS的
小文件问题,过多将占用namenode过多的内存 - 每个memstore刷写都会产生一个storefile,况且HBase总
每条记录存在版本的概念,相同rowkey的数据在value变更,或者删除都会产生版本,造成读取效率下降 ^3192ef
数据刷写
写数据时的刷写时机
^e1761e
- 当region内的
某个memstore大小达到l128M,会进行该region内所有memstore的flush - 当数据的写操作速率高于flush的速率,就会延迟触发memestore级别,此时若
region内的所有ms数据达到
了512M,就会停止接受写请求,等待该region内的所有memstore刷写完成 - 当某个regionserver
所有region大小达到了hbase堆内存 * 0.4 * 0.95,就会触发hbase集群所有
regionserver的刷写,当写速率高于flush的速率,当数据达到hbase堆内存 * 0.4,该regionserver就会停止接受写请求,直到所有regionserver大小低于hbase堆内存 * 0.4 * 0.95,取消阻塞
预分区
- 预分区原因
减少region进行自动split时带来的性能开销 - 如何预分区
建hbase表使用关键字splts时指定分区,可以时分区规划文件,后者直接指定 - 项目没有使用预分区
数据热点问题和数据倾斜问题
数据热点
当client频繁地访问一个或者几个region,可能超过了单台regionserver负载能力,导致该RS上的其他region也不可用的情况
- rowkey的设计原则
- 唯一性原则 表级别的唯一性
- 长度原则 rowkey的大小尽量小
- 散列原则 足够散列地分布再region中,视业务情况选用
当需要进行大量的连续查询,则rowkey可以设计得集中连续地分布再region中,提高查询效率
当需要进行大量的散列插叙,则可以设计得足够散列地分布在region中,实现负载均衡
- rowkey具体设计
- rowkey取hash
这样可以将数据较随机散列的分布在regionserver中 - rowkey反转
比如rowkey中出现连续时间戳开头,那么可能会出现数据热点问题,此时,可以对rowkey字符串进行一个反转 - rowkey加盐
区别于密码学中的加盐,HBase中加盐是指在rowkey前加上随机的字符串,使得打乱rowkey的集中分布
- rowkey取hash
- 数仓实时项目中设计
利用了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的读请求优化点
- hfile存储使用类似LSM的结构存储,查询高效
- 读取Hfile时使用BoolFilter,高效定位数据位置
实时项目中使用hbase的原因
- hbase用来存储dim层维度数据,hbase读写效率高效
- 维度数据字段比较多,而hbase是一个拥有可以存储百万列的实时组件
- 项目常用来查询单条数据,对于hbase表的k-v型设计比较符合
HBase的二级索引问题
项目中使用了Phoenix On HBase的模式,可以利用Phoenix提供的二级索引功能,实现HBase的二级索引
HBase的水平扩展问题
版权声明