青训营 深入浅出 HBase 实战
概述
2022の夏天,半壶水响叮当的我决定充实一下自我
一、内容介绍
青训营
总述
- 介绍 HBase 的适用场景和数据模型
- 分析 HBase 的整体架构和模块设计
- 针对大数据场景 HBase 的解决方案
- 分享 HBase 大规模实战的最佳实践
分布式系统理论基础
HBase 核心数据模型
- HBase 是存储计算分离架构,以 HDFS 作为分布式存储底座。数据实际存储在 HDFS。
- HBase 依赖 Zookeeper 实现元数据管理和服务发现。Client 通过 Zookeeper 配置连接到 HBase集群
- Log-Structured Merge Tree 了解 LSM tree 的基本结构和特性。
- HBase 写流程:
- WAL(Write Ahead Log)预写日志
- 数据先写入 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等命令。
-
客户端定位数据
直连HBase的客户端需要配置对应的Zookeeper信息来定位数据所在的RegionServer,具体包括zookeeper集群实例的地址列表和该hbase集群在zookeeper中对应的根路径。
直连客户端具体定位步骤如下:
- 客户端访问Zookeeper获取元信息表hbase:meta所在的regionserver地址;
- 客户端访问该regionserver查询要读/写的table的rowkey在哪个regionserver;
- 客户端访问存数据的regionserver进行读写。
通过Thrift协议访问HBase的客户端需要ThriftServer的地址,通过ThriftServer转发请求。可以通过Consul实现thriftserver的服务发现。
LSM tree
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的设计理念、数据模型、适用场景、业界典型用例
2.1 什么是 HBase ?
HBase是一个开源的NoSQL分布式数据库,是Apache软件基金会顶级项目之一。
- 参考Google BigTable的设计,对稀疏表提供更高的存储空间使用率和读写效率。
采用存储计算分离架构
- 存储层基于HDFS存储数据,提供容错机制和高可靠性;
- 计算层提供灵活快速的水平扩展、负载均衡和故障恢复能力;
提供强一致语义,在CAP理论中属于CP系统
- Consistency/一致性,Availability/可用性,Partition Tolerance/分区容限
2.2 HBase和关系型数据库的区别
2.3 HBase数据模型
HBase以列族(column family))为单位存储数据,以行键(rowkey)索引数据
- 列族需要在使用前预先创建,列名(column qualifier)不需要预先声明,因此支持半结构化数据模型
- 支持保留多个版本的数据,(行键 + 列族 + 列名 + 版本号)定位一个具体的值
2.3.1 HBase数据模型-逻辑结构
通过非关系型视图理解HBase数据模型:
- 适合稀疏数据,缺省列不占用存储空间.
- 通过(rowkey, column family, column qualifier, version/行键,列族,列限定符,版本)唯一指定一个具体的值
- 允许批量读取多行的部分列族/列数据
json:
简单起见可以将HBase数据格式理解为如下结构:
// table名格式:"${namespace}:${table}"
// 例如:table = "default:test_table"
[
"rowKey1": { // rowkey定位一行数据
"cf1": { // column family需要预先定义到表结构
"cq_a": { // column qualifier无需定义,使用任意值
"timestamp3": "value3",
// row=rowKey1, column="cf1:cf_a", timestamp=timestamp2
// 定位一个cell
"timestamp2": "value2",
"timestamp1": "value1"
},
"cq_b": {
"timestamp2": "value2",
"timestamp1": "value1"
}
},
"cf3": {
"cq_m": {
"timestamp1": "value1"
},
"cq_n": {
"timestamp1": "value1"
}
},
},
"rowKey3": {
"cf2": { // 缺省column family不占用存储空间
"cq_x": {
"timestamp3": "value3",
"timestamp2": "value2",
"timestamp1": "value1"
},
"cq_y": {
"timestamp1": "value1"
}
},
}
]
2.3.2 HBase数据模型–物理结构
物理数据结构最小单元是KeyValue结构:
- 每个版本的数据都携带全部行列信息
- 同一行,同一列族的数据物理上连续有序存储
- 同列族内的 KeyValue按rowkey/行 字典序升序, column qualifier/列 限定升序, version/版本 降序排列
- 不同列族的数据存储在相互独立的物理文件,列族间不保证数据全局有序
- 同列族下不同物理文件间不保证数据全局有序
- 仅单个物理文件内有序
2.4 使用场景
- “近在线”的海量分布式KV/宽表存储,数据量级达到百TB级以上
- 写密集型应用,高吞吐,可接受一定的时延抖动
- 需要按行顺序扫描的能力
- 接入Hadoop大数据生态
- 结构化、半结构化数据,可以经常新增/更新列属性
- 平滑的水平扩展
2.4.1 业务落地场景包括:
- 电商订单数据 抖音电商每日交易订单数据基于 HBase 存储,支持海量数据存储的同时满足稳定低延时的查询需求,并且只需相对很低的存储成本。通过多个列存储订单信息和处理进度,快速查询近期新增/待处理订单列表。同时也可将历史订单数据用于统计、用户行为分析等离线任务
- 搜索推荐引擎 存储网络爬虫持续不断抓取并处理后的原始网页信息,通过 MapReduce、Flink、Spark 等大数据计算框架分析处理原始数据后产出粗选、精选、排序后的网页索引集,再存储到 HBase 以提供近实时的随机查询能力,为上层的多个字节跳动应用提供通用的搜索和推荐能力
- 大数据生态 天生融入 Hadoop 大数据生态。对多种大数据组件、框架拥有良好的兼容性,工具链完善,快速打通大数据链路,提高系统构建落地效率,并借助 HDFS 提供可观的成本优势。敏捷平滑的水平扩展能力可以自如地应对数据体量和流量的快速增长
- 广告数据流 存储广告触达、点击、转化等事件流,为广告分析系统提供快速的随机查询及批量读取能力,助力提升广告效果分析和统计效率
- 用户交互数据 Facebook 曾使用 HBase 存储用户交互产生的数据,例如聊天、评论、帖子、点赞等数据,并利用 HBase 构建用户内容搜索功能。
- 时序数据引擎 基于 HBase 构建适用于时序数据的存储引擎,例如日志、监控数据存储。例如 OpenTSDB(Open Time Series Database)是一个基于 HBase 的时序存储系统,适用于日志、监控打点数据的存储查询。
- 图存储引擎 基于 HBase 设计图结构的数据模型,如节点、边、属性等概念,作为图存储系统的存储引擎。例如 JanusGraph 可以基于 HBase 存储图数据。
2.4.2 使用场景–半结构化/字典序有序索引的数据
2.4.3 使用场景-“近在线”海量分布式 KV/宽表存储
2.4.4 使用场景–写密集型的高吞吐场景
2.5 HBase数据模型的优缺点
三、架构设计
介绍HBase的整体架构设计、主要模块设计
3.1 HBase架构设计
主要组件包括:
- HMaster:元数据管理,集群调度、保活。
- RegionServer: 1E1-中K数据。负责若干个互不重叠的rowkey 区间内的数据。
- ThriftServer:提供Thrift API读写的代理层。
依赖组件包括:
- Zookeeper:分布式一致性共识协作管理,例如HMaster 选主、任务分发、元数据变更管理等
- HDFS:分布式文件系统,HBase数据存储底座
3.2 Hmaster
3.2.1 主要职责
- 管理RegionServer实例生命周期,保证服务可用性
- 协调RegionServer数据故障恢复,保证数据正确性
- 集中管理集群元数据,执行负载均衡等维护集群稳定性
- 定期巡检元数据,调整数据分布,清理废弃数据等
- 处理用户主动发起的元数据操作如建表、删表等
3.2.2 主要组件
- ActiveMasterManager:管理HMaster 的active/backup (活动/备份) 状态
- ServerManager:管理集群内RegionServer的状态
- AssignmentManager:管理数据分片(region)的状态
- SplitWalManager:负责故障数据恢复的WAL 拆分工作
- LoadBalancer:定期《检、调整集群负载状态
- RegionNormalizer(区域规范器):定期巡检并拆分热点、整合碎片
- CatalogJanitor:定期巡检、清理元数据
- Cleaners:定期清理废弃的 HFile / WAL等文件
- MasterFileSystem:封装访问HDFS的客户端 SDK
3.3 RegionServer
3.3.1 主要职责
- 提供部分rowkey/行键 区间数据的读写服务
- 如果负责meta表,向客户端 SDK提供rowkey位置信息
- 认领 HMaster 发布的故障恢复任务,帮助加速数据恢复过程
- 处理HMaster下达的元数据操作,如region打开/关闭/分裂/合并操作等
3.3.2 主要组件
- MemStore:基于SkipList数据结构实现的内存态存储,定期批量写入硬盘
- Write-Ahead-Log/预写日志 :顺序记录写请求到持久化存储,用于故障恢复内存中丢失的数据
- Store/商店 : 对应一个 Column Family/列族 在一个 region/区域 下的数据集合,通常包含多个文件
- StoreFile:即HFile,表示HBase在HDFS存储数据的文件格式,其内数据按rowkey字典序有序排列
- BlockCache: HBase以数据块为单位读取数据并缓存在内存中以加速重复数据的读取
3.4 ZooKeeper主要职责
- HMaster登记信息,对active/backup(活动/备份) 分工达成共识的
- RegionServer 登记信息,失联时 HMaster保活处理的
- 登记meta表位置信息,供 SDK查询读写位置信息
- 供 HMaster 和RegionServer协作处理分布式任务
3.5 ThriftServer主要职责
- 实现 HBase定义的Thrift APl,作为代理层向用户提供RPC读写服务
- 用户可根据IDL自行生成客户端实现
- 独立于RegionServer水平扩展,用户可访问任意ThriftServer 实例( scan/扫描 操作较特殊,需要同实例维护scan状态)
四、大数据支撑
介绍HBase针对海量数据场景所做的设计优化,包括水平扩展能力、负载均衡策略、故障恢复机制
4.1. HBase在大数据生态的定位
- 对TB、PB级海量数据支持强一致、近实时的读写性能,支持快速的ad-hoc分析查询任务;
- 支持字典序批量扫描大量数据,支持只读取部分列族的数据,灵活支持不同查询模式,避免读取不必要的数据;
- 心存储大规模任务(例如MapReduce,Spark,Flink)的中间/最终计算结果;
- 平滑快速的水平扩展能力,能够敏捷应对大数据场景高速增长的数据体量和大规模的并发访问
- 精细化的资源成本控制,计算层和存储层分别按需扩展,避免资源浪费。
4.2. 水平扩展能力
- 增加RegionServer/区域服务器 实例,分配部分region到新实例。
- 扩展过程平滑,无需搬迁实际数据。
- 可用性影响时间很短,用户基本无感知。
4.3. Region热点切分
- 当某个region数据量过多,切分成两个独立的子region分摊负载。
- RegionServer在特定时机(flush、compaction/刷新、压缩)检查region是否应该切分,计算切分点并RPC上报HMaster,由 AssignmentManager/分配管理器 负责执行 RegionStateTransition。
- 不概迁实际数据,切分产生的新 region数据目录下生成一个以原region文件信息命名的文件,内容是切分点对应的rowkey,以及标识新region是上/下半部分的数据。
4.3.1 Region 热点切分–切分点选取
HBase原生提供的多种切分策略使用相同的切分点选择策略。
- 目标:优先把最大的数据文件均匀切分。
切分点选择步骤:
- 找到该表中哪个region 的数据大小最大
- 找到该region内哪个column family的数据大小最大
- 找到 column family内哪个HFile的数据大小最大
- 找到HFile里处于最中间位置的Data Block/数据块;
- 用这个Data Block的第一条KeyValue的Rowkey 作为切分点。
4.3.2 Region热点切分–切分过程
- 所有 Column Family/列族 都按照统一的切分点来切分数据。
- 目的是优先均分最大的文件,不保证所有Column Family的所有文件都被均分。
-
HFile 1作为最大的文件被均分,其他文件也必须以相同的 rowkey/行 切分以保证对齐新region的rowkey区间。
-
切分出的新region分别负责rowkey区间 [2000,2500) 和 [2500,4000) 。
-
每个新region分别负责原region的上/下半部分rowkey 区间的数据
-
在 compaction/压缩 执行前不实际切分文件,新 region下的文件通过 reference file/参考文件 指向原文件读取实际数据。
4.3.3 Region 热点切分–流程设计
AssignmentManager/分配管理器 检查cluster、table、region(集群、表、区域)的状态后,创建SplitTableRegionProcedure通过状态机实现执行切分过程。
4.4 Region/区 碎片整合
- 当某些region数据量过小、碎片化,合并相邻region整合优化数据分布。
- AssignmentManager/指派经理 创建MergeTableRegionsProcedure执行整合操作。
- 不搬迁实际数据,通过reference file定位原region的文件,直到下次 compaction/压实 时实际处理数据。
- *注意:只允许合并相邻region,否则会打破rowkey空间连续且不重合的约定。
4.4.1 Region碎片整合–流程设计
类似于region切分,不立刻处理实际数据文件,而是通过创建reference files引用到原文件,然后原子地更新元数据来完成碎片整合,后续靠compaction整合数据文件,靠CatalogJanitor异步巡检元数据处理遗留数据。
4.5 Region负载均衡
定期巡检各RegionServer 上的region数量,保持region 的数量均匀分布在各个RegionServer 上。
SimpleLoadBalancer/简单负载均衡器 具体步骤:
- 根据总region 数量和RegionServer 数量计算平均region 数,设定弹性上下界避免不必要的操作。例如默认 slop/斜率 为0.2,平均 region/区域 数为5,负载均衡的RS 上region数量应该在[4,6]区间内。
- 将RegionServer按照region数量降序排序,对region 数量超出上限的选取要迁出的region并按创建时间从新到老排序;
4.5.1 Region 负载均衡–调度策略
SimpleLoadBalancer具体步骤:
- 选取出region数量低于下限的RegionServer列表,round-robin/循环 分配步骤2选取的regions,尽量使每个RS的region数量都不低于下限;
- 处理边界情况,无法满足所有RS 的region数量都在合理范围内时,尽量保持region数量相近。
4.5.2 Region负载均衡-其他策略
stochasticLoadBalancer/负载均衡器
- 随机尝试不同的region 放置策略,根据提供的 cost function/成本函数 计算不同策略的分值名(0为最优策略,1为最差策略);
- cost计算将下列指标纳入统计:
- region负载、表负载、数据本地性(本地访问HDFS). Memstore大小、HFile大小。
- 根据配置加权计算最终cost,选择最优方案进行负载均衡;
FavoredNodeLoadBalancer
- 用于充分利用本地读写HDFS文件来优化读写性能。
- 每个region会指定优选的3个RegionServer地址,同时会告知HDFS在这些优选节点上放置该 region的数据;
- 即使第一节点出现故障,HBase 也可以将第二节点提升为第一节点,保证稳定的读时延;
4.6 故障恢复机制- HMaster
HMaster通过多实例基于Zookeeper 选主实现高可用性。
- 所有实例尝试向Zookeeper 的/hbase/active-master l临时节点CAS地写入自身信息,
- 写入成功表示成为主实例,失败即为从实例,通过watch 监听/hbase/active-master节点的变动。
- 主实例不可用时临时节点被删除,此时触发其他从实例重新尝试选主。
4.6.1 故障恢复机制-HMaster恢复流程
一、HMaster 自身恢复流程:
- 监听到/hbase/active-master 临时节点被删除的事件,触发选主逻辑;
- 选主成功后执行HMaster启动流程,从持久化存储读取未完成的procedures从之前状态继续执行;
- 故障HMaster实例恢复后发现主节点已存在,继续监听/hbaselactive-master。
二、调度RegionServer的故障恢复流程:
- AssignmentManager/分配管理器 从 procedure列表中找出 Region-In-Transition/过渡 状态的region继续调度过程;
- RegionServerTracker/区域服务器跟踪器 从Zookeeper梳理online状态的RegionServer列表,结合ServerCrashProcedure列表、HDFS中 WAL目录里alive / splitting状态的RegionServer记录,获取掉线RegionServer的列表,分别创建 ServerCrashProcedure 执行恢复流程。
4.7 故障恢复机制– RegionServer
- 每个 RegionServer实例启动时都会往Zookeeper的/hbase/rs路径下创建对应的临时节点。
- HMaster 通过监听RegionServer在Zookeeper的临时节点状态,监控数据读写服务的可用性,及时调度恢复不可用的regions。
- RegionServer的故障恢复需要将内存中丢失的数据从WAL中恢复,HMaster利用Zookeeper配合所有RegionServer 实例,分布式地处理WAL数据,提升恢复速度。
4.7.1 故障恢复机制- RegionServer 恢复流程
启动流程:
- 启动时去Zookeeper登记自身信息,告知主 HMaster实例有新RS实例接入集群
- 接收和执行来自HMaster的region调度命令
- 打开region前先从HDFS读取该region的recovered.edits目录下的WAL记录,回放恢复数据
- 恢复完成,认领Zookeeper上发布的分布式任务(如WAL切分)帮助其他数据恢复
4.8 Distributed Log Split/分布式日志分割 原理
背景:
- 写入HBase的数据首先顺序持久化到 Write-Ahead-Log/写前日志 ,然后写入内存态的MemStore即完成,不立即写盘,RegionSever故障会导致内存中的数据丢失,需要回放WAL 来恢复;
- 同RegionServer的所有region复用WAL,因此不同region的数据交错穿插,RegionServer故障后重新分配 region前需要先按region维度拆分WAL。
4.8.1 Distributed Log Split原理-具体流程
实现原理:
-
RegionServer故障,Zookeeper检测到心跳超时或连接断开,删除对应的临时节点并通知监听该节点的客户端
-
active HMaster 监听到RS 临时节点删除事件,从HDFS梳理出该RS负责的WAL文件列表
-
HMaster为每个WAL文件发布一个 log split task/日志拆分 到ZK
-
其他在线的RS 监听到新任务,分别认领
-
将 WAL entries/条目 按region拆分,分别写入HDFS上该region的recovered.edits
-
HMaster监听到 log split/日志分割 任务完成,调度region到其他RS
-
RS打开region前在HDFS 找到先回放recovered.edits目录下的WAL文件将数据恢复到Memstore 里,再打开region恢复读写服务
4.8.2 Distributed Log Split原理–优化空间
进一步优化:Distributed Log Replay/分布式日志重放
- HMaster先将故障RegionServer上的所有region以Recovering状态调度分配到其他正常RS上;
- 再进行类似 Distributed Log Split/分布式日志分割 的WAL日志按region维度切分;
- 切分后不写入HDFS,而是直接回放,通过SDK写流程将WAL记录写到对应的新 RS;
- Recovering 状态的region接受写请求但不提供读服务,直到WAL回放数据恢复完成。
五、最佳实践
分享HBase实战经验总结与最佳实践
5.1 Rowkey设计策略
5.2 Column Family 设计策略
- Column family数量过多容易影响性能,建议尽量少,不超过5个。
- 需要同时读取的数据尽量放在相同列族,反之尽量放在不同列族,读取时尽量只读取必需的列族,避免读不必要的列族。
- 列族(以及column qualifier)名称尽量短,因为会冗余存储到每个KeyValue 中。
5.3 参数调优经验
5.4 ByteTable -字节跳动自研分布式表格存储系统
6. 大佬牛逼
实习职位: 分布式存储实习生 - 字节跳动 (bytedance.com)
社招职位: 字节跳动内推 (bytedance.com)
晚安玛卡巴卡
快乐暑假