当前复制系统的问题

本文档介绍了 Hive 复制的第二个版本。有关先前实施的详细信息,请参考Hive 复制的第一个版本

这项工作正在开发中,接口可能会发生变化。它被设计为与外部编排工具结合使用,该工具将负责协调源集群和目标集群之间的正确命令 Sequences,容错/故障处理,并提供必要的正确配置选项。进行跨集群复制。

从 Hive 3.0.0 版本开始:仅支持 Hive 用户拥有表内容的托管表复制.不支持外部表,ACID 表,统计信息和约束复制.

当前的复制实现出现的一些问题如下:

  • Slowness

  • 要求具有完整副本的暂存目录(4xcopy 问题)

  • 不适合负载均衡用例

  • 与 ACID 不兼容

  • 依赖于外部工具来完成很多工作(登台目录 mgmt,Management 状态信息等)

因此,我们将首先尝试了解每种情况的发生原因以及我们可以采取的措施。

Slowness

为什么 Hive Replication 的第一个版本运行缓慢?

其速度慢的主要原因是它取决于状态转移,而不是增量重放。这意味着被传送到目标的数据量比原本要大得多。对于频繁的更新/插入,这尤其是一个问题。 (由于原始状态为空,并且删除是瞬时的,因此创建无法进行增量优化)。

第二个原因是,最初的实现旨在确保弹性方面的“正确性”。我们正在计划进行优化,以大幅度减少已处理事件的数量,但尚未实施。优化可以通过一次处理一个事件窗口并在将来的事件使处理第一个事件的效果无效时跳过某些事件的处理来实现(例如在插入后跟插入,或在后面跟上 drop 的情况)创建等)。因此,我们当前的实现可以看作是窗口大小为 1 的幼稚实现。

要求具有完整副本的登台目录(4xcopy 问题)

同样,此问题归结为需要进行状态转移,并使用导出和导入来执行此操作。第一个副本是源表,然后将其导出到暂存目录。这是第二份。必须将其 dist-cp-ed 到目标群集,然后再形成第三副本。然后,在导入时,它将数据压印到目标表上,成为第四份副本。

现在,从复制的术语上来说,其中两个副本即源表和目标表是必需的-需要两个副本。主要问题是临时目录中临时需要两个附加副本。对于没有太多临时溢出空间的群集,这成为主要限制。

让我们检查每个副本以及每个副本背后的设计推理。首先,在源上,完成导出的原因是为了提供一个稳定点,当我们尝试将其分发到目标时,该表将具 Elastic,以防万一在尝试复制表时应修改表。如果我们不进行复制,那么源端工作的关键部分将是更长的部分,并且任何重放/恢复语义都将变得更加复杂。

在目标端,优化当然是可能的,并且实际上已经完成,因此我们在这里没有多余的副本。在导入时,导入实际上会将文件移动到目标表,因此,我们的 4xcopy 问题实际上是 3x 复制问题。但是,我们将其称为 4xcopy 问题,因为这是我们遇到并解决的第一个问题。

但是,此优化的意思是,如果我们在移动后的导入过程中失败,则该导入的重做实际上也将需要导出端的重做,因此不可轻易重试。与我们所拥有的其他故障点相比,这是一个有意识的决定,因为发生这种情况的可能性很小。如果我们也希望使导入具有可恢复性,那么我们将遇到 4 倍复制问题。

不适合负载均衡用例

通过尽早强制执行“导出”,我们可以处理灾难恢复用例,因此,即使应该破坏源配置单元,我们也不会受到太大影响,并且可以在目标位置重播导出的命令并进行恢复。但是,这样做时,我们将每个表和分区都视为独立的对象,对此,唯一重要的考虑因素是我们保存每个表和分区的最新状态,而不考虑它们如何到达那里。

因此,任何跨表关系都可能暂时丢失(一个表中可能存在一个对象,该对象引用了另一个表中尚未更新的内容),并且跨表运行的查询可能会在不存在的目标上产生结果在源中。所有这一切使得复制的当前实现完全不适合负载平衡。

本质上,主要的问题是状态转移方法意味着每个对象都被认为是独立的,并且每个对象都可以“橡皮筋”到自身的最新版本。如果所有事件均已处理,我们将处于与来源相同的稳定状态,因此,这对于在仓库中具有明显“装载时间”且与“阅读”分开的用户而言,可以实现负载平衡时间”,这使我们有足够的时间来赶上并处理所有事件。在表级别上也是如此。这可以适用于许多传统的数据仓库用例,但不能满足许多类似分析的期望。

由于这是我们试图在 Replv2 中解决的主要问题,我们将在以后的单独部分中进一步研究该橡皮筋问题。

与 ACID 不兼容

就我个人而言,我认为复制的概念应该在健壮的事务系统的基础上开发(也很容易开发),并且尝试在没有此类概念的情况下 Management 复制会导致类似我们在 Hive 中所面临的问题,我们必须利用自己所拥有的来尽力而为。 Hive 在使用上的灵 Active 是它的主要可用性点之一,而必须解决大多数安装基础的需求是导致我们尝试在不采用 ACID 的情况下开发复制的原因。

而且,尽管如此,由于压缩的性质,我们当前简单地复制数据并重新表达元数据的方法在不需要复制事务对象,转换它们以及在目的地上重新压缩的情况下也行不通。在没有分布式事务的情况下也能很好地工作。

因此,具有讽刺意味的是,在我们的复制实现中,我们不支持 ACID 表。我们已经考虑了复制 ACID 表所需要做的事情,并且在大多数讨论中,一个流行的想法似乎是使用流技术将增量发送到目标,而不是复制文件并尝试在周围进行摸索与 Transaction 元数据。但是,这将需要更多的工作,因此,我们也不打算在 replv2 中解决该问题。这很可能是我们进行复制的下一批工作的主要推动/重点。

依赖外部工具完成大量工作

当前的实现假定我们扩展了 EXPORT 和 IMPORT 的工作方式,并允许基于 Notification 和 ReplicationTask/Command 的 api,外部工具可以使用该 api 在我们之上实现复制。但是,这意味着它们必须 Management 登台目录,并且还必须 Management 每个目标表/数据库处于什么状态的概念,并且随着时间的流逝,可能会有大量的配置单元逻辑渗入他们。 Apache Falcon 有一个称为 HiveDR 的工具,该工具已实现了这些接口,并且他们表示希望 Hive 承担更多 Management 方面的任务,以使接口更干净。

为此,replv2 的目标之一是我们 Management 自己的登台目录,而不是使用复制工具来移动数据,而是更主动地介入,将数据从源拉到目的地。

支持中心轮辐模型

我们获得的另一反馈是希望支持中心辐射模型进行复制。尽管当前的复制设计中没有任何东西可以阻止部署中心辐射模型,但是基于 Hive Replication 的第三方工具的当前实现并未明确支持 1:n 复制,因为它们最终需要做记账太多了。现在,我们承担了对 Hive 进行复制的更多责任,我们不应该遇到使集线器分支复制更加困难的设计伪像的情况。

这种考虑影响我们的主要方式之一是,我们转向了拉模型,在此模型中,目的地从源中拉出了必要的信息,而不是推模型。我们可能选择重新审视此方面,但是与此同时,在设计系统的其余部分时,请牢记这一点。

Rubberbanding

请考虑以下一系列操作:

CREATE TABLE blah (a int) PARTITIONED BY (p string); INSERT INTO TABLE blah [PARTITION (p="a") VALUES 5; INSERT INTO TABLE blah [PARTITION (p="b") VALUES 10; INSERT INTO TABLE blah [PARTITION (p="a") VALUES 15;

现在,对于发生的每个操作,DbNotificationListener 会提供单调递增的状态 ID,以便我们能够按事件发生的时间对其进行排序。为了简单起见,假设它们分别在状态 10、20、30、40 中依次发生。

现在,如果还有另一个线程正在运行“ SELECT * from blah;”。来自另一个线程,然后根据 SELECT 命令的运行时间,其结果将有所不同:

  • 如果它在 10 之前运行,则该表尚不存在,并且将返回错误。

  • 如果它在 10 到 20 之间运行,则该表存在,但没有内容,因此它将返回一个空集。

  • 如果它介于 20 到 30 之间,则它将返回\ { (a,5) }

  • 如果它介于 30 到 40 之间,则它将返回\ { (a,5) , (b,10) },即 2 行。

  • 如果在 40 之后运行,它将返回\ { (a,5) , (b,10) , (a,15) }。

现在,当前复制的状态转移方法出现的问题是,如果我们将这些事件序列从源复制到目标仓库,则很可能第一次 EXPORT 在表上运行会在一段时间后该事件已经发生,例如在事件 500 附近。在此时间点,如果它尝试导出分区的状态(p =“ a”),则它将捕获到该点为止发生的所有更改。

让我们将处理事件 E 的事件表示为 PROC(E),以将 E 从源复制到目标。因此,PROC(10)表示事件 10 从源到目的地的处理。

现在,让我们看一下在源上观察到的与目标上观察到的相同的 select *行为。

  • 如果 select *在 PROC(10)之前运行,则由于尚未创建表,因此会出现错误。

  • 如果 select *在 PROC(10)和 PROC(20)之间运行,则将导致分区 p =“ a”)被压入。

  • 如果 PROC(20)在 40 发生之前发生,则它将返回\ { (a,5) }

    • 如果 PROC(20)在 40 发生之后发生,则它将返回\ { (a,5) , (a,15) }-这是因为 PROC(20)捕获的分区状态将在 40 以后发生,因此包含(a,15),但分区 p =“ b ”,因为我们还没有对分区进行重新印象,所以它尚未被重新打动,只有在 PROC(30)上才会出现。

我们现在停止检查,因为我们在目的地选择*看到了一个可能的结果,而这在源码上是不可能的。这是状态转移引入的问题,我们称之为橡皮筋-命名规则来自在线游戏,这种游戏处理具有不同潜伏期的每个玩家,并且服务器必须以交错/口吃的方式协调更新。

An example:

//www.youtube.com/embed/x3BDKyPPaCw?wmode=opaque

状态转移有一些好处,例如具 Elastic 和幂等性,但是它引入了临时状态的问题,这种临时状态可能是源中不存在的,这对于负载均衡的使用来说是一个很大的禁忌-在这种情况下,目标数据库不仅是冷备份,而且是正在主动用于读取的数据库。

Change Management

现在让我们考虑复制工作流的基本部分。它需要包含以下部分:

  • 源上发生了导致更改的事件(例如,t1)

  • 为此生成一个通知事件(例如,t2)

  • 然后在源上处理该通知事件,以“准备”在目标上执行的可操作任务以复制该事件。 (例如,t3)

  • 必要的数据从源复制到目标仓库

  • 然后,目的地将执行重述所需的所有任务

现在,到目前为止,我们的主要问题似乎是我们只能捕获“最新”状态,而不能捕获事件发生时的原始状态。也就是说,在处理通知时,我们获取的是当时对象 t3 的状态,而不是对象在时间 t1 的状态。在 t1 到 t3 之间的时间中,对象可能发生了很大的变化,如果我们 continue 进行并在 t3 处获取状态,然后以幂等方式应用于目标,始终仅进行更新,则可以得到当前的实现,并且橡皮筋问题。

从根本上讲,这是我们问题的核心。为了没有橡皮筋,在 t1 和 t3 之间的那个时间段内必须满足以下条件之一:

  • 不必对对象进行任何其他更改-这意味着我们等效于将有问题的对象从 t1 锁定到 t3.如果 t1 和 t3 在相同的事务间隔中发生,则这种方法是可能的。

  • 如果不可避免要在 t1 和 t3 之间更改对象,则必须有一种记录每个状态变化的方法,以便在 t3 滚动时,我们仍然可以在某个地方使用原始 t1 状态。

Route(1)是我们应该如何访问 ACID 表的方式,并且应该是希望将来在某些时候访问所有配置单元表的方式。事务处理 Route 的好处是,我们可以准确地得到要应用的增量/更改,并且可以保存该增量以传递到另一端。

但是,与此同时,我们也必须尝试解决(2)。为此,我们使用 replv2 的目标是确保如果存在任何对对象进行任何更改的配置单元访问,我们将捕获原始状态。原始状态有两个方面-元数据和数据。元数据可以轻松解决,因为 t1 和 t2 可以在单个配置单元操作的上下文中完成,并且我们可以在同一 metastore 事务中给通知的元数据和对元数据的更改留下深刻的印象。现在,剩下的问题是对象的备份文件系统数据会发生什么。

现在,除了解决我们要跟踪转储时文件系统状态的问题之外,还有一个我们要解决的问题,那就是 4x 复制问题。我们已经使用目标上的额外副本解决了该问题。现在,我们需要以某种方式阻止源上的多余副本以使此工作正常进行。本质上,为了防止在源上复制整个数据,我们需要一种“稳定”的方法来确定事件发生时对象的 FS 支持状态。

如果我们在事件发生时拥有源文件系统的快照,那么这两个问题(4x 复制问题)以及确保我们知道在 t1 处存在什么 FS 状态以防止橡皮筋的问题都可以解决。首先,对我们而言,这导致我们将 HDFS 快照视为解决此问题的方法。不幸的是,根据与 HDFS 人士的讨论,HDFS 快照虽然可以解决我们的问题,但是我们无法创建大量快照,因此很可能需要为每个随之发生的事件创建快照。

但是,快照背后的想法仍然是我们 true 想要的,如果 HDFS 无法支持我们要创建的快照数量,则我们可以进行伪快照,以便对所有支持配置单元对象的文件进行快照。 ,如果我们检测到任何配置单元操作会移动它们或对其进行修改,则会将原始单元保留在单独的目录中,类似于我们 Management 垃圾箱的方式。这种类似虚 Pseudo 捕获行为就是我们所谓的“变更 Management”部分,是解决橡皮筋问题和 4x 复制问题所需的主要部分。

_files

当前,当我们执行表的 EXPORT 时,在此转储中创建的目录结构在其根目录中具有_metadata 文件,该文件包含所有要压印的元数据状态,然后具有要压印的每个分区的目录结构。

为了填充每个分区目录,它运行一个 CopyTask,该任务复制每个分区的文件。现在,要确保我们不进行二级复制,我们的设计非常简单-我们使用 ReplCopyTask 代替 CopyTask,它使用 ReplCopyTask 代替将文件复制到目标目录,而是创建一个名为_files 的文件在目标目录中,其中包含原始文件的每个文件名的列表。

因此,我们将使用带有_files 文件的分区目录来代替包含实际数据的分区目录,该文件随后包含原始文件的位置。 (我们将讨论并处理原始文件在以后被移走或删除时会发生什么情况,现在,就可以假设这些 url 将是到我们转储时文件状态的稳定 url,就好像这是一个伪快照.)

现在,当导入导出转储时,我们需要确保对于每个加载的_files 文件,我们都要检查_files 的内容,然后将副本应用于基础文件。另外,当我们尝试从远程群集复制文件时,我们将自动从配置单元中调用 DistCp。 (同样,可以对其进行优化,稍后将对其进行详细讨论,但就目前而言,我们能够访问它就足够了.)

使用 EXPORT 的概念创建_files 作为对实际文件的间接寻址,并通过 IMPORT 加载_files 来定位需要复制的实际文件,我们解决了 4x 复制问题。

橡皮筋解决方案

这是前面描述的橡皮筋问题的可能解决方案:
对于为其生成通知的每个元存储事件,存储元数据对象(例如表,分区等),文件的位置(与事件相关联)以及每个受影响文件的校验和(解释了存储校验和的原因)不久)。如果发生事件会删除文件(例如,删除表/分区),请将删除的文件移动到文件系统上的可配置位置(在此讨论中,我们将其命名为$ cmroot),而不是删除它们。

考虑以下命令序列以进行说明:

Event 100: ALTER TABLE tbl ADD PARTITION (p=1) SET LOCATION <location>; Event 110: ALTER TABLE tbl DROP PARTITION (p=1); Event 120: ALTER TABLE tbl ADD PARTITION (p=1) SET LOCATION <location>;

在目标端加载转储时(稍后),当事件 100 被重播时,目标上的加载任务将尝试从\ (_files 包含 的路径)中提取文件,其中可能包含新数据或不同数据。为了复制事件 100 在源处发生时的源的确切状态,我们执行以下操作:

  • 当事件 100 在源处发生时,在通知事件中,我们将文件的校验和与文件路径一起存储在新添加的分区中。

  • 当事件 110 在源处发生时,我们将删除的分区的文件移动到$ cmroot/database/tbl/p = 1 而不是清除它们。

  • 当事件 120 发生在源码时,在通知事件中,我们再次将文件的校验和与文件路径一起存储在新添加的分区中。

现在,当事件 100 在稍后的位置重播时,目的地将计算分区路径中文件的校验和。如果校验和不同(例如,在这种情况下,由于源中发生了事件 110 和事件 120),则目标将在$ cmroot/database/table/p = 1 中查找这些文件,并将它们从该位置拉至在源上发生事件 100 时,复制源的状态。

_metadata

当前,当我们导出表或分区时,我们生成一个_metadata 文件,该文件包含有关对象的元数据状态的快照。 _metadata 在完成导出时生成。为了解决橡皮筋,我们现在还需要能够在事件发生时捕获所讨论对象的元数据状态。因此,DbNotificationListener 得到了增强,它还可以存储对象本身的快照,而不只是对象的名称,并且在事件导出时,它不从元存储中而是从事件数据中获取元数据。

然后,这使我们能够在目标需要更新到该状态时(而不是更早)在目标上生成适当的对象。这与我们引入的文件伪快照结合在一起,使我们可以在目标位置上重播元数据和数据的状态。

需要引导

我们得到的要求之一是,通过卸载过多的复制需求,我们将太多的“配置单元知识”推到了与我们集成的工具上,要求他们从本质上将目标仓库引导到可以支持的位置接收增量更新。当前,我们建议用户在所有涉及的表上运行手册“ EXPORT ... FOR REPLICATION”,设置所需的任何数据库,并根据需要导入这些转储,等等,以准备复制目标。我们需要引入一种机制,通过该机制,我们可以构建比表(例如,数据库级别)更大的复制转储。为此,最合适的方法似乎是新的工具或命令,类似于 mysqldump。

(注意,在本节中,我经常引用 mysql 和 mysqldump,不是因为这是唯一的解决方案,而是因为我对此有点熟悉.其他数据库具有等效的工具)

但是,在对 mysqldump 之类的期望与我们实现的命令之间存在几个主要区别:

  • 与典型的 mysql 数据库相比,初始转储中涉及的数据规模是配置单元仓库的订单量更大。

  • 事务隔离和基于日志的方法意味着 mysqldump 可以具有整个 db/metadata 的稳定快照,在此期间,它会 continue 转储所有数据库和表。因此,即使需要一段时间将其转储出去,也不必担心对象在转储时会发生变化。另一方面,我们需要处理。

可以通过使用我们正在开发的变更 Management 语义并使用_files 惰性方法而不是 CopyTask 来解决第一点。

第二部分涉及更多内容,并且需要在转储生成期间进行一些合并。在简短介绍了一些用于 Management 复制转储和重新加载的新命令之后,我们将简短地讨论这一点。

New Commands

当前的复制实现是基于现有命令 EXPORT 和 IMPORT 构建的。与适用事件日志的直接概念相比,这些命令在语义上更适合于导出和导入任务。在 EXPORT 上使用懒惰_files 行为的概念不是一个很好的选择,因为完成 EXPORT 的前提是,无论源上的清除策略如何,它们都必须是稳定的副本。另外,导出“事件”是比较微不足道的。导出 CREATE 事件很容易,但是导出 DROP 事件是语义上的延伸。因此,为了更好地满足我们的需求,而不必使现有的 EXPORT 和 IMPORT 方式变得更加复杂,我们引入了一种新的 REPL 命令,该命令具有三种操作模式:REPL DUMP,REPL LOAD 和 REPL STATUS。

REPL DUMP

Syntax:

REPL DUMP <repl_policy> {REPLACE <old_repl_policy>} {FROM <init-evid> {TO <end-evid>} {LIMIT <num-evids>} } {WITH ('key1'='value1', 'key2'='value2')};

Replication policy: <dbname>{{.[<comma_separated_include_tables_regex_list>]}{.[<comma_separated_exclude_tables_regex_list>]}}

可以通过以下每种命令语法的示例更好地描述这一点:

(a)REPL DUMP 销售;

REPL DUMP 销售。['.*?']

复制销售数据库以进行引导,从\ = 0(引导情况)到\ =<CURR-EVID>(批量大小为 0,即不分批)。

(b)REPL DUMP 销售。['T3','[a-z]'];

与情况(a)相似,但是设置了数据库级复制,该复制仅包括表/视图“ T3”以及任何表/视图名称,其中仅包含任何长度的字母,例如“订单”,“Store”等。

(c)REPL DUMP 销售。['.*?']。['T [0-9]','Q4'];

与情况(a)相似,但是设置了数据库级复制,该复制不包括表/视图“ Q4”以及所有前缀为“ T”和任何长度的数字后缀的表/视图名称。例如,“ T3”,“ T400”,“ t255”等。表/视图名称本质上不区分大小写,因此带有前缀“ t”的表/视图名称也将从转储中排除。

(d)REPL DUMP 销售。[];

这将设置数据库级别的复制,该复制将排除所有表/视图,但仅包含函数。

(e)将销售翻倍从 200 增至 1400;

FROM<init-evid>标签的存在使此转储不是引导程序,而是使转储查看事件日志以产生增量转储。从 200 到 1400 是不言而喻的,因为它将通过事件 ID 200 到 1400 寻找来自相关数据库的事件。

(f)替换销售自 200 起;

与上面类似,但是在命令运行时隐式假定\ 为当前事件 ID。

(g)从 200 到 1400 极限 100 的 REPL DUMP 销售;从 200 极限 100 的 REPL DUMP 销售;

与情况(d)和(e)相似,但批大小为\ = 100.如果达到 100 个事件,这将导致我们停止处理,然后返回该点。请注意,这并不意味着我们从 200 开始就停止了事件 id = 300 的处理-这意味着当我们处理了属于该复制的事件流(具有不相关事件)中的 100 个事件时,我们将停止处理事件-definition,即相关的 db 或 db.table,然后我们停止。

(h)替换销售。['[a-z]']替换 200 以上的销售;

REPL DUMP 销售。['[a-z]','Q5'] REPLACE 销售。['[a-z]']起 500;

这是在增量复制周期中动态更改复制策略/作用域的示例。

在第一种情况下,将完整的数据库复制策略“ sales”更改为仅包含表/视图名称且仅包含字母“ sales。['[az]']”(例如“ stores”,“ products”等)的复制策略。使用此转储的 REPL LOAD 将智能地删除根据新策略排除的表。例如,如果名称“ T5”的表已经存在于目标集群中,那么它将在 REPL LOAD 期间自动删除。

在第二种情况下,策略再次更改为包括表/视图“ Q5”,在这种情况下,Hive 会智能地在当前增量转储中引导表/视图“ Q5”。同样适用于表/视图重命名,其中

(i)使用('hive.repl.include.external.tables'='false','hive.repl.dump.metadata.only'='true')替换销售商品;

REPL DUMP 命令具有一个可选的 WITH 子句,用于设置尝试转储时要使用的特定于命令的配置。这些配置仅由相应的 REPL DUMP 命令使用,而不会用于在同一会话中运行的其他查询。在此示例中,我们将配置设置为排除外部表,并且还仅包括元数据并且不转储数据。

Return values:

  • 作为返回错误代码返回的错误代码(如果使用 HS2,则通过 jdbc 返回)

  • 返回结果集中的 2 列:

  • \ -它已将信息转储到的目录。

    • \ -与此转储关联的最后一个事件 ID,视情况而定,它可能是最终值,也可能是当前值。

Note:

现在,生成的转储将类似于 EXPORT 生成的转储,因为它将包含_metadata 文件,但将不包含实际的数据文件,而是使用_files 文件作为对实际文件的间接引用。 REPL DUMP 的另一方面是,它不会将目录作为转储位置的参数。相反,它在由新 HiveConf 参数hive.repl.rootdir指定的根目录中创建自己的转储目录,该参数将为转储配置根目录,并将转储目录作为其返回值的一部分返回。我们还打算引入复制 dumpdir 清理器,该清理器将定期清理它。

此调用旨在实现同步,并期望调用方 await 结果。

如果 HiveConf 参数hive.in.testfalse,则 REPL DUMP 将不使用新的转储位置,因此它将使现有的转储乱码。因此,如果hive.in.testfalse.,则在进行增量转储之前,请清除引导程序转储位置。

引导说明:FROM 子句意味着我们阅读事件日志以确定要转储的内容。对于引导,我们将不使用 FROM。

引导程序转储正在进行时,它将阻止转储数据库的任何表上的重命名表/分区操作,并引发 HiveException。引导程序转储完成后,将启用重命名操作,并将照常运行。如果在进行引导程序转储时 HiveServer2 崩溃,则即使在没有进行 REPL DUMP 的情况下还原了 HiveServer2 之后,重命名操作也将 continue 引发 HiveException。应使用以下解决方法手动修复此异常状态。

在 HiveServer 日志中查找以下一对日志消息。

Note

REPL DUMP ::设置数据库的属性:\ <db_name>,属性:\ <bootstrap.dump.state.xxxx>,值:ACTIVE

REPL DUMP ::重置数据库的属性:\ <db_name>,属性:\ <bootstrap.dump.state.xxxx>

如果未为相应的“设置”属性日志找到“重置”属性日志,则用户需要使用 ALTER DATABASE 命令手动重置值为“ IDLE”的数据库属性\ <bootstrap.dump.state.xxxx>。

REPL LOAD

REPL LOAD {<dbname>} FROM <dirname> {WITH ('key1'='value1', 'key2'='value2')};

这将导致\ 中存在的 REPL DUMP(将是完全合格的 HDFS URL)被拉出并加载。如果指定\ ,并且原始转储是数据库级转储,则允许 Hive 在导入时执行 db-rename-mapping。如果未指定 dbname,则将使用转储中记录的原始 dbname。

REPL LOAD 命令具有可选的 WITH 子句,以设置在尝试从源集群进行复制时要使用的特定于命令的配置。这些配置仅由相应的 REPL LOAD 命令使用,而不会用于在同一会话中运行的其他查询。

Return values:

  • 正常返回错误代码。

  • 在 ResultSet 中不返回任何内容,希望用户运行 REPL STATUS 进行检查。

REPL STATUS

REPL STATUS <dbname>;

将返回 REPL LOAD 返回的相同输出,允许 REPL LOAD 异步运行。如果不存在与该数据库关联的复制的知识,即没有已知的复制,则返回一个空集。请注意,在存在目标数据库或表但不存在已知 repl 的情况下,对于调用 REPL LOAD 的工具传递给最终用户以警告他们可能正在覆盖现有的数据库与另一个。

Return values:

  • 正常返回错误代码。

  • 返回给定数据库的最后复制状态(事件 ID)。

Bootstrap, Revisited

当我们介绍需要引导的概念时,我们说引导期间的时间流逝是一个需要单独解决的问题。

假设我们从 evid = 170 开始转储,到完成转储中包含的所有对象的转储时,现在为 evid = 230.为了使转储保持一致,我们现在还必须将事件 170-230 中包含的信息整合到转储中,然后才能将警棍传递给增量复制。

让我们考虑表 T1 的情况,该表在 evid = 200 左右转储了。现在,让我们说在转储进行期间,两个表上发生了以下操作:

event idoperation
184更改表 T1 的分区(Px)
196更改表 T1 添加分区(Pa)
204更改表 T1 添加分区(Pb)
216更改表 T1 的分区(Py)

基本上,让我们尝试了解在转储表之前和之后添加分区(Pa&Pb)和删除分区(Px&Py)时会发生什么。因此,对于我们的引导程序,我们经历了两个阶段-首先是对所有预期要转储的对象的对象转储,然后是合并阶段,在此阶段中处理对象转储期间发生的所有事件。

如果表 T1 在大约 evid = 200 处转储,则它将不包含分区 Px,因为删除操作将在转储发生之前进行,并且将包含分区 Pa,因为该分区是在对象转储之前添加的发生了。相反,分区 Pb 将不会出现在转储中,因为尚未添加 Pb,并且它仍将包含分区 Py,因为尚未删除该分区。

因此,鉴于这种差异,我们需要以某种方式加以整合。有两种合并方法。

方法 1:在目的地合并。

现在,一种解决方法是简单地说,我们说转储是整个对象的最小状态,例如 170,并且让各种事件只要适用就可以在目标上应用,而忽略错误(例如,当我们尝试从已没有 Px 的复制表 T1 中删除分区 Px 时.)

尽管这可行,但此方法的问题在于,由于转储,目标位置现在可以具有处于不同状态的表-即,在大约 evid = 220 处转储的表 T2 将比在大约 2 时转储的 T1 具有更新的信息。 evid = 200,这本身就是一种小型橡皮筋,因为整体的不同部分处于不同的状态。由于表的不同分区实际上可能处于不同的状态,因此这个问题实际上会更糟。因此,我们将不遵循这种方法。

方法 2:从源码进行合并。

然后,替代方法是遍历示例中从 evid = 170 到 evid = 230 的每个事件,它们是对象转储阶段开始时和对象转储阶段结束时的当前事件 ID。分别使用它来修改我们刚刚创建的对象转储。任何删除将导致更改/删除转储的对象,而任何创建将导致添加其他转储的对象。更改将导致转储的对象被其较新的等效对象替换。在此合并结束时,所有转储的对象都应能够在目的地上还原,就像它们的状态为 230,然后增量复制可以接管,continue 处理事件 230.

这是我们期望采取的方法。从当前的导出语义来看,这将需要做进一步的修改,即当前,export 仅导出每个表 1 个_metadata 文件,其中包含_metadata 文件本身中所有分区的列表。相反,现在,我们建议将其拆分,以使对象级别的_metadata 级别仅包含该对象的元数据。因此,表级别的_metadata 将仅包含表对象,并且其中的各个目录将包含所有必需的分区,并且这些目录中的每个目录都将具有分区级别_metadata。

Metastore 通知 API 的安全性

我们希望通过添加授权逻辑(其他 API 不受影响)来保护下面列出的 DbNotificationListener 相关的 metastore API。这三个 API 主要由复制操作使用,因此仅允许 admin/superuser 使用:

  • get_next_notification

  • get_current_notificationEventId

  • get_notification_events_count

相关的配置单元配置参数是“ hive.metastore.event.db.notification.api.auth”,默认情况下设置为 true。

auth 机制的工作原理如下:

  • 不管“ hive.metastore.event.db.notification.api.auth”设置如何,都可以在嵌入式 metasore 模式下跳过身份验证
    原因是我们知道 metastore 调用是由配置单元发出的,而不是其他正在运行 metastoreClient 端的未经授权的进程。

  • 如果“ hive.metastore.event.db.notification.api.auth”设置为 true,则在远程元存储模式下启用身份验证
    远程 metastoreClient 端的 UGI 始终在 metastore 服务器上设置。我们检索该用户信息,并根据proxy user设置检查该用户是否具有代理权限。例如,UGI 是用户“ hive”,“ hive”已配置为对主机列表具有代理特权。然后,身份验证将通过这些主机的通知相关呼叫。如果用户“ foo”正在执行 repl 操作(例如,通过带有 doAs = true 的 HS2),则除非用户“ foo”配置为具有代理特权,否则身份验证将失败。

Setup/Configuration

需要在源集群中设置以下参数-

hive.metastore.transactional.event.listeners = org.apache.hive.hcatalog.listener.DbNotificationListener

hive.metastore.dml.events = true

//“打开 ChangeManager,因此删除文件将转到 cmrootdir。”

hive.repl.cm .enabled = true

还有其他与复制相关的参数(及其默认值)。这些仅与充当源群集的群集有关。在大多数情况下,默认设置应适用于这些情况-

REPLDIR(“ hive.repl.rootdir”,“/user/hive/repl /”,“所有复制转储的 HDFS 根目录.”),
REPLCMDIR(“ hive.repl.cmrootdir”,“/user/hive/cmroot /”,“ ChangeManager 的根目录,用于已删除的文件.”),
REPLCMRETIAN(“ hive.repl.cm .retain”,“ 24h”,新的 TimeValidator(TimeUnit.HOURS),“将删除的文件保留在 cmrootdir 中的时间。”),
REPLCMINTERVAL(“ hive.repl.cm .interval”,“ 3600s”,新的 TimeValidator(TimeUnit.SECONDS),“ cmroot 清理线程的间隔。”),