HBase原理 | HBase RegionServer宕机数据恢复

2024-06-12 21:32

本文主要是介绍HBase原理 | HBase RegionServer宕机数据恢复,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

HBase采用类LSM的架构体系,数据写入并没有直接写入数据文件,而是会先写入缓存(Memstore),在满足一定条件下缓存数据再会异步刷新到硬盘。为了防止数据写入缓存之后不会因为RegionServer进程发生异常导致数据丢失,在写入缓存之前会首先将数据顺序写入HLog中。如果不幸一旦发生RegionServer宕机或者其他异常,这种设计可以从HLog中进行日志回放进行数据补救,保证数据不丢失。HBase故障恢复的最大看点就在于如何通过HLog回放补救丢失数据。

HLog简介

为了更好的理解HBase故障恢复原理,需要对HLog有简单的认识。HLog的整个生命历程可以使用下面一张图来表示:

1. HLog构建:详见另一篇博文《HBase-数据写入流程解析》中相关章节,此处再将HLog的结构示意图拿出来:

上图可以看出,一个HLog由RegionServer上所有Region的日志数据构成,日志数据的最小单元为<HLogKey,WALEdit>,其中HLogKey由sequenceid、writetime、clusterid、regionname以及tablename组成。其中sequenceid是日志写入时分配给数据的一个自增数字,先写入的日志数据sequenceid小,后写入的sequenceid大。

2. HLog滚动:HBase后台启动了一个线程会每隔一段时间(由参数’hbase.regionserver.logroll.period’决定,默认1小时)进行日志滚动,即新生成一个新的日志文件。可见,HLog日志文件并不是一个大文件,而是会产生很多小文件。有些同学就会问了,为什么需要产生多个日志文件,一个日志文件不好吗?这是因为随着数据的不断写入,HLog所占空间将会变的越来越大,然而很多日志数据其实已经没有任何作用了,这部分数据完全可以被删掉。而删除数据最好是一个文件一个文件整体删除,因此设计了日志滚动机制,方便文件整体删除。类似于Binlog的处理机制。

3. HLog失效:上文提到,很多日志数据在之后会因为失效进而可以被删除,并且删除操作是以文件为单元执行的。那怎么判断一个日志文件里面的数据失效了呢?首先从原理上讲一旦数据从Memstore中落盘,对应的日志就可以被删除,因此一个文件所有数据失效,只需要看该文件中最大sequenceid对应的数据是否已经落盘就可以,HBase会在每次执行flush的时候纪录对应的最大的sequenceid,如果前者小于后者,则可以认为该日志文件失效。一旦判断失效就会将该文件从WALs文件夹移动到OldWALs文件夹。

4. HLog删除:HMaster后台会启动一个线程每隔一段时间(由参数’hbase.master.cleaner.interval’,默认1分钟)会检查一次文件夹OldWALs下的所有失效日志文件,确认是否可以被删除,确认之后执行删除操作。又有同学问了,刚才不是已经确认可以被删除了吗?这里基于两点考虑,第一对于使用HLog进行主从复制的业务来说,第三步的确认并不完整,需要继续确认是否该HLog还在应用于主从复制;第二对于没有执行主从复制的业务来讲,HBase依然提供了一个过期的TTL(由参数’hbase.master.logcleaner.ttl’决定,默认10分钟),也就是说OldWALs里面的文件最多依然再保存10分钟。

HBase故障恢复三部曲

HBase的故障恢复我们都以RegionServer宕机恢复为例,引起RegionServer宕机的原因各种各样,有因为Full GC导致、网络异常导致、官方Bug导致(close wait端口未关闭)以及DataNode异常导致等等。

这些场景下一旦RegionServer发生宕机,HBase都会马上检测到这种宕机,并且在检测到宕机之后会将宕机RegionServer上的所有Region重新分配到集群中其他正常RegionServer上去,再根据HLog进行丢失数据恢复,恢复完成之后就可以对外提供服务,整个过程都是自动完成的,并不需要人工介入。基本原理如下图所示:

HBase检测宕机是通过Zookeeper实现的, 正常情况下RegionServer会周期性向Zookeeper发送心跳,一旦发生宕机,心跳就会停止,超过一定时间(SessionTimeout)Zookeeper就会认为RegionServer宕机离线,并将该消息通知给Master。上述步骤中比较特殊的是HLog切分,其他步骤相信都能够理解,为什么需要切分HLog?大家都知道当前(0.98)版本中一台RegionServer只有一个HLog文件,即所有Region的日志都是混合写入该HLog的,然而,回放日志是以Region为单元进行的,一个Region一个Region回放,因此在回放之前首先需要将HLog按照Region进行分组,每个Region的日志数据放在一起,方便后面按照Region进行回放。这个分组的过程就称为HLog切分。

根据实现方式的不同,HBase的故障恢复前后经历了三种不同模式,如下图所示,下面会针对每一种模式进行详细介绍:

LogSplitting

HBase的最初阶段是使用如下流程进行日志切分的,整个过程都由HMaster控制执行。如下图所示:

1. 将待切分日志文件夹重命名,为什么需要将文件夹重命名呢?这是因为在某些场景下RegionServer并没有真正宕机,但是HMaster会认为其已经宕机并进行故障恢复,比如最常见的RegionServer发生长时间Full GC,这种场景下用户并不知道RegionServer宕机,所有的写入更新操作还会继续发送到该RegionServer,而且由于该RegionServer自身还继续工作所以会接收用户的请求,此时如果不重命名日志文件夹,就会发生HMaster已经在使用HLog进行故障恢复了,但是RegionServer还在不断写入HLog

2. 启动一个读线程依次顺序读出每个HLog中所有<HLogKey,WALEdit>数据对,根据HLogKey所属的Region不同写入不同的内存buffer中,如上图Buffer-Region1内存存放Region1对应的所有日志数据,这样整个HLog所有数据会被完整group到不同的buffer中

3. 每个buffer会对应启动一个写线程,负责将buffer中的数据写入hdfs中(对应的路径为/hbase/table_name/region/recoverd.edits/.tmp),再等Region重新分配到其他RegionServer之后按顺序回放对应Region的日志数据。

这种日志切分可以完成最基本的任务,但是效率极差,在某些场景下(集群整体宕机)进行恢复可能需要N个小时!也因为恢复效率太差,所以开发了Distributed Log Splitting架构。

Distributed Log Splitting

Distributed Log Splitting是Log Splitting的分布式实现,它借助Master和所有RegionServer的计算能力进行日志切分,其中Master作为协调者,RegionServer作为实际的工作者。基本工作原理如下图所示:

1. Master会将待切分日志路径发布到Zookeeper节点上(/hbase/splitWAL),每个日志作为一个任务,每个任务都会有对应状态,起始状态为TASK_UNASSIGNED

2. 所有RegionServer启动之后都注册在这个节点上等待新任务,一旦Master发布任务之后,RegionServer就会抢占该任务

3. 抢占任务实际上首先去查看任务状态,如果是TASK_UNASSIGNED状态,说明当前没有人占有,此时就去修改该节点状态为TASK_OWNED。如果修改失败,说明其他RegionServer也在抢占,修改成功表明任务抢占成功。

4. RegionServer抢占任务成功之后会分发给相应线程处理,如果处理成功,会将该任务对应zk节点状态修改为TASK_DONE,一旦失败会修改为TASK_ERR

5. Master会一直监听在该ZK节点上,一旦发生状态修改就会得到通知。任务状态变更为TASK_ERR的话,Master会重新发布该任务,而变更为TASK_DONE的话,Master会将对应的节点删除

下图是RegionServer抢占任务以及抢占任务之后的工作流程:

1. 假设Master当前发布了4个任务,即当前需要回放4个日志文件,分别为hlog1、hlog2、hlog3和hlog4

2. RegionServer1抢占到了hlog1和hlog2日志,RegionServer2抢占到了hlog3日志,RegionServer3抢占到了hlog4日志

3. 以RegionServer1为例,其抢占到hlog1和hlog2日志之后会分别分发给两个HLogSplitter线程进行处理,HLogSplitter负责对日志文件执行具体的切分,切分思路还是首先读出日志中每一个<HLogKey, WALEdit>数据对,根据HLogKey所属Region写入不同的Region Buffer

4. 每个Region Buffer都会有一个对应的写线程,将buffer中的日志数据写入hdfs中,写入路径为/hbase/table/region2/seqenceid.temp,其中seqenceid是一个日志中某个region对应的最大sequenceid

5. 针对某一region回放日志只需要将该region对应的所有文件按照sequenceid由小到大依次进行回放即可

这种Distributed Log Splitting方式可以很大程度上加快整个故障恢复的进程,正常故障恢复时间可以降低到分钟级别。然而,这种方式会产生很多日志小文件,产生的文件数将会是M * N,其中M是待切分的总hlog数量,N是一个宕机RegionServer上的Region个数。假如一个RegionServer上有200个Region,并且有90个hlog日志,一旦该RegionServer宕机,那这种方式的恢复过程将会创建 90 * 200 = 18000个小文件。这还只是一个RegionServer宕机的情况,如果是整个集群宕机小文件将会更多!!!

Distributed Log Replay

该方案在基本流程上做了一些改动,如下图所示:

相比Distributed Log Splitting方案,流程上的改动主要有两点:先重新分配Region,再切分回放HLog;Region重新分配打开之后状态设置为recovering,核心在于recovering状态的Region可以对外提供写服务,不能提供读服务,而且不能执行split、merge等操作。

DLR的HLog切分回放基本框架类似于Distributed Log Splitting,但它在分解完HLog为Region-Buffer之后并没有去写入小文件,而是直接去执行回放。这样设计可以大大减少小文件的读写IO消耗,解决DLS的切身痛点。

可见,在写可用率以及恢复性能上,DLR要远远优于DLS方案,官方也给出了一个简单的测试报告:

可见,DLR在写可用恢复是最快的,读可用恢复稍微弱一点,但都比DLS好很多

了解了DLR的解决方案后,再回头看DLS的实现方案,就不禁要问上一句:为什么DLS需要将Buffer中的数据写入小文件中?个人认为原因最有可能是写入小文件之后,同一个Region的不同日志数据可以按照文件名中sequenceid由小到大进行顺序回放,完全可以模拟当时数据写入的流程,不会有丝毫偏差。而如果不写小文件,很难在分布式环境下对sequenceid进行排序,这里就有一个问题,不按顺序对HLog进行回放会不会出现问题?这个问题可以分为下面两个层面进行讨论:

1. 不同时间更新的相同rowkey,不按顺序回放会不会有问题?比如WAL1中有<rowkey, t1>,WAL2中有<rowkey,t2>,正常情况下应该先回放<rowkey,t1>对应的日志数据,再回放<rowkey,t2>对应的日志数据,如果顺序颠倒会不会有问题?

第一眼看到这样的问题觉得一定不行啊,正常情况下<rowkey,t2>对应的日志数据才是最后的真正数据,一旦颠倒之后不就变成<rowkey,t1>对应的日志数据了。

这里需要关注更新时间的概念,rowkey回放时,如果写入时间戳定义为回放时间的话,肯定会有异常的。但是如果回放日志数据的时候rowkey写入时间戳被定义为当时rowkey数据写入日志的时间的话,就正常了。按照上面例子,顺序即使颠倒,先写<rowkey,t2>再写<rowkey,t1>,但是写入数据的时间戳(版本)依然保持不变时t2和t1的话,最大版本数据还是<rowkey,t2>,用户读取最新数据依然是<rowkey,t2>,和回放顺序并没有关系。

2. 相同时间戳更新的相同rowkey,不按顺序回放会不会有问题?比如WAL1中有<rowkey, t0>,WAL2中有<rowkey,t0>,正常情况下也应该先回放前者,再回放后者,如果顺序颠倒会不会有问题?

为什么同一时间会有多条相同rowkey的写入更新,而且还在不同的日志文件中?大家肯定会有这样的疑问。问题中‘同一时间’的单位是ms,在很多写入吞吐量很大的场景下同一毫秒写入大量数据并不是不可能,那先后写入两条相同rowkey的数据也必然可能,至于为什么在不同文件,假如刚好第一次更新完rowkey的时候日志截断了,第二次更新就会落入下一个日志。

那这种情况下前后两次更新时间戳还一致,颠倒顺序就办法分出哪个版本大了呀!莫慌,不是还有sequenceid~,只要在回放的时候将日志数据原生sequenceid也一同写入,不就可以在时间戳相同的情况下根据sequenceid进行判断了。具体实现只需要写入一个replay标示(用来表示该数据时回放写入)和相应的sequenceid,用户在读取的时候如果遇到两个都带有replay标示而且rowkey、cf、column、时间戳都相同的情况,还需要比较sequenceid就可以分辨出来哪个数据版本更大。

具体可以参考这个官方jira:https://issues.apache.org/jira/browse/HBASE-8701

在0.95版本DLR功能已经基本实现,一度在0.99版本已经设为默认,但是因为还是有一些功能性缺陷(主要是在rolling upgrades的场景下可能会导致数据丢失),又在1.1版本取消了默认设置。用户可以通过设置参数hbase.master.distributed.log.replay = true 来开启DLR功能,当然前提是将HFile格式设置为V3(v3格式HFile引入了tag功能,replay标示就是用tag进行实现的)

总结

本文主要介绍了HLog相关知识,同时基于此对HBase中RegionServer宕机之后整个恢复流程以及原理进行了深入分析,重点分析了DLS方案以及DLR方案,希望和大家一起学习HBase故障恢复模块知识。文中如有错误,可以一起讨论学习。

这篇关于HBase原理 | HBase RegionServer宕机数据恢复的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1055385

相关文章

数据库原理与安全复习笔记(未完待续)

1 概念 产生与发展:人工管理阶段 → \to → 文件系统阶段 → \to → 数据库系统阶段。 数据库系统特点:数据的管理者(DBMS);数据结构化;数据共享性高,冗余度低,易于扩充;数据独立性高。DBMS 对数据的控制功能:数据的安全性保护;数据的完整性检查;并发控制;数据库恢复。 数据库技术研究领域:数据库管理系统软件的研发;数据库设计;数据库理论。数据模型要素 数据结构:描述数据库

计算机组成原理——RECORD

第一章 概论 1.固件  将部分操作系统固化——即把软件永恒存于只读存储器中。 2.多级层次结构的计算机系统 3.冯*诺依曼计算机的特点 4.现代计算机的组成:CPU、I/O设备、主存储器(MM) 5.细化的计算机组成框图 6.指令操作的三个阶段:取指、分析、执行 第二章 计算机的发展 1.第一台由电子管组成的电子数字积分和计算机(ENIAC) 第三章 系统总线

GaussDB关键技术原理:高性能(二)

GaussDB关键技术原理:高性能(一)从数据库性能优化系统概述对GaussDB的高性能技术进行了解读,本篇将从查询处理综述方面继续分享GaussDB的高性能技术的精彩内容。 2 查询处理综述 内容概要:本章节介绍查询端到端处理的执行流程,首先让读者对查询在数据库内部如何执行有一个初步的认识,充分理解查询处理各阶段主要瓶颈点以及对应的解决方案,本章以GaussDB为例讲解查询执行的几个主要阶段

【计算机组成原理】部分题目汇总

计算机组成原理 部分题目汇总 一. 简答题 RISC和CICS 简要说明,比较异同 RISC(精简指令集)注重简单快速的指令执行,使用少量通用寄存器,固定长度指令,优化硬件性能,依赖软件(如编译器)来提升效率。 CISC(复杂指令集)包含多样复杂的指令,能一条指令完成多步操作,采用变长指令,减少指令数但可能增加执行时间,倾向于硬件直接支持复杂功能减轻软件负担。 两者均追求高性能,但RISC

MySQL数据库锁的实现原理

MySQL数据库的锁实现原理主要涉及到如何确保在多用户并发访问数据库时,保证数据的完整性和一致性。以下是MySQL数据库锁实现原理的详细解释: 锁的基本概念和目的 锁的概念:在数据库中,锁是用于管理对公共资源的并发控制的机制。当多个用户或事务试图同时访问或修改同一数据时,数据库系统通过加锁来确保数据的一致性和完整性。 锁的目的:解决多用户环境下保证数据库完整性和一致性的问题。在并发的情况下,会

线性回归(Linear Regression)原理详解及Python代码示例

一、线性回归原理详解         线性回归是一种基本的统计方法,用于预测因变量(目标变量)与一个或多个自变量(特征变量)之间的线性关系。线性回归模型通过拟合一条直线(在多变量情况下是一条超平面)来最小化预测值与真实值之间的误差。 1. 线性回归模型         对于单变量线性回归,模型的表达式为:         其中: y是目标变量。x是特征变量。β0是截距项(偏置)。β1

标准分幅下的图幅号转换成经纬度坐标【原理+源代码】

最近要批量的把标准分幅下的图幅号转换成经纬度坐标,所以这两天写了个程序来搞定这件事情。 先举个例子说明一下这个程序的作用。 例如:计算出图幅号I50G021040的经纬度范围,即最大经度、最小经度、最大纬度、最小纬度。 运用我编写的这个程序,可以直接算出来,这个图幅号的经纬度范围,最大经度为115.3125°,最小经度为115.25°,最大纬度为31.167°,最小纬度为31.125°。

SpingBoot原理

配置优先级 SpringBoot配置的优先级从高到低依次为命令行参数、JNDI属性、Java系统属性、操作系统环境变量、外部配置文件、内部配置文件、注解指定的配置文件和编码中直接指定的默认属性。具体如下: 命令行参数:启动应用时,通过命令行指定的参数拥有最高优先级。例如,使用--server.port=8081会直接改变应用程序的端口,无论在什么配置文件中定义过该值。JNDI属性:这些属性由当

HashMap 的工作原理及其在 Java 中的应用?

在Java的数据结构中,HashMap是最常见且最重要的一个数据结构之一。HashMap是Java集合框架中的一部分,它存储的是键值对(Key-value)映射,也就是说,你可以通过键(Key)找到对应的值(Value)。让我们来详细地看一下HashMap的工作原理。 HashMap的工作原理 HashMap内部有一个数组,数组中的每个元素又是一个链表。当我们将一个键值对存入HashM