HRegionServer是 RegionServer 实现。它负责服务和管理地区。在分布式群集中,RegionServer 在 DataNode 上运行。

71.1。接口

HRegionRegionInterface公开的方法包含面向数据和区域维护方法:

  • 数据(获取,放置,删除,下一步等)

  • Region(splitRegion,compactRegion 等)例如,当在表上调用Admin方法majorCompact时,客户端实际上迭代指定表的所有区域并直接请求对每个区域进行主要压缩。

71.2。流程

RegionServer 运行各种后台线程:

71.2.1。 CompactSplitThread

检查拆分并处理轻微压缩。

71.2.2。 MajorCompactionChecker

检查主要的压缩。

71.2.3。 MemStoreFlusher

定期刷新 MemStore 到 StoreFiles 中的内存中写入。

71.2.4。 LogRoller

定期检查 RegionServer 的 WAL。

71.3。协处理器

协处理器的添加量为 0.92。有一个彻底的博客概述 CoProcessors 发布。文档最终会转到此参考指南,但博客是目前最新的信息。

71.4。块缓存

HBase 提供两种不同的 BlockCache 实现来缓存从 HDFS 读取的数据:默认的堆上LruBlockCacheBucketCache,它(通常)是堆外的。本节讨论每个实现的优缺点,如何选择适当的选项以及每个实现的配置选项。

阻止缓存报告:U

有关缓存部署的详细信息,请参阅 RegionServer UI。请参阅配置,大小,当前使用情况,缓存时间,甚至是块计数和类型的详细信息。

71.4.1。缓存选择

LruBlockCache是原始实现,完全在 Java 堆中。 BucketCache是可选的,主要用于将块缓存数据保留在堆外,尽管BucketCache也可以是文件支持的缓存。

启用 BucketCache 时,您将启用双层缓存系统。我们曾经将这些层描述为“L1”和“L2”,但是从 hbase-2.0.0 开始就弃用了这个术语。 “L1”缓存将 LruBlockCache 的实例和“L2”引用到堆外 BucketCache。相反,当启用 BucketCache 时,所有 DATA 块都保存在 BucketCache 层中,元块(INDEX 和 BLOOM 块)在LruBlockCache中堆栈。这两个层次的管理以及决定块如何在它们之间移动的策略由CombinedBlockCache完成。

71.4.2。常规缓存配置

除了缓存实现本身,您还可以设置一些常规配置选项来控制缓存的执行方式。参见 CacheConfig 。设置任何这些选项后,重新启动或滚动重新启动群集以使配置生效。检查日志是否有错误或意外行为。

另见 Blockcache 的预取选项,它讨论了 HBASE-9857 中引入的新选项。

71.4.3。 LruBlockCache 设计

LruBlockCache 是​​一个 LRU 缓存,包含三个级别的块优先级,以允许扫描阻力和内存中的 ColumnFamilies:

  • 单一访问优先级:第一次从 HDFS 加载块时,它通常具有此优先级,并且它将成为驱逐期间要考虑的第一组的一部分。优点是扫描块比使用更多的块更容易被驱逐。

  • 多访问优先级:如果再次访问先前优先级组中的块,则会升级到此优先级。因此,它是在驱逐期间考虑的第二组的一部分。

  • 内存中访问优先级:如果块的族已配置为“内存中”,则它将成为此优先级的一部分,而不考虑它的访问次数。目录表配置如下。该组是在驱逐期间考虑的最后一组。

    要将列族标记为内存,请调用

HColumnDescriptor.setInMemory(true); 

如果从 java 创建表,或在 shell 中创建或更改表时设置IN_MEMORY ⇒ true:例如

hbase(main):003:0> create  't', {NAME => 'f', IN_MEMORY => 'true'} 

有关更多信息,请参阅 LruBlockCache 源

71.4.4。 LruBlockCache 用法

默认情况下,为所有用户表启用块缓存,这意味着任何读取操作都将加载 LRU 缓存。这可能适用于大量用例,但通常需要进一步调整才能获得更好的性能。一个重要的概念是工作集大小或 WSS,它是:“计算问题答案所需的内存量”。对于网站,这将是在短时间内回答查询所需的数据。

计算 HBase 中用于缓存的内存量的方法是:

number of region servers * heap size * hfile.block.cache.size * 0.99 

块缓存的默认值为 0.4,表示可用堆的 40%。最后一个值(99%)是 LRU 高速缓存中的默认可接受加载因子,在此之后启动逐出。它包含在这个等式中的原因是,说可以使用 100%的可用内存是不现实的,因为这会使进程从加载新块的点开始阻塞。这里有些例子:

  • 堆大小设置为 1 GB 的一个区域服务器和默认的块高速缓存大小将具有 405 MB 的块高速缓存可用。

  • 堆大小设置为 8 GB 的 20 个区域服务器和默认的块高速缓存大小将具有 63.3 的块高速缓存。

  • 堆大小设置为 24 GB 且块缓存大小为 0.5 的 100 个区域服务器将具有大约 1.16 TB 的块缓存。

您的数据不是块缓存的唯一驻留者。以下是您可能需要考虑的其他因素:

目录表

hbase:meta表被强制进入块缓存并具有内存中的优先级,这意味着它们更难被驱逐。

hbase:元表可以占用几 MB,具体取决于区域的数量。

HFiles 指数

HFile 是 HBase 用于在 HDFS 中存储数据的文件格式。它包含一个多层索引,允许 HBase 在不必读取整个文件的情况下查找数据。这些索引的大小是块大小(默认为 64KB),键大小和存储数据量的一个因素。对于大数据集,每个区域服务器的数字大约为 1GB 并不罕见,尽管并非所有数据都在缓存中,因为 LRU 将驱逐未使用的索引。

按键

存储的值只是图片的一半,因为每个值都与其键(行键,族限定符和时间戳)一起存储。请参阅尝试最小化行和列大小

布隆过滤器

就像 HFile 索引一样,这些数据结构(启用时)存储在 LRU 中。

目前,测量 HFile 索引和布隆过滤器大小的推荐方法是查看区域服务器 Web UI 并检查相关指标。对于密钥,可以使用 HFile 命令行工具进行采样,并查找平均密钥大小度量。从 HBase 0.98.3 开始,您可以在 UI 中的特殊“块缓存”部分中查看有关 BlockCache 统计信息和指标的详细信息。

当 WSS 不适合内存时,使用块缓存通常是不好的。例如,当您在所有区域服务器的块缓存中提供 40GB 可用时,您需要处理 1TB 数据。其中一个原因是驱逐产生的流失将不必要地引发更多的垃圾收集。以下是两个用例:

  • 完全随机的读取模式:这种情况下,您几乎不会在很短的时间内两次访问同一行,这样命中缓存块的几率就接近于 0.在这样的表上设置块缓存是浪费内存和 CPU 周期,更多,以便它将产生更多垃圾由 JVM 提取。有关监视 GC 的更多信息,请参阅 JVM 垃圾收集日志

  • 映射表:在输入中占用表的典型 MapReduce 作业中,每行只读一次,因此不需要将它们放入块缓存中。 Scan 对象可以选择通过 setCaching 方法将其关闭(将其设置为 false)。如果您需要快速随机读取访问,您仍然可以在此表上保持块缓存。一个例子是计算提供实时流量的表中的行数,缓存该表的每个块会产生大量流失,并且肯定会驱逐当前正在使用的数据。

仅缓存 META 块(fscache 中的 DATA 块)

一个有趣的设置是我们只缓存 META 块,我们在每次访问时读取 DATA 块。如果 DATA 块适合 fscache,当访问在非常大的数据集中完全随机时,这种替代方案可能有意义。要启用此设置,请更改表和每个列族集BLOCKCACHE ⇒ 'false'。您只为此列系列“禁用”BlockCache。您永远不能禁用 META 块的缓存。由于 HBASE-4683 始终缓存索引和布隆块,即使禁用了 BlockCache,我们也会缓存 META 块。

71.4.5。堆外块缓存

如何启用 BucketCache

BucketCache 的常规部署是通过一个管理类来设置两个缓存层:LruBlockCache 实现的堆内缓存和 BucketCache 实现的第二个缓存。默认情况下,管理类是 CombinedBlockCache 。上一个链接描述了 CombinedBlockCache 实现的缓存“策略”。简而言之,它的工作原理是将元块 - INDEX 和 BLOOM 保留在堆上 LruBlockCache 层 - 而 DATA 块保存在 BucketCache 层中。

Pre-hbase-2.0.0 版本

与 pre-hbase-2.0.0 中的 BucketCache 相比,与本机堆上 LruBlockCache 相比,获取总是会更慢。但是,随着时间的推移,延迟往往不那么不稳定,因为使用 BucketCache 时垃圾收集较少,因为它管理的是 BlockCache 分配,而不是 GC。如果 BucketCache 以堆外模式部署,则该内存根本不由 GC 管理。这就是为什么你在 2.0.0 之前使用 BucketCache,所以你的延迟不那么不稳定,以缓解 GC 和堆碎片,因此你可以安全地使用更多的内存。有关运行堆上和堆外测试的比较,请参阅 Nick Dimiduk 的 BlockCache 101 。另请参阅比较 BlockCache Deploys ,它会发现如果您的数据集适合您的 LruBlockCache 部署,请使用它,否则如果您遇到缓存流失(或者您希望缓存存在于 java GC 的变幻莫测之外),请使用 BucketCache 。

在 2.0.0 之前的版本中,可以配置 BucketCache,以便它接收 LruBlockCache 驱逐的victim。所有数据和索引块首先缓存在 L1 中。当从 L1 发生逐出时,块(或victims)将移动到 L2。通过(HColumnDescriptor.setCacheDataInL1(true)或在 shell 中设置cacheDataInL1,创建或修改列族设置CACHE_DATA_IN_L1为真:例如

hbase(main):003:0> create 't', {NAME => 't', CONFIGURATION => {CACHE_DATA_IN_L1 => 'true'}} 

hbase-2.0.0 +版本

HBASE-11425 更改了 HBase 读取路径,因此它可以将读取数据保留在堆外,从而避免将缓存数据复制到 Java 堆上。参见 Offheap 读取路径。在 hbase-2.0.0 中,堆外延迟接近堆栈缓存延迟,具有不会激发 GC 的额外好处。

从 HBase 2.0.0 开始,L1 和 L2 的概念已被弃用。当 BucketCache 打开时,DATA 块将始终转到 BucketCache,INDEX / BLOOM 块转到堆 LRUBlockCache。 cacheDataInL1支持已被删除。

BucketCache Block Cache 可以部署 _ 堆外 文件 _ 或 mmaped 文件模式。

您可以通过hbase.bucketcache.ioengine设置进行设置。将其设置为offheap将使 BucketCache 在堆外进行分配,并且file:PATH_TO_FILE的 ioengine 设置将指示 BucketCache 使用文件缓存(特别是如果您有一些快速 I / O 连接到盒子,如 SSD) )。从 2.0.0 开始,可以有多个文件支持 BucketCache。当 Cache 大小要求很高时,这非常有用。对于多个后备文件,请将 ioengine 配置为files:PATH_TO_FILE1,PATH_TO_FILE2,PATH_TO_FILE3。 BucketCache 也可以配置为使用 mmapped 文件。为此,将 ioengine 配置为mmap:PATH_TO_FILE

可以部署分层设置,我们绕过 CombinedBlockCache 策略并让 BucketCache 作为 L1 LruBlockCache 的严格 L2 缓存。对于这样的设置,将hbase.bucketcache.combinedcache.enabled设置为false。在这种模式下,在从 L1 驱逐时,块转到 L2。缓存块时,它首先在 L1 中缓存。当我们去寻找一个缓存的块时,我们首先查看 L1,如果没有找到,则搜索 L2。我们称这种部署格式为 Raw L1 + L2 。注意:此 L1 + L2 模式已从 2.0.0 中删除。使用 BucketCache 时,它将严格地是 DATA 缓存,LruBlockCache 将缓存 INDEX / META 块。

其他 BucketCache 配置包括:指定一个位置以在重新启动时保持缓存,使用多少线程来编写缓存等。有关配置选项和说明,请参阅 CacheConfig.html 类。

要检查它是否已启用,请查找描述缓存设置的日志行;它将详细介绍如何部署 BucketCache。另请参阅 UI。它将详细介绍缓存分层及其配置。

BucketCache 示例配置

此示例提供了 4 GB 堆外 BucketCache 的配置,其中包含 1 GB 的堆栈缓存。

配置在 RegionServer 上执行。

设置hbase.bucketcache.ioenginehbase.bucketcache.size> 0 启用CombinedBlockCache。让我们假设 RegionServer 已设置为以 5G 堆运行:即HBASE_HEAPSIZE=5g

  1. 首先,编辑 RegionServer 的 hbase-env.sh 并将HBASE_OFFHEAPSIZE设置为大于所需的堆外大小的值,在本例中为 4 GB(表示为 4G)。我们将它设置为 5G。对于我们的堆外缓存,这将是 4G,对于堆外存储器的任何其他用途都是 1G(除了 BlockCache 之外还有其他堆外存储器用户;例如 RegionServer 中的 DFSClient 可以利用堆外存储器)。参见 HBase 中的直接内存使用情况。

    HBASE_OFFHEAPSIZE=5G 
    
  2. 接下来,将以下配置添加到 RegionServer 的 hbase-site.xml

    <property>
      <name>hbase.bucketcache.ioengine</name>
      <value>offheap</value>
    </property>
    <property>
      <name>hfile.block.cache.size</name>
      <value>0.2</value>
    </property>
    <property>
      <name>hbase.bucketcache.size</name>
      <value>4196</value>
    </property> 
    
  3. 重新启动或滚动重新启动群集,并检查日志中是否存在任何问题。

在上面,我们将 BucketCache 设置为 4G。我们配置堆上 LruBlockCache 有 20%(0.2)的 RegionServer 的堆大小(0.2 * 5G = 1G)。换句话说,您可以像平常一样配置 L1 LruBlockCache(就好像没有 L2 缓存一样)。

HBASE-10641 引入了为 BucketCache 的桶配置多种大小的能力,HBase 0.98 及更高版本。要配置多个存储桶大小,请将新属性hbase.bucketcache.bucket.sizes配置为以逗号分隔的块大小列表,从最小到最大排序,不带空格。目标是根据您的数据访问模式优化存储桶大小。以下示例配置大小为 4096 和 8192 的存储桶。

<property>
  <name>hbase.bucketcache.bucket.sizes</name>
  <value>4096,8192</value>
</property> 

HBase 中的直接内存使用

默认的最大直接内存因 JVM 而异。传统上它是 64M 或与分配的堆大小(-Xmx)或根本没有限制(显然是 JDK7)的某种关系。 HBase 服务器使用直接内存,特别是短路读取(参见利用本地数据),托管 DFSClient 将分配直接内存缓冲区。 DFSClient 使用多少不容易量化;它是打开的 HFiles * hbase.dfs.client.read.shortcircuit.buffer.size的数量,其中hbase.dfs.client.read.shortcircuit.buffer.size在 HBase 中设置为 128k - 参见 hbase-default.xml 默认配置。如果你进行堆外块缓存,你将使用直接内存。 RPCServer 使用 ByteBuffer 池。从 2.0.0 开始,这些缓冲区是堆外 ByteBuffers。启动 JVM,确保 conf / hbase-env.sh 中的-XX:MaxDirectMemorySize设置考虑了堆外 BlockCache(hbase.bucketcache.size),DFSClient 使用情况,RPC 端 ByteBufferPool 最大值。这必须比关闭堆 BlockCache 大小和最大 ByteBufferPool 大小的总和高一点。为最大直接内存大小分配额外的 1-2 GB 已经在测试中起作用。直接内存是 Java 进程堆的一部分,它与-Xmx 分配的对象堆是分开的。 MaxDirectMemorySize分配的值不得超过物理 RAM,并且由于其他内存要求和系统限制,可能会小于总可用 RAM。

您可以通过查看 Server Metrics:Memory 选项卡,查看 RegionServer 配置使用的内存量和堆外/直接内存量以及它在任何时候使用了多少内存。 UI。它也可以通过 JMX 获得。特别是服务器当前使用的直接内存可以在java.nio.type=BufferPool,name=direct bean 上找到。在 Java 中使用堆外内存时,Terracotta 有一个良好的写入。它适用于他们的产品 BigMemory,但是很多问题一般都适用于任何脱机的尝试。看看这个。

hbase.bucketcache.percentage.in.combinedcache

这是删除了之前的 HBase 1.0 配置,因为它令人困惑。它是一个浮点数,你可以设置为 0.0 到 1.0 之间的某个值。它的默认值是 0.9。如果部署使用 CombinedBlockCache,则 LruBlockCache L1 大小计算为(1 - hbase.bucketcache.percentage.in.combinedcache) * size-of-bucketcache,BucketCache 大小为hbase.bucketcache.percentage.in.combinedcache * size-of-bucket-cache。其中 bucket-cache-cache 本身的大小是配置的值hbase.bucketcache.size如果它被指定为兆字节 OR hbase.bucketcache.size * -XX:MaxDirectMemorySize,如果hbase.bucketcache.size在 0 和 1.0 之间。

在 1.0 中,它应该更直接。使用hfile.block.cache.size setting(不是最佳名称)将 Onheap LruBlockCache 大小设置为 java 堆的一小部分,并将 BucketCache 设置为绝对兆字节。

71.4.6。压缩的 BlockCache

HBASE-11331 引入了懒惰的 BlockCache 解压缩,更简单地称为压缩的 BlockCache。当启用压缩的 BlockCache 时,数据和编码数据块以其磁盘格式缓存在 BlockCache 中,而不是在缓存之前进行解压缩和解密。

对于托管更多数据而不能容纳缓存的 RegionServer,通过 SNAPPY 压缩启用此功能已显示吞吐量增加 50%,平均延迟提高 30%,同时将垃圾收集增加 80%并增加总体 CPU 负载 2%。有关如何衡量和实现性能的更多详细信息,请参阅 HBASE-11331。对于托管可以轻松适应缓存的数据的 RegionServer,或者如果您的工作负载对额外的 CPU 或垃圾收集负载敏感,您可能会获得较少的好处。

默认情况下禁用压缩的 BlockCache。要启用它,请在所有 RegionServers 上的 hbase-site.xml 中将hbase.block.data.cachecompressed设置为true

71.5。 RegionServer Offheap 读/写路径

71.5.1。 Offheap 读取路径

在 hbase-2.0.0 中, HBASE-11425 改变了 HBase 读取路径,因此它可以将读取数据保留在堆外,从而避免将缓存数据复制到 Java 堆上。由于产生的垃圾较少而且清除较少,因此可以减少 GC 暂停。堆外读取路径的性能与堆上 LRU 缓存的性能类似/更好。此功能自 HBase 2.0.0 起可用。如果 BucketCache 处于file模式,则与本机堆上 LruBlockCache 相比,获取总是会更慢。有关更多详细信息和测试结果,请参阅下面的博客关闭 Apache HBase 中的读取路径:第 2 部分生产中的 Offheap 读取路径 - 阿里巴巴故事

对于端到端的非堆积读取路径,首先应该有一个堆外支持的堆外块缓存(BC)。在 hbase-site.xml 中将'hbase.bucketcache.ioengine'配置为堆外。同时使用hbase.bucketcache.size配置指定 BC 的总容量。请记住在 hbase-env.sh 中调整'HBASE_OFFHEAPSIZE'的值。这就是我们如何为 RegionServer java 进程指定最大可能的堆外内存分配。这应该比堆外 BC 大小更大。请记住,hbase.bucketcache.ioengine没有默认值,这意味着 BC 默认关闭(参见 HBase 中的直接内存使用情况)。

接下来要调整的是 RPC 服务器端的 ByteBuffer 池。此池中的缓冲区将用于累积单元字节并创建结果单元块以发送回客户端。 hbase.ipc.server.reservoir.enabled可用于打开或关闭此池。默认情况下,此池为 ON 且可用。 HBase 将创建关闭堆 ByteBuffers 并将它们池化。如果您希望在读取路径中进行端到端的堆叠,请确保不要将其关闭。如果关闭此池,服务器将在堆上创建临时缓冲区以累积单元字节并生成结果单元块。这可能会影响高度读取加载的服务器上的 GC。用户可以根据池中缓冲区的数量以及每个 ByteBuffer 的大小来调整此池。使用 config hbase.ipc.server.reservoir.initial.buffer.size调整每个缓冲区大小。默认值为 64 KB。

当读取模式是随机行读取负载并且每个行的大小与此 64 KB 相比较小时,请尝试减少此行。当结果大小大于一个 ByteBuffer 大小时,服务器将尝试获取多个缓冲区并从中生成结果单元块。当池缓冲区用完时,服务器将最终创建临时堆栈缓冲区。

可以使用 config'hbase.ipc.server.reservoir.initial.max'调整池中 ByteBuffers 的最大数量。其值默认为 64 *区域服务器处理程序配置(请参阅 config'hbase.regionserver.handler.count')。数学是这样的,默认情况下我们认为每个读取结果的结果单元块大小为 2 MB,每个处理程序将处理读取。对于 2 MB 大小,我们需要 32 个大小为 64 KB 的缓冲区(请参阅池中的默认缓冲区大小)。所以每个处理程序 32 ByteBuffers(BB)。我们将此大小的两倍分配为最大 BBs 计数,以便一个处理程序可以创建响应并将其交给 RPC Responder 线程,然后处理创建新响应单元块的新请求(使用池化缓冲区)。即使响应者无法立即发回第一个 TCP 回复,我们的计数应该允许我们在池中仍然有足够的缓冲区而不必在堆上创建临时缓冲区。同样,对于较小尺寸的随机行读取,请调整此最大计数。有懒惰创建的缓冲区,计数是要合并的最大计数。

如果在堆外端到端读取路径之后仍然看到 GC 问题,请在相应的缓冲池中查找问题。使用 INFO 级别检查以下 RegionServer 日志:

Pool already reached its max capacity : XXX and no free buffers now. Consider increasing the value for 'hbase.ipc.server.reservoir.initial.max' ? 

hbase-env.sh 中 _HBASE OFFHEAPSIZE 的设置也应考虑 RPC 端的堆缓冲池。我们需要将 RegionServer 的最大堆大小配置为略高于此最大池大小和关闭堆高速缓存大小的总和。 TCP 层还需要为 TCP 通信创建直接字节缓冲区。此外,DFS 客户端将需要一些堆外工作来完成其工作,尤其是在配置了短路读取的情况下。为最大直接内存大小分配额外的 1 - 2 GB 已经在测试中起作用。

如果您正在使用协处理器并在读取结果中引用单元格,请不要将这些单元格的引用存储在 CP 挂钩方法的范围之外。有时,CP 需要存储有关单元格的信息(如其行键),以便在下一个 CP 钩子调用等中进行考虑。对于这种情况,请根据用例克隆整个 Cell 的必需字段。 [参见 CellUtil#cloneXXX(Cell)API]

71.5.2。 Offheap 写路径

去做

71.6。 RegionServer 拆分实现

由于写请求由区域服务器处理,它们会累积在称为 memstore 的内存存储系统中。 memstore 填充后,其内容将作为附加存储文件写入磁盘。此事件称为 memstore flush 。随着存储文件的累积,RegionServer 将压缩为更少,更大的文件。每次刷新或压缩完成后,存储在该区域中的数据量已更改。 RegionServer 查询区域拆分策略,以确定该区域是否已经变得太大,或者是否应该针对另一个特定于策略的原因进行拆分。如果策略建议,则会将区域拆分请求排入队列。

从逻辑上讲,分割区域的过程很简单。我们在区域的键空间中找到一个合适的点,我们应该将区域分成两半,然后将区域的数据分成两个新的区域。然而,该过程的细节并不简单。当发生拆分时,新创建的 _ 子区域 _ 不会立即将所有数据重写为新文件。相反,他们创建类似于符号链接文件的小文件,名为参考文件,它根据分割点指向父商店文件的顶部或底部。参考文件的使用方式与常规数据文件类似,但只考虑了一半的记录。如果不再有对父区域的不可变数据文件的引用,则只能拆分该区域。这些参考文件通过压缩逐渐清理,以便该区域将停止引用其父文件,并可以进一步拆分。

虽然拆分区域是 RegionServer 做出的本地决策,但拆分过程本身必须与许多参与者协调。 RegionServer 在拆分之前和之后通知 Master,更新.META.表,以便客户端可以发现新的子区域,并重新排列 HDFS 中的目录结构和数据文件。拆分是一个多任务流程。要在发生错误时启用回滚,RegionServer 会保留有关执行状态的内存日记。 RegionServer 执行拆分所采取的步骤在 RegionServer 拆分流程中说明。每个步骤都标有其步骤编号。 RegionServers 或 Master 的操作显示为红色,而客户端的操作显示为绿色。

Region Split Process图 1. RegionServer 拆分流程

  1. RegionServer 在本地决定拆分区域,并准备拆分。 分拆交易已经开始。 作为第一步,RegionServer 获取表上的共享读锁,以防止在拆分过程中修改模式。然后它在/hbase/region-in-transition/region-name下的 zookeeper 中创建一个 znode,并将 znode 的状态设置为SPLITTING

  2. Master 了解这个 znode,因为它有一个父region-in-transition znode 的观察者。

  3. RegionServer 在 HDFS 的父级region目录下创建一个名为.splits的子目录。

  4. RegionServer 关闭父区域,并在其本地数据结构中将该区域标记为脱机。 分裂区域现在离线。 此时,来到父区域的客户端请求将抛出NotServingRegionException。客户端将重试一些退避。关闭区域被刷新。

  5. RegionServer 在.splits目录下为子区域 A 和 B 创建区域目录,并创建必要的数据结构。然后它会分割存储文件,因为它会在父区域中为每个存储文件创建两个参考文件。这些参考文件将指向父区域的文件。

  6. RegionServer 在 HDFS 中创建实际的区域目录,并移动每个子项的参考文件。

  7. RegionServer 将Put请求发送到.META.表,在.META.表中将父级设置为脱机,并添加有关子区域的信息。此时,.META.中的女儿不会有个别条目。客户将看到父区域在扫描.META.时被拆分,但在.META.出现之前不会知道这些女儿。另外,如果这Put.META。成功后,父母将被有效分割。如果 RegionServer 在此 RPC 成功之前失败,则 Master 和下一个打开该区域的 Region Server 将清除有关区域拆分的脏状态。但是,.META.更新后,区域拆分将由 Master 进行前滚。

  8. RegionServer 并行打开女儿 A 和 B.

  9. RegionServer 将女儿 A 和 B 添加到.META.,以及它托管区域的信息。 分裂地区(带有父母参考的孩子)现在在线。 在此之后,客户可以发现新区域并向他们发出请求。客户端在本地缓存.META.条目,但是当它们向 RegionServer 或.META.发出请求时,它们的缓存将无效,并且它们将从.META.中了解新区域。

  10. RegionServer 将 ZooKeeper 中的 znode /hbase/region-in-transition/region-name更新为状态SPLIT,以便主人可以了解它。如有必要,平衡器可以自由地将子区域重新分配给其他区域服务器。 分裂交易现已完成。

  11. 拆分后,.META.和 HDFS 仍将包含对父区域的引用。当子区域中的压缩重写数据文件时,将删除这些引用。主服务器中的垃圾收集任务会定期检查子区域是否仍然引用父区域的文件。如果不是,则将删除父区域。

71.7。写前方日志(WAL)

71.7.1。目的

_ 预写日志(WAL)_ 将对 HBase 中数据的所有更改记录到基于文件的存储。在正常操作下,不需要 WAL,因为数据更改从 MemStore 移动到 StoreFiles。但是,如果 RegionApp 在刷新 MemStore 之前崩溃或变得不可用,则 WAL 会确保可以重播对数据的更改。如果写入 WAL 失败,则修改数据的整个操作将失败。

HBase 使用 WAL 接口的实现。通常,每个 RegionServer 只有一个 WAL 实例。一个例外是携带 hbase:meta 的 RegionServer; meta 表获得了自己的专用 WAL。 RegionServer 记录其 WAL 的删除和删除,然后为受影响的商店记录这些突变 MemStore

HLo

在 2.0 之前,HBase 中 WALs 的接口被命名为HLog。在 0.94 中,HLog 是 WAL 实现的名称。您可能会在针对这些旧版本的文档中找到对 HLog 的引用。

WAL 驻留在 / hbase / WALs / 目录中的 HDFS 中,每个区域都有子目录。

有关预写日志概念的更多一般信息,请参阅 Wikipedia Write-Ahead Log 文章。

71.7.2。 WAL 提供商

在 HBase 中,有许多 WAL 元素(或“提供者”)。每个都有一个简短的名称标签(不幸的是,它并不总是描述性的)。您在 hbase-site.xml 中设置提供程序,将 WAL 提供者短名称作为 hbase.wal.provider 属性的值设置(设置 hbase 的提供程序: meta 使用 _hbase.wal.meta _ 提供程序 _ 属性,否则它使用由 hbase.wal.provider 配置的相同提供程序。

  • asyncfs默认。自 hbase-2.0.0 以来的新版本(HBASE-15536​​,HBASE-14790)。这个 AsyncFSWAL 提供程序,它在 RegionServer 日志中标识自身,是基于一个新的非阻塞 dfsclient 实现。它目前驻留在 hbase 代码库中,但目的是将其移回 HDFS 本身。 WALs 编辑被并发(“扇出”)样式写入每个 DataNode 上的每个 WAL 块复制品,而不是像默认客户端那样在链式管道中。延迟应该更好。有关实施的更多详细信息,请参阅第 14 页第 14 页的小米的 Apache HBase 改进和实践。

  • filesystem :这是 hbase-1.x 版本中的默认值。它建立在阻塞 DFSClient 的基础上,并在经典 DFSCLient 管道模式下写入副本。在日志中,它标识为 FSHLogFSHLogProvider

  • multiwal :此提供程序由 asyncfs 或 _ 文件系统 _ 的多个实例组成。有关 multiwal 的更多信息,请参阅下一节。

在 RegionServer 日志中查找如下所示的行,以查看适当的提供程序(下面显示了默认的 AsyncFSWALProvider):

2018-04-02 13:22:37,983 INFO  [regionserver/ve0528:16020] wal.WALFactory: Instantiating WALProvider of type class org.apache.hadoop.hbase.wal.AsyncFSWALProvider 

由于 AsyncFSWAL 侵入了 DFSClient 实现的内部,因此即使对于简单的补丁版本,也可以通过升级 hadoop 依赖关系来轻松破解它。因此,如果您没有明确指定 wal 提供程序,我们将首先尝试使用 asyncfs ,如果失败,我们将回退使用 _ 文件系统 _。请注意,这可能并不总是有效,因此如果由于启动 AsyncFSWAL 的问题仍然无法启动 HBase,请在配置文件中明确指定 _ 文件系统 _。

已经在 hadoop-3.x 中添加了 EC 支持,并且它与 WAL 不兼容,因为 EC 输出流不支持 hflush / hsync。为了在 EC 目录中创建非 EC 文件,我们需要为 FileSystem 使用新的基于构建器的创建 API,但它仅在 hadoop-2.9 +中引入,对于 HBase 我们仍然需要支持 hadoop-2.7.x。因此,在我们找到处理它的方法之前,请不要为 WAL 目录启用 EC。

71.7.3。 MultiWAL

对于每个 RegionServer 一个 WAL,RegionServer 必须串行写入 WAL,因为 HDFS 文件必须是顺序的。这导致 WAL 成为性能瓶颈。

HBase 1.0 在 HBASE-5699 中引入了对 MultiWal 的支持。 MultiWAL 允许 RegionServer 通过在底层 HDFS 实例中使用多个管道来并行写入多个 WAL 流,这会增加写入期间的总吞吐量。这种并行化是通过按区域划分传入编辑来完成的。因此,当前的实现无助于增加单个区域的吞吐量。

使用原始 WAL 实现的 RegionServers 和使用 MultiWAL 实现的 RegionServers 都可以处理任一组 WAL 的恢复,因此通过滚动重启可以实现零停机配置更新。

配置 MultiWAL

要为 RegionServer 配置 MultiWAL,请通过粘贴以下 XML 将属性hbase.wal.provider的值设置为multiwal

<property>
  <name>hbase.wal.provider</name>
  <value>multiwal</value>
</property> 

重新启动 RegionServer 以使更改生效。

要为 RegionServer 禁用 MultiWAL,请取消设置该属性并重新启动 RegionServer。

71.7.4。 WAL 法拉盛

TODO(描述)。

71.7.5。 WAL 分裂

RegionServer 服务于许多地区。区域服务器中的所有区域共享相同的活动 WAL 文件。 WAL 文件中的每个编辑都包含有关它所属的区域的信息。打开某个区域时,需要重播属于该区域的 WAL 文件中的编辑。因此,WAL 文件中的编辑必须按区域分组,以便可以重放特定的集合以重新生成特定区域中的数据。按区域对 WAL 编辑进行分组的过程称为 _ 日志分割 _。如果区域服务器出现故障,这是恢复数据的关键过程。

日志拆分由集群启动期间的 HMaster 完成,或者由区域服务器关闭时由 ServerShutdownHandler 完成。为了保证一致性,受影响的区域在数据恢复之前不可用。在给定区域再次可用之前,需要恢复和重放所有 WAL 编辑。因此,在进程完成之前,受日志拆分影响的区域将不可用。

过程:Log Step,Step by Step

  1. 重命名 / hbase / WALs / <host>,<port>, <startcode></startcode> </port></host> 目录。

    重命名目录很重要,因为即使 HMaster 认为它已关闭,RegionServer 仍可能正在启动并接受请求。如果 RegionServer 没有立即响应并且没有对其 ZooKeeper 会话进行心跳,则 HMaster 可能会将其解释为 RegionServer 故障。重命名 logs 目录可确保仍然无法写入仍由活动但繁忙的 RegionServer 使用的现有有效 WAL 文件。

    新目录根据以下模式命名:

    /hbase/WALs/&lt;host&gt;,&lt;port&gt;,&lt;startcode&gt;-splitting 
    

    此类重命名目录的示例可能如下所示:

    /hbase/WALs/srv.example.com,60020,1254173957298-splitting 
    
  2. 每个日志文件被拆分,一次一个。

    日志分割器一次读取一个编辑条目的日志文件,并将每个编辑条目放入与编辑区域对应的缓冲区中。同时,拆分器启动了几个写入程序线程。 Writer 线程获取相应的缓冲区并将缓冲区中的编辑条目写入临时恢复的编辑文件。临时编辑文件使用以下命名模式存储到磁盘:

    /hbase/&lt;table_name&gt;/&lt;region_id&gt;/recovered.edits/.temp 
    

    此文件用于存储此区域的 WAL 日志中的所有编辑。日志分割完成后, .temp 文件将重命名为写入该文件的第一个日志的序列 ID。

    要确定是否已编写所有编辑,将序列 ID 与写入 HFile 的最后一次编辑的序列进行比较。如果最后一次编辑的序列大于或等于文件名中包含的序列 ID,则很明显编辑文件中的所有写入都已完成。

  3. 日志拆分完成后,每个受影响的区域都将分配给 RegionServer。

    打开该区域时,将检查 restored.edits 文件夹中是否有恢复的编辑文件。如果存在任何此类文件,则通过阅读编辑并将其保存到 MemStore 来重放它们。重放所有编辑文件后,MemStore 的内容将写入磁盘(HFile)并删除编辑文件。

日志拆分过程中的错误处理

如果将hbase.hlog.split.skip.errors选项设置为true,则会将错误视为:

  • 将记录拆分期间遇到的任何错误。

  • 有问题的 WAL 日志将被移动到 hbase rootdir下的 .corrupt 目录中,

  • WALL 的处理将继续进行

如果hbase.hlog.split.skip.errors选项设置为false(默认值),则会传播该异常并将拆分记录为失败。请参阅 HBASE-2958 当 hbase.hlog.split.skip.errors 设置为 false 时,我们无法进行拆分,但这就是。如果设置了这个标志,我们需要做的不仅仅是失败拆分。

拆分崩溃的 RegionServer 的 WAL 时如何处理 EOFExceptions

如果在分割日志时发生 EOFException,即使hbase.hlog.split.skip.errors设置为false,分割仍会继续。在读取要拆分的文件集中的最后一个日志时可能会出现 EOFException,因为 RegionServer 可能正在崩溃时写入记录。有关背景信息,请参阅 HBASE-2643 图如何处理 eof 拆分日志

日志拆分期间的性能改进

WAL 日志拆分和恢复可能是资源密集型的,需要很长时间,具体取决于崩溃中涉及的 RegionServers 的数量和区域的大小。 启用或禁用分布式日志拆分是为了提高日志拆分期间的性能而开发的。

启用或禁用分布式日志拆分

默认情况下启用分布式日志处理,因为 HBase 为 0.92。该设置由hbase.master.distributed.log.splitting属性控制,可以设置为truefalse,但默认为true

分布式日志拆分,一步一步

配置分布式日志分割后,HMaster 控制该过程。 HMaster 在日志分割过程中注册每个 RegionServer,分割日志的实际工作由 RegionServers 完成。如分布式日志分割,逐步中所述的日志分割的一般过程仍然适用于此处。

  1. 如果启用了分布式日志处理,则 HMaster 会在启动集群时创建 _ 拆分日志管理器 _ 实例。

    1. 拆分日志管理器管理需要扫描和拆分的所有日志文件。

    2. 拆分日志管理器将所有日志放入 ZooKeeper splitWAL 节点( / hbase / splitWAL )作为任务。

    3. 您可以通过发出以下zkCli命令来查看 splitWAL 的内容。显示示例输出。

      ls /hbase/splitWAL
      [hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2FWALs%2Fhost8.sample.com%2C57020%2C1340474893275-splitting%2Fhost8.sample.com%253A57020.1340474893900,
      hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2FWALs%2Fhost3.sample.com%2C57020%2C1340474893299-splitting%2Fhost3.sample.com%253A57020.1340474893931,
      hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2FWALs%2Fhost4.sample.com%2C57020%2C1340474893287-splitting%2Fhost4.sample.com%253A57020.1340474893946] 
      

      输出包含一些非 ASCII 字符。解码后,它看起来更简单:

      [hdfs://host2.sample.com:56020/hbase/WALs
      /host8.sample.com,57020,1340474893275-splitting
      /host8.sample.com%3A57020.1340474893900,
      hdfs://host2.sample.com:56020/hbase/WALs
      /host3.sample.com,57020,1340474893299-splitting
      /host3.sample.com%3A57020.1340474893931,
      hdfs://host2.sample.com:56020/hbase/WALs
      /host4.sample.com,57020,1340474893287-splitting
      /host4.sample.com%3A57020.1340474893946] 
      

      该列表表示要扫描和拆分的 WAL 文件名,这是日志拆分任务的列表。

  2. 拆分日志管理器监视日志拆分任务和工作人员。

    拆分日志管理器负责以下正在进行的任务:

    • 一旦拆分日志管理器将所有任务发布到 splitWAL znode,它就会监视这些任务节点并等待它们被处理。

    • 检查是否有任何死亡分裂日志工作者排队。如果它找到无响应的工作人员声称的任务,它将重新提交这些任务。如果重新提交由于某些 ZooKeeper 异常而失败,则死亡工作者将再次排队等待重试。

    • 检查是否有任何未分配的任务。如果找到任何,它将创建一个短暂的重新扫描节点,以便通知每个拆分日志工作者通过nodeChildrenChanged ZooKeeper 事件重新扫描未分配的任务。

    • 检查已分配但已过期的任务。如果找到任何一个,它们将再次移回TASK_UNASSIGNED状态,以便可以重试它们。这些任务可能会分配给慢速工作人员,或者可能已经完成。这不是问题,因为日志拆分任务具有幂等性。换句话说,可以多次处理相同的日志分割任务而不会引起任何问题。

    • 拆分日志管理器不断监视 HBase 拆分日志 znodes。如果更改了任何拆分日志任务节点数据,则拆分日志管理器将检索节点数据。节点数据包含任务的当前状态。您可以使用zkCli get命令检索任务的当前状态。在下面的示例输出中,输出的第一行显示该任务当前未分配。

      get /hbase/splitWAL/hdfs%3A%2F%2Fhost2.sample.com%3A56020%2Fhbase%2FWALs%2Fhost6.sample.com%2C57020%2C1340474893287-splitting%2Fhost6.sample.com%253A57020.1340474893945
      
      unassigned host2.sample.com:57000
      cZxid = 0×7115
      ctime = Sat Jun 23 11:13:40 PDT 2012
      ... 
      

      根据更改数据的任务的状态,拆分日志管理器执行以下操作之一:

    • 如果未分配,则重新提交任务

    • 如果已分配任务,请心跳任务

    • 如果任务被重新签名则重新提交或失败(请参阅任务失败的原因

    • 如果任务完成但有错误,请重新提交或失败(请参阅任务失败的原因

    • 如果由于错误而无法完成任务,请重新提交或失败(请参阅任务失败的原因