关于 HBase 的一些经验

发布时间 2023-04-01 14:27:59作者: owenqing

1. 理解 HBase

[1] 深入理解 HBase Compaction 机制
[2] HBase 架构详解及读写流程
[3] HBase 问题合集

  • 一个列族对于一个 Store
  • 一个 Store 包含一个 MemStore
  • 每次从 MemStore Flush 都会产生 StoreFlie(底层是 HFile 格式的文件)
  • 每次 Flush 都会检查是否需要 compact StoreFile 文件,compact 分为小合并与大合并,是一个高 I/O 的操作。compact 主要是为了获取稳定的读性能(打开文件多消耗大)
  • 一个 RegionServer 公用一个 HLog (减小 I/O 开销)
  • LSM Tree 都会有写放大的现象

compcat:

  1. minor compcat (小合并)降低读数据 open file 的数量提升性能
  2. major compcat (大合并) 删除TTL过期数据、版本号超过设定版本号的数据

2. 关于 HBase 的一些问题

2.1 rowkey 设计原则

rowkey 设计的不好会数据倾斜,导致热点访问(部分 Region Server 压力增大)。

2.1.1 长度原则

rowkey 设计要尽可能的短,过长会占用更多的元数据空间。

2.1.2 散列原则

  1. reverse (利用数据尾部的随机性)
  2. salt
  3. hash
  • 小数据量 < 100w: 主键拼接CONCAT_WS('-', CAST(pk1 AS string), CAST(pk2 AS string))
  • 大数据量 > 100w: 数据量大需要预分区,通过在 rowkey 上加前缀实现
    1. 自增 id 直接反转 REVERSE(CAST(id AS string))
    2. hash MD5(CAST(id AS string))

通用处理方式 CONCAT_WS('-', SUBSTRING(MD5((CAST(id AS String))), 0, 1), CAST(id AS String)) 主键 hash 一个 md5 前缀

2.1.3 唯一原则

2.2 HBase 分区过多的危害

  1. 频繁刷写。一个 region 里面的一个 Store 对应有一个 MemStore。region 越多 MemStore 分配到的内存越小,会导致频繁 flush。
  2. 压缩风暴。频繁 Flush 会导致 HFile 小文件过多,触发 Compact,非常消耗 I/O 资源。
  3. MSLAB (MemStore Local Allocation** Buffer) 内存消耗大**。MSLAB 存在每个 MemStore 中,主要是为了解决 HBase 内存碎片问题,默认 2MB 用于缓存最新数据。1000个包含1个列族的Region,MSLAB就会使用1.95GB的堆内存,即使没有数据写入也会消耗这么多内存。
  4. Master 分配 Region 时间较长,重启困难。特别体现在重启HBase时Region上线时间较长,严重的会达到小时级,造成业务长时间等待的后果。(重启困难的原因:HBase 元数据管理是 Region 级别的,每一个 4 BM)

2.3 HBase 性能调优

2.3.1 客户端调优

  1. 先缓存在批量写入 setAutuFlush(false)

通过调用 HTable.setAutoFlush(false) 方法可以将 HTable 写客户端的自动 flush 关闭,这样可以批量写入数据到 HBase,而不是有一条 put 就执行一次更新,只有当 put 填满客户端写缓存时,才实际向 HBase 服务端发起写请求。默认情况下 auto flush 是开启的

  1. scan setCaching setBatch以空间换时间
  • .setCaching => .setNumberOfRowsFetchSize (客户端每次 rpc fetch 的行数)
  • .setMaxResultSize => .setMaxResultByteSize (客户端缓存的最大字节数)
  • .setBatch => .setColumnsChunkSize (客户端每次获取的列数
  1. 跳过 WAL

2.3.2 服务端调优

  1. 合理的 Region 大小 5-10GB
    1. Region 过小,会频繁触发 split, 集群不稳定
    2. Region 过大,一个 region 得不到 split, 同一个 region 发生多次 compact 概率变大
  2. 合理的 cell 大小(不超过 10MB)
  3. 一个表的 column family 不超过三个
    1. Hbase 管理不好多个列族,一个 region 下有一个 StoreFile 达到大小就会触发 split。不同列族数据不均衡,会导致一些不必要的 I/O 开销
    2. 如果表存在多个列族,列族A有100万行,列族B有10亿行,那么列族A可能会被分散到很多个Region上,这会导致扫描列族A的性能低下
  4. 数据压缩
  5. 预分区 (解决热点请求与负载均衡)
  6. 禁止大合并与 split , 服务非高峰时期手动执行

2.3 HBase 二级索引

HBase 最基础的查询分为三种:

  1. 根据 rowkey get
  2. 根据 rowkey 前缀进行 StartRow EndRow 的 Range Scan
  3. 全表 Scan

二级索引的目的就是想除了根据 rowkey 查询,还能根据其它列进行 RANGE SCAN。

2.3.1 一级索引: RowKey

2.3.2 二级索引

  1. 借助 phoenix 组件实现(基于协处理器):
  • 全局索引 (global index) : 两张表,数据表与索引表,适合读多少写的场景
    • CREATE INDEX age_idx ON table(age);
    • 如果查询的字段不仅仅是二级索引的字段,那么全局索引将不被使用。例: 对 age 添加索引 SELECT age, name FROM student WHERE age = 18 这个语句将是 FULL SCAN
  • 包含索引 (covered index) 本质上是全局索引。
    • CREATE INDEX age_idx ON table(age) INCLUDE (name)一个 row 里面两条数据
    • RANGE SCAN
  • 本地索引 (local index)
    • 与数据同一张表(且是同一个 region), 减少 I/O 开销,适合写比较多的场景
    • CREATE LOCAL INDEX x_idx ON table(col1, col2, ...)
  1. 借助 Elasticsearch 实现

存储需要索引的字段与 rowkey 的关系,在 ES 中查出一堆 rowkey 再去 hbase batch get

2.4 HBase 故障转移

WAL (write ahead log) 机制,数据在同一个 RegionServer 上写入到同一个 HLog。服务器发生故障在 SPLIT HLog 进行恢复
【阅读】HBase RegionServer宕机数据恢复

2.5 HBase LSM Tree

LSM Tree 分为内存部分,与磁盘部分

  • MemStore 内存中使用的是 SkipList (有序,更少的锁)
  • 磁盘中的树采用 b+ 树

内存部分的 MemStore 会 Flush 到磁盘上形成一个 HFile 文件(b+ 树)
【详细阅读】HBase-LSM-Tree

LSM Tree 的特点:

  • LSM Tree 会导致写放大,会多次拷贝数据进行合并
  • LSM Tree 在内存中保证数据的有序性,到磁盘中是顺序写入(append only), 极大的提高了写入效率