深入浅出HBase实战 |青训营笔记

199 阅读18分钟

这是我参与「第四届青训营 」笔记创作活动的第9天.

HDFS是一种开源的分布式文件系统,基于常见商用硬件构造海量大规模存储集群,提供极低的存储成本,极大的存储容量支持。HDFS提供高可靠性的数据保障,通常采用三副本冗余存储数据到不同的机器来实现容灾备份能力。

HBase是基于HDFS实现存储计算分离架构的分布式表格存储服务,数据实际存储在HDFS中。

客户端定位数据

直连HBase的客户端需要配置对应的Zookeeper信息来定位数据所在的RegionServer,具体包括zookeeper集群实例的地址列表和该hbase集群在zookeeper中对应的根路径。

直连客户端具体定位步骤如下:

  1. 客户端访问Zookeeper获取元信息表hbase:meta所在的regionserver地址;
  1. 客户端访问该regionserver查询要读/写的table的rowkey在哪个regionserver;
  1. 客户端访问存数据的regionserver进行读写。

通过Thrift协议访问HBase的客户端需要ThriftServer的地址,通过ThriftServer转发请求。可以通过Consul实现thriftserver的服务发现。

屏幕截图 2022-08-06 222719.jpg HBase将每个column family的数据独立管理,称为HStore。一个HStore包含一到多个物理文件块(称为HFile)存储到HDFS。实际存储时每个column family独立存储,一个column family对应多个HFile文件块。

每个HFile内的数据按rowkey有序存储,但HFile间没有顺序保证。这一特点是由于LSM的写入方式决定的,下面介绍LSM树的读写流程:

  • 写入:写入操作先记录到Write-Ahead Log持久化(可选)保存,然后写入内存中的MemStore。WAL可以保证实例挂掉或重启后丢失的内存数据可以恢复。
  • 读取:从写入逻辑可以看出HFile包含的rowkey范围会有交集,以全局rowkey顺序读取就需要以一种归并排序的形式组织所有HFile。HBase会打开该cf下所有HFile,分别构建一个迭代器用以rowkey从小到大扫对应HFile的数据。所有这些迭代器又以当前指向rowkey的大小组织成一个最小堆,这样堆顶的迭代器指向的rowkey就是下一个全局最小的rowkey。迭代该rowkey后重新调整最小堆即可。

一、HBase适用场景

近实时读写性能,海量数据,稀疏表、字典序批量读取,主键索引查询,大数据生态友好,快速水平扩展。

1、什么是HBase

屏幕截图 2022-08-06 211154.jpg

  • HBase 写流程:

    • 数据先写入 WAL 持久化,用于宕机时恢复内存里丢失的数据;
    • 再写入内存态 MemStore,以一种跳表(SkipList)数据结构提供有序的数据和高效的随机读写;
    • 当满足特定条件时(比如内存中数据过多,或间隔时间过长),MemStore 数据以 HFile 格式写入 HDFS
  • HBase 读流程

    • 首次读某个 rowkey 时,client 需要从 Zookeeper 获取 hbase:meta 表位于哪个 RegionServer上;
    • 然后访问该 RegionServer 查询 hbase:meta 表该 rowkey 对应 region 所在的 RegionServer B;
    • Client 缓存该位置信息,去 RegionServer B 读取 rowkey;
    • 基于该region内可能存在该 rowkey 的 HFile 和 MemStore 构建一个最小堆,用以全局有序地 scan 数据(具体实现可搜索参考 LSM tree 设计原理)
  • Compaction

    • HBase 基于策略和定期整理 HFile 文件集合,将多个有序小文件合并成若干个有序的大文件。

    • HBase 提供两种 compaction 类型:

      • Minor compaction: 指选取一些小的、相邻的StoreFile将他们合并成一个更大的StoreFile,在这个过程中不会处理已经Deleted或Expired的Cell。一次 Minor Compaction 的结果是更少并且更大的StoreFile。
      • Major compaction: 指将所有的StoreFile合并成一个StoreFile,这个过程会清理三类没有意义的数据:被删除的数据、TTL过期数据、版本号超过设定版本号的数据。另外,一般情况下,major compaction时间会持续比较长,整个过程会消耗大量系统资源,对上层业务有比较大的影响。因此线上业务都会将关闭自动触发major compaction功能,改为手动在业务低峰期触发。
    • Compaction 触发条件:

      • memstore flush:可以说compaction的根源就在于flush,memstore 达到一定阈值或其他条件时就会触发flush刷写到磁盘生成HFile文件,正是因为HFile文件越来越多才需要compact。HBase每次flush之后,都会判断是否要进行compaction,一旦满足minor compaction或major compaction的条件便会触发执行。
      • 后台线程周期性检查: 后台线程 CompactionChecker 会定期检查是否需要执行compaction,检查周期为hbase.server.thread.wakefrequency*hbase.server.compactchecker.interval.multiplier,这里主要考虑的是一段时间内没有写入请求仍然需要做compact检查。其中参数 hbase.server.thread.wakefrequency 默认值 10000 即 10s,是HBase服务端线程唤醒时间间隔,用于log roller、memstore flusher等操作周期性检查;参数 hbase.server.compactchecker.interval.multiplier 默认值1000,是compaction操作周期性检查乘数因子。10 * 1000 s 时间上约等于2hrs, 46mins, 40sec。
      • 手动触发:是指通过HBase Shell、Master UI界面或者HBase API等任一种方式 执行 compact、major_compact等命令。

2、HBase与关系型数据库的区别

屏幕截图 2022-08-06 211244.jpg

3、HBase数据模型

屏幕截图 2022-08-06 211405.jpg (1)逻辑化结构

屏幕截图 2022-08-06 211508.jpg

屏幕截图 2022-08-06 211610.jpg HBase是半结构化存储。数据以行(row)组织,每行包括一到多个列簇(column family)。使用列簇前需要通过创建表或更新表操作预先声明column family。

column family是稀疏存储,如果某行数据未使用部分column family则不占用这部分存储空间。

每个column family由一到多个列(column qualifier)组成。column qualifier不需要预先声明,可以使用任意值。

最小数据单元为cell,支持存储多个版本的数据。由rowkey + column family + column qualifier + version指定一个cell。

同一行同一列族的数据物理上连续存储,首先以column qualifier字典序排序,其次以timestamp时间戳倒序排序。

(2)物理结构

屏幕截图 2022-08-06 211651.jpg

4、使用场景

屏幕截图 2022-08-06 211822.jpg (1)典型应用

屏幕截图 2022-08-06 211910.jpg

  • 电商订单数据 抖音电商每日交易订单数据基于 HBase 存储,支持海量数据存储的同时满足稳定低延时的查询需求,并且只需相对很低的存储成本。通过多个列存储订单信息和处理进度,快速查询近期新增/待处理订单列表。同时也可将历史订单数据用于统计、用户行为分析等离线任务。
  • 搜索推荐引擎 存储网络爬虫持续不断抓取并处理后的原始网页信息,通过 MapReduce、Flink、Spark 等大数据计算框架分析处理原始数据后产出粗选、精选、排序后的网页索引集,再存储到 HBase 以提供近实时的随机查询能力,为上层的多个字节跳动应用提供通用的搜索和推荐能力。
  • 大数据生态 天生融入 Hadoop 大数据生态。对多种大数据组件、框架拥有良好的兼容性,工具链完善,快速打通大数据链路,提高系统构建落地效率,并借助 HDFS 提供可观的成本优势。敏捷平滑的水平扩展能力可以自如地应对数据体量和流量的快速增长。
  • 广告数据流 存储广告触达、点击、转化等事件流,为广告分析系统提供快速的随机查询及批量读取能力,助力提升广告效果分析和统计效率。
  • 用户交互数据 Facebook 曾使用 HBase 存储用户交互产生的数据,例如聊天、评论、帖子、点赞等数据,并利用 HBase 构建用户内容搜索功能。
  • 时序数据引擎 基于 HBase 构建适用于时序数据的存储引擎,例如日志、监控数据存储。例如 OpenTSDB(Open Time Series Database)是一个基于 HBase 的时序存储系统,适用于日志、监控打点数据的存储查询。
  • 图存储引擎 基于 HBase 设计图结构的数据模型,如节点、边、属性等概念,作为图存储系统的存储引擎。例如 JanusGraph 可以基于 HBase 存储图数据。

(2)半结构化/字典序有序索引的数据

屏幕截图 2022-08-06 212009.jpg (3)近在线 海量分布式KV /宽表存储

屏幕截图 2022-08-06 212114.jpg

屏幕截图 2022-08-06 212220.jpg (4)写密集型的高吞吐场景

屏幕截图 2022-08-06 212307.jpg 5、HBase数据模型的优点

屏幕截图 2022-08-06 212356.jpg

二、HBase架构设计

HM管理元数据,调度保活。RS提供数据读写。依赖ZK协调调度,组件探活,同时实现元数据管理和服务发现。CLient通过ZK配置连接到HBase集群。基于HDFS存储底座实现存储计算分离架构,

1、HBase架构设计

屏幕截图 2022-08-06 212500.jpg

(1)Hmaster

  • 主要职责

元信息管理组件,以及集群调度、保活等功能。通常部署一个主节点和一到多个备节点,通过Zookeeper选主。

屏幕截图 2022-08-06 212609.jpg

  • 主要组件

屏幕截图 2022-08-06 212738.jpg

CatalogJanitor

定期扫描元数据hbase:meta的变化,回收无用的region(仅当没有region-in-transition时)。

scan方法执行扫描、回收操作:

  1. 扫描一遍hbase:meta找出可回收的region,检查元数据一致性并生成报告实例
  1. 已完成merge或split的region都可被回收,即子目录/父目录没有reference文件后
  1. 创建GCMultipleMergedRegionsProcedure/GCRegionProcedure异步回收regions

    1. GCMultipleMergedRegionsProcedure步骤:

      1. merge的两个源region分别创建GCRegionProcedure删除region数据
      2. 删除merge生成region元信息的merge qualifiers(info:mergeN)
    2. GCRegionProcedure步骤:

      1. archive要回收region的HFiles
      2. 删除region对应的WAL
    3. assignmentManager删除region state

    4. meta表删除该region记录

    5. masterService、FavoredNodesManager删除该region

AssignmentManager

管理region分配,processAssignQueue方法每当pendingAssignQueue放满RegionStateNode时批量处理。单独启动daemon线程循环处理。

processAssignmentPlans方法雇用LoadBalancer

  1. 首先尝试尽量保持现有分配
  1. 将分配plan的目标server region location更新到对应RegionStateNode中
  1. 将regionNNode的ProcedureEvent放入队列,统一唤醒所有regionNodes的events上等待的procedures
  1. 不保持原有分配的通过loadbalancer的round-robin策略分配(原则是不能降低availability)
  1. 分配失败的塞回pendingAssignQueue下次重新处理

setupRIT方法处理RIT,仅仅是设置RegionStateNode的this.procedure和ritMap

  • 故障恢复机制-Hmaster

屏幕截图 2022-08-06 214738.jpg 恢复流程:

  • 自身恢复流程:1、监听到/hbase/active-master临时节点被删除的事件,触发选主逻辑。2、选主成功后执行HMaster启动流程,从而持久化存储读取未完成的procedures从之前状态继续执行。3、故障Hmaster实例恢复后发现主节点已存在,继续监听/hbase/active-master。
  • 调度RegionServer的故障恢复流程:1、AssignmenManager从Procedure列表中找出Region-In-Transition状态下region继续调度过程。2、RegionServerTracker从Zookeeper梳理Online状态的RegionServer列表,结合ServerCrashProcedure列表、HDFS中WAL目录里alive/splitting状态的Region Server记录,获取掉线的RS的列表,分布创建ServerCrashProced执行恢复流程。

(2)RegionServer

  • 主要职责

提供数据读写服务,每个实例负责一段不重叠的连续rowkey范围内的数据。

屏幕截图 2022-08-06 212840.jpg

  • 主要组件

屏幕截图 2022-08-06 212920.jpg

  • 故障恢复机制-RS

屏幕截图 2022-08-06 215803.jpg

  • 恢复流程:

屏幕截图 2022-08-06 215840.jpg

(3)ZooKeeper主要职责

提供分布式一致性的元数据管理服务。HBase使用Zookeeper实现master节点信息登记、master节点选主、RegionServer信息登记、分布式任务管理等功能。

屏幕截图 2022-08-06 213030.jpg

(4)ThriftServer主要职责

提供一层以Thrift协议访问数据的代理层。

屏幕截图 2022-08-06 213139.jpg

三、大数据支持

热点切分避免数据分布倾斜带来党的性能瓶颈,碎片整合清理过小的数据分片来维持元数据在合理的形态。负载均衡通过巡检调度各实例负载来避免性能瓶颈及优化集群资源利用率。

功能

Bulkload

大批量向HBase导入数据的功能。使用MapReduce任务直接生成底层存储的HFile文件,并直接移动到HBase存储目录下,节省HBase写路径的开销从而提高写入效率。

Coprocessor

提供一套接口框架,给HBase原生接口添加类似lifecycle hook函数的能力,用来执行用户自定义的功能来扩展HBase的能力,例如二级索引、Observer等功能。

Filter

将用户的过滤逻辑下推到HBase服务端,避免无用数据传输处理开销来提高查询效率。

MOB

Medium Object Storage解决HBase对中等大小对象(10-100MB)的低延迟读写支持,拓宽HBase适用场景。

Snapshot

数据备份功能,将某一时刻的数据以及元数据备份,用于数据恢复、快照读、复制表等功能。

Replication

将一个HBase集群中的数据复制到目标HBase集群,使用WAL将变更记录同步到其他集群。

1、HBase在大数据生态的定位

屏幕截图 2022-08-06 213306.jpg 通过在 HBase之上引入各种组件可以使HBase应用场景得到极大扩展,例如监控、车联网、风控、实时推荐、人工智能等场景的需求。

  1. Phoenix

主要提供SQL的方式来查询HBase里面的数据。一般能够在毫秒级别返回,比较适合OLTP以及操作性分析等场景,支持构建二级索引。

  1. Spark

很多企业使用HBase存储海量数据,一种常见的需求就是对这些数据进行离线分析,我们可以使用Spark(Spark SQL) 来实现海量数据的离线分析需求。同时,Spark还支持实时流计算,我们可以使用 HBase+Spark Streaming 解决实时广告推荐等需求。

  1. HGraphDB

分布式图数据库,可以使用其进行图 OLTP查询,同时结合 Spark GraphFrames 可实现图分析需求,帮助金融机构有效识别隐藏在网络中的黑色信息,在团伙欺诈、黑中介识别等。

  1. GeoMesa

目前基于NoSQL数据库的时空数据引擎中功能最丰富、社区贡献人数最多的开源系统。提供高效时空索引,支持点、线、面等空间要素存储,百亿级数据实现毫秒(ms)级响应;提供轨迹查询、区域分布统计、区域查询、密度分析、聚合、OD 分析等常用的时空分析功能;提供基于Spark SQL、REST、GeoJSON、OGC服务等多种操作方式,方便地理信息互操作。

  1. OpenTSDB

基于HBase的分布式的,可伸缩的时间序列数据库,适合做监控系统;比如收集大规模集群(包括网络设备、操作系统、应用程序)的监控数据并进行存储,查询。

  1. Solr

原生的HBase只提供了Rowkey单主键,仅支持对Rowkey进行索引查找。可以使用 Solr来建立二级索引/全文索引来扩展更多查询场景的支持。

2、水平扩展能力

屏幕截图 2022-08-06 213357.jpg

3、Region热点切分

屏幕截图 2022-08-06 213512.jpg (1)切分点选取

屏幕截图 2022-08-06 213606.jpg

屏幕截图 2022-08-06 213629.jpg

屏幕截图 2022-08-06 213739.jpg

屏幕截图 2022-08-06 213806.jpg (2)切分过程

屏幕截图 2022-08-06 213849.jpg

屏幕截图 2022-08-06 213926.jpg

屏幕截图 2022-08-06 213949.jpg

屏幕截图 2022-08-06 214020.jpg (3)流程设计

屏幕截图 2022-08-06 214103.jpg (4)碎片整合

image.png 设计流程:

屏幕截图 2022-08-06 214240.jpg (5)负载均衡

屏幕截图 2022-08-06 214325.jpg 调整策略:

屏幕截图 2022-08-06 214405.jpg 其他策略:

屏幕截图 2022-08-06 214442.jpg

4、Distributed Log Split原理

屏幕截图 2022-08-06 220001.jpg

  • 具体流程:

屏幕截图 2022-08-06 220046.jpg

屏幕截图 2022-08-06 220114.jpg

  • 优化空间:

屏幕截图 2022-08-06 220154.jpg

四、最佳实践分享

根据查询模式选择合适的RK设计,避免数据分布侵倾斜。避免冗长的RW。CF、Column Qualifier浪费存储空间,控制CF数量。

1、Rowkey设计策略

  • 最大长度是64KB,实际应用中长度一般为 10 ~ 100bytes。key在保证功能的前提下建议越短越好,因为key是冗余到每个cell存储的,过长的key会占用更多存储、缓存空间。
  • 设计Key时,要充分利用排序存储这个特性,将经常一起读取的行存储到一起。HBase以HFile文件块为单位从HDFS读取数据,一次性读出相邻相关数据可以达到随机读变成顺序读的效果。

但同时要防止出现热key聚焦打爆region server实例。

反例:以时间戳作rowkey前缀,一段时间的请求会全部打到同一regionserver。

屏幕截图 2022-08-06 220243.jpg

2、Column Family设计策略

屏幕截图 2022-08-06 220359.jpg

3、参数调优经验

屏幕截图 2022-08-06 220435.jpg

4、字节研发分布式表格存储系统

屏幕截图 2022-08-06 220521.jpg

value大小

建议不要超过1MB。过大的value会影响HBase读写性能。可以将实际value存储在其他对象存储系统,在HBase的value存储其位置信息。

Region数量

一个region的大小最好在10-50GB之间。

mapreduce的应用在低峰期运行

批处理任务建议在业务低峰期运行,并且需要对HBase的访问流量进行一定限制。

尽量复用client实例

新建client实例需要访问Zookeeper和元信息表所在regionserver,频繁操作有打垮服务的风险。

client参数调优

  • nodelay设置true
  • 读较多的应用中gc的新生代不能设置太小
  • 数据版本尽可能少来增加有效缓存容量,提升命中率
  • 避免一次scan过多的row,尽量拆分为多次小规模scan作分页查询

五、补充知识点

HDFS目录结构

MemStore

MemStore数据结构详细介绍可参考 hbasefly.com/2019/10/18/…

MemStore使用SkipList组织KeyValue数据,提供O(logN)的查询/插入/删除操作,支持双向遍历。

具体实现采用JDK标准库的ConcurrentSkipListMap,结构如下图:

写一个KeyValue到MemStore的顺序如下:

  1. 在JVM堆中为KeyValue对象申请一块内存区域。
  1. 调用ConcurrentSkipListMap的put(K key, V value)方法将这个KeyValue对象作为参数传入。

HFile

HFile结构介绍详见hbasefly.com/2016/03/25/…

HFile是HBase存储数据的文件组织形式,参考BigTable的SSTable和Hadoop的TFile实现。HFile共经历了三个版本,其中V2在0.92引入,V3在0.98引入。HFileV1版本的在实际使用过程中发现它占用内存多,HFileV2版本针对此进行了优化,HFileV3版本基本和V2版本相同,只是在cell层面添加了Tag数组的支持。这里主要针对V2版本进行分析。

官方文档的HFile结构图如下:

HFlie主要分为4个section

  • Scanned block section:顾名思义,表示顺序扫描HFile时所有的数据块将会被读取,包括Leaf Index Block和Bloom Block。
  • Non-scanned block section:表示在HFile顺序扫描的时候数据不会被读取,主要包括Meta Block和Intermediate Level Data Index Blocks两部分。
  • Load-on-open-section:这部分数据在HBase的region server启动时,需要加载到内存中。包括FileInfo、Bloom filter block、data block index和meta block index。
  • Trailer:这部分主要记录了HFile的基本信息、各个部分的偏移值和寻址信息。

\

一个HFile内的数据会被切分成等大小的block,每个block的大小可以在创建表列簇的时候通过参数blocksize => ‘65535’进行指定,默认为64k,大号的Block有利于顺序Scan,小号Block利于随机查询,需要根据使用场景来权衡block大小。

HFileBlock

block有多种类型,除了data block还包括index block和bloom block来优化数据读取。但格式统一抽象如下:

Data Block

HBase中数据存储的最小文件单元。主要存储用户的KeyValue数据。KeyValue是HBase数据表示的最基础单位,每个数据都是以KeyValue结构在HBase中进行存储。KeyValue结构的内存/磁盘表示如下:

可以看出Row、ColumnFamily、ColumnQualifier是每个KeyValue都会存储,有明显的信息冗余。因此设计名称时最好比较精简,尽量减小重复信息占用的存储。

Block Index

可参见hbasefly.com/2016/04/03/…

索引分为单层和多层两种。单层即root data index,在打开HFile时就全量加载进内存。结构表示如下:

  • Block Offset 表示索引指向数据块的偏移量,
  • BlockDataSize 表示索引指向数据块在磁盘上的大小,
  • BlockKey 表示索引指向数据块中的第一个key
  • 其他三个字段记录HFile所有Data Block中最中间的一个Data Block,用于在对HFile进行split操作时,快速定位HFile的中间位置

Root Index Entry不是定长的,因此需要在Trail Block中的dataIndexCount记录entry数量才能保证正常加载。

NonRoot Index一般有intermediate和leaf两层,具有相同结构。Index Entry指向leaf索引块或者数据块。NonRoot index通过entry Offset实现索引块内的二分查找来优化查询效率。

完整的索引流程如下图,rowkey通过多级索引以类似B+树的方式定位到所属的数据块。

⭐部分补充我们没有,详情参见【大数据专场 学习资料四】第四届字节跳动青训营 - 掘金 (juejin.cn)HMa 启动流程到实践前。