# Apache HBase 性能调优

## 114.操作系统

### 114.1。记忆

RAM，RAM，RAM。不要饿死 HBase。

### 114.2。 64 位

使用 64 位平台（和 64 位 JVM）。

### 114.3。交换

注意交换。将`swappiness`设置为 0。

### 114.4。中央处理器

确保已将 Hadoop 设置为使用本机硬件校验和。见链接：[hadoop.native.lib]。

## 115.网络

避免网络问题降低 Hadoop 和 HBase 性能的最重要因素可能是使用的交换硬件，在项目范围的早期做出的决策可能会导致群集大小增加一倍或三倍（或更多）时出现重大问题。

需要考虑的重要事项：

*   设备的切换容量

*   连接的系统数量

*   上行容量

### 115.1。单开关

此配置中最重要的一个因素是硬件的交换容量能够处理连接到交换机的所有系统可能产生的流量。一些较低价格的商用硬件可以具有比完整交换机可以使用的更慢的交换容量。

### 115.2。多个开关

多个交换机是架构中的潜在缺陷。低价硬件的最常见配置是从一个交换机到另一个交换机的简单 1Gbps 上行链路。这种经常被忽视的夹点很容易成为集群通信的瓶颈。特别是对于同时读取和写入大量数据的 MapReduce 作业，此上行链路上的通信可能已经饱和。

缓解此问题非常简单，可以通过多种方式完成：

*   使用适当的硬件来处理您尝试构建的群集的规模。

*   使用较大的单开关配置，即单个 48 端口而不是 2 个 24 端口

*   配置上行链路的端口中继，以利用多个接口来增加交叉交换机带宽。

### 115.3。多个机架

多个机架配置带来与多个交换机相同的潜在问题，并且可能会从两个主要方面降低性能：

*   开关容量性能不佳

*   上行链路到另一个机架的能力不足

如果机架中的交换机具有适当的交换容量以全速处理所有主机，则下一个最可能的问题将是通过在机架中引导更多群集引起的。跨越多个机架时避免问题的最简单方法是使用端口中继来创建到其他机架的绑定上行链路。然而，该方法的缺点在于可能使用的端口的开销。这样做的一个例子是，从机架 A 到机架 B 创建一个 8Gbps 端口通道，使用 24 个端口中的 8 个在机架之间进行通信会降低投资回报率，使用太少但是可能意味着您无法充分利用簇。

在机架之间使用 10Gbe 链路将大大提高性能，并且假设您的交换机支持 10Gbe 上行链路或允许扩展卡，则允许您为机器而不是上行链路保存端口。

### 115.4。网络接口

所有网络接口都正常运行吗？你确定吗？请参阅[案例研究＃1（单节点上的性能问题）](#casestudies.slownode)中的故障排除案例研究。

### 115.5。网络一致性和分区容忍度

[CAP 定理](http://en.wikipedia.org/wiki/CAP_theorem)指出分布式系统可以保持以下三个特征中的两个： - _C_ onsistency - 所有节点都看到相同的数据。 - _A_ 可用性 - 每个请求都会收到有关它是成功还是失败的响应。 - _P_ 的动作容忍度 - 即使其他组件无法使用，系统也会继续运行。

HBase 支持一致性和分区容忍度，必须做出决定。 Coda Hale 在 [http://codahale.com/you-cant-sacrifice-partition-tolerance/](http://codahale.com/you-cant-sacrifice-partition-tolerance/) 中解释了为什么分区容差如此重要。

罗伯特 Yokota 使用一个名为 [Jepson](https://aphyr.com/tags/jepsen) 的自动化测试框架来测试 HBase 在网络分区面前的分区容忍度，使用 Aphyr 的 [Call Me Maybe](https://aphyr.com/posts/281-call-me-maybe-carly-rae-jepsen-and-the-perils-of-network-partitions) 系列之后建模的技术。结果，作为[博客文章](https://rayokota.wordpress.com/2015/09/30/call-me-maybe-hbase/)和[附录](https://rayokota.wordpress.com/2015/09/30/call-me-maybe-hbase-addendum/)，显示 HBase 正确执行。

## 116\. Java

### 116.1。垃圾收集器和 Apache HBase

#### 116.1.1。长 GC 暂停

在他的演讲中，[避免使用 MemStore-本地分配缓冲区](http://www.slideshare.net/cloudera/hbase-hug-presentation)，Todd Lipcon 描述了 HBase 中常见的两种世界性垃圾收集案例，特别是在加载过程中; CMS 故障模式和老一代堆碎片带来了。

要解决第一个问题，请通过添加`-XX:CMSInitiatingOccupancyFraction`并将其设置为默认值来启动早于默认值的 CMS。从 60％或 70％开始（降低阈值越低，GCing 越多，使用的 CPU 越多）。为了解决第二个碎片问题，Todd 添加了一个实验工具（MSLAB），必须在 Apache HBase 0.90.x 中明确启用（它在 Apache 0.92.x HBase 中默认为上的 _）。在`Configuration`中将`hbase.hregion.memstore.mslab.enabled`设置为 true。有关背景和细节，请参阅引用的幻灯片。最新的 JVM 更好地考虑碎片，因此请确保您运行的是最新版本。在消息中读入，[识别由碎片引起的并发模式故障](http://osdir.com/ml/hotspot-gc-use/2011-11/msg00002.html)。请注意，启用后，每个 MemStore 实例将至少占用一个 MSLAB 内存实例。如果您有数千个区域或许多区域，每个区域都有许多列族，那么 MSLAB 的这种分配可能会导致很大一部分堆分配，并且在极端情况下会导致您进入 OOME。在这种情况下禁用 MSLAB，或者降低它使用的内存量或每个服务器浮动更少的区域。_

如果您的工作量很大，请查看 [HBASE-8163 MemStoreChunkPool：使用 MSLAB](https://issues.apache.org/jira/browse/HBASE-8163) 时对 JAVA GC 的改进。它描述了在写入负载期间降低年轻 GC 数量的配置。如果您没有安装 HBASE-8163，并且您正在尝试改善年轻的 GC 时间，那么我们需要考虑的一个诀窍 - 我们的梁燮 - 是在 _hbase-env.sh 中设置 GC 配置`-XX:PretenureSizeThreshold`_ 只是小于`hbase.hregion.memstore.mslab.chunksize`的大小所以 MSLAB 分配直接发生在终身空间而不是年轻时代。你这样做是因为这些 MSLAB 分配很可能无论如何都要用于旧版本，而不是在 eden 空间中支付 s0 和 s1 之间的拷贝价格，然后在 MSLABs 之后从年轻到旧代复制。取得了足够的任期，节省了一点 YGC 流失并直接分配到旧版。

长 GC 的其他来源可以是 JVM 本身的日志记录。请参阅[消除由后台 IO 流量引起的大型 JVM GC 暂停](https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic)

有关 GC 日志的更多信息，请参阅 [JVM 垃圾收集日志](#trouble.log.gc)。

还要考虑启用堆外块缓存。这已被证明可以缓解 GC 暂停时间。见 [Block Cache](#block.cache)

## 117\. HBase 配置

参见[推荐配置](#recommended_configurations)。

### 117.1。改善第 99 百分位数

尝试链接：[hedged_reads]。

### 117.2。管理压缩

对于较大的系统，管理链接：[compactions 和 splits]可能是您想要考虑的事情。

### 117.3。 `hbase.regionserver.handler.count`

见 [[hbase.regionserver.handler.count]](#hbase.regionserver.handler.count) 。

### 117.4。 `hfile.block.cache.size`

见 [[hfile.block.cache.size]](#hfile.block.cache.size) 。 RegionServer 进程的内存设置。

### 117.5。 Blockcache 的预取选项

[HBASE-9857](https://issues.apache.org/jira/browse/HBASE-9857) 添加了一个新选项，用于在打开 BlockCache 时预取 HFile 内容，如果设置了 Column 系列或 RegionServer 属性。此选项适用于 HBase 0.98.3 及更高版本。目的是在打开缓存后，使用内存中的表数据尽可能快地加热 BlockCache，而不将预取计为缓存未命中。这对于快速读取非常有用，但如果要预加载的数据不适合 BlockCache，则不是一个好主意。它可用于调整预取的 IO 影响与所有数据块在缓存中的时间之间的关系。

要在给定列族上启用预取，可以使用 HBase Shell 或使用 API​​。

使用 HBase Shell 启用预取

```
hbase> create 'MyTable', { NAME => 'myCF', PREFETCH_BLOCKS_ON_OPEN => 'true' } 
```

示例 38.使用 API​​启用预取

```
// ...
HTableDescriptor tableDesc = new HTableDescriptor("myTable");
HColumnDescriptor cfDesc = new HColumnDescriptor("myCF");
cfDesc.setPrefetchBlocksOnOpen(true);
tableDesc.addFamily(cfDesc);
// ... 
```

请参阅 [CacheConfig](https://hbase.apache.org/devapidocs/org/apache/hadoop/hbase/io/hfile/CacheConfig.html) 的 API 文档。

要查看运行中的预取，请在 hbase-2.0 +中的`org.apache.hadoop.hbase.io.hfile.HFileReaderImpl`或 HBase 的早期版本 hbase-1.x 中的`org.apache.hadoop.hbase.io.hfile.HFileReaderV2`上启用 TRACE 级别日志记录。

### 117.6。 `hbase.regionserver.global.memstore.size`

见 [[hbase.regionserver.global.memstore.size]](#hbase.regionserver.global.memstore.size) 。通常根据需要为 RegionServer 进程调整此内存设置。

### 117.7。 `hbase.regionserver.global.memstore.size.lower.limit`

见 [[hbase.regionserver.global.memstore.size.lower.limit]](#hbase.regionserver.global.memstore.size.lower.limit) 。通常根据需要为 RegionServer 进程调整此内存设置。

### 117.8。 `hbase.hstore.blockingStoreFiles`

参见 [[hbase.hstore.blockingStoreFiles]](#hbase.hstore.blockingStoreFiles) 。如果 RegionServer 日志中存在阻塞，则增加此功能会有所帮助。

### 117.9。 `hbase.hregion.memstore.block.multiplier`

参见 [[hbase.hregion.memstore.block.multiplier]](#hbase.hregion.memstore.block.multiplier) 。如果有足够的 RAM，增加这个可以帮助。

### 117.10。 `hbase.regionserver.checksum.verify`

让 HBase 将校验和写入数据块，并保存在您阅读时必须进行校验和搜索。

见 [[hbase.regionserver.checksum.verify]](#hbase.regionserver.checksum.verify) ， [[hbase.hstore.bytes.per.checksum]](#hbase.hstore.bytes.per.checksum) 和 [[hbase.hstore.checksum.algorithm]](#hbase.hstore.checksum.algorithm) 。有关更多信息，请参阅 HBase 块缓存中 [HBASE-5074 支持校验和的发行说明。](https://issues.apache.org/jira/browse/HBASE-5074)

### 117.11。调整`callQueue`选项

[HBASE-11355](https://issues.apache.org/jira/browse/HBASE-11355) 引入了几种可以提高性能的 callQueue 调整机制。有关基准信息，请参阅 JIRA。

要增加呼叫队列的数量，请将`hbase.ipc.server.num.callqueue`设置为大于`1`的值。要将 callqueue 拆分为单独的读写队列，请将`hbase.ipc.server.callqueue.read.ratio`设置为`0`和`1`之间的值。此因子将队列加权写入（如果低于.5）或读取（如果高于.5）。另一种说法是，该因子决定了拆分队列中有多少百分比用于读取。以下示例说明了一些可能性。请注意，无论您使用何种设置，始终至少有一个写入队列。

*   `0`的默认值不会拆分队列。

*   值`.3`使用 30％的队列进行读取，60％用于写入。给定`hbase.ipc.server.num.callqueue`的`10`值，3 个队列将用于读取，7 个用于写入。

*   值`.5`使用相同数量的读取队列和写入队列。给定`hbase.ipc.server.num.callqueue`的`10`值，5 个队列将用于读取，5 个用于写入。

*   值`.6`使用 60％的队列进行阅读，30％进行阅读。给定`hbase.ipc.server.num.callqueue`的`10`值，7 个队列将用于读取，3 个用于写入。

*   值`1.0`使用一个队列来处理写请求，所有其他队列处理读请求。高于`1.0`的值与`1.0`的值具有相同的效果。给定`hbase.ipc.server.num.callqueue`的`10`值，9 个队列将用于读取，1 个用于写入。

您还可以拆分读取队列，以便通过设置`hbase.ipc.server.callqueue.scan.ratio`选项将单独的队列用于短读取（来自 Get 操作）和长读取（来自扫描操作）。此选项是介于 0 和 1 之间的因子，它决定了用于获取和扫描的读取队列的比率。如果值低于`.5`，则将更多队列用于获取;如果值高于`.5`，则将更多队列用于扫描。无论您使用何种设置，至少有一个读取队列用于 Get 操作。

*   值`0`不会拆分读取队列。

*   值`.3`使用 60％的读取队列获取，30％用于扫描。给定`hbase.ipc.server.num.callqueue`的`20`值和`hbase.ipc.server.callqueue.read.ratio`的`.5`值，将有 10 个队列用于读取，其中 10 个，7 个用于获取，3 个用于扫描。

*   值`.5`使用获取的一半读取队列和扫描的一半。给定`hbase.ipc.server.num.callqueue`的`20`值和`hbase.ipc.server.callqueue.read.ratio`的`.5`值，将有 10 个队列用于读取，其中 10 个用于获取，5 个用于扫描。

*   值`.6`使用 30％的读取队列获取，60％用于扫描。给定`hbase.ipc.server.num.callqueue`的`20`值和`hbase.ipc.server.callqueue.read.ratio`的`.5`值，将有 10 个队列用于读取，其中 10 个用于获取，3 个用于扫描。

*   值`1.0`使用除扫描队列之外的所有读取队列。给定`hbase.ipc.server.num.callqueue`的`20`值和`hbase.ipc.server.callqueue.read.ratio`的`.5`值，将有 10 个队列用于读取，其中 10 个用于获取，1 个用于扫描。

您可以使用新选项`hbase.ipc.server.callqueue.handler.factor`以编程方式调整队列数：

*   值`0`在所有处理程序之间使用单个共享队列。

*   值`1`为每个处理程序使用单独的队列。

*   `0`和`1`之间的值会根据处理程序的数量调整队列数。例如，`.5`的值在每两个处理程序之间共享一个队列。

    拥有更多队列（例如，在每个处理程序中有一个队列的情况下）可以在将任务添加到队列或从队列中选择任务时减少争用。权衡的是，如果你有一些长时间运行任务的队列，处理程序可能最终等待从该队列执行而不是处理另一个具有等待任务的队列。

要使这些值在给定的 RegionServer 上生效，必须重新启动 RegionServer。这些参数仅用于测试目的，应谨慎使用。

## 118\. ZooKeeper

有关配置 ZooKeeper 的信息，请参阅 [ZooKeeper](#zookeeper) ，并参阅有关具有专用磁盘的部分。

## 119.架构设计

### 119.1。列族数

参见[关于列族数](#number.of.cfs)。

### 119.2。键和属性长度

请参阅[尝试最小化行和列大小](#keysize)。另见[但是......](#perf.compression.however) 用于压缩警告。

### 119.3。表 RegionSize

在某些表需要与配置的默认区域大小不同的区域大小的情况下，可以通过 [HTableDescriptor](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HTableDescriptor.html) 上的`setFileSize`在每个表的基础上设置区域大小。

有关详细信息，请参阅[确定区域计数和大小](#ops.capacity.regions)。

### 119.4。布隆过滤器

以其创建者 Burton Howard Bloom 命名的 Bloom 过滤器是一种数据结构，旨在预测给定元素是否是一组数据的成员。 Bloom 过滤器的阳性结果并不总是准确的，但保证阴性结果是准确的。布隆过滤器被设计成对于数据集“足够准确”，这些数据集非常大以至于传统的散列机制是不切实际的。有关 Bloom 过滤器的更多信息，请参阅 [http://en.wikipedia.org/wiki/Bloom_filter](http://en.wikipedia.org/wiki/Bloom_filter) 。

就 HBase 而言，Bloom 过滤器提供了一个轻量级的内存结构，以便将给定 Get 操作（Bloom 过滤器不能与 Scans 一起使用）的磁盘读取次数减少到仅可能包含所需 Row 的 StoreFiles。潜在的性能增益随着并行读取的数量而增加。

Bloom 过滤器本身存储在每个 HFile 的元数据中，永远不需要更新。当因为区域部署到 RegionServer 而打开 HFile 时，Bloom 过滤器将加载到内存中。

HBase 包括一些调整机制，用于折叠 Bloom 过滤器以减小大小并将误报率保持在所需范围内。

在 [HBASE-1200](https://issues.apache.org/jira/browse/HBASE-1200) 中引入了 Bloom 过滤器。自 HBase 0.96 起，默认情况下启用基于行的 Bloom 过滤器。 （ [HBASE-8450](https://issues.apache.org/jira/browse/HBASE-8450) ）

有关与 HBase 相关的布隆过滤器的更多信息，请参阅[布隆过滤器](#blooms)以获取更多信息，或参考以下 Quora 讨论：[如何在 HBase 中使用布隆过滤器？](http://www.quora.com/How-are-bloom-filters-used-in-HBase) 。

#### 119.4.1。何时使用布隆过滤器

自 HBase 0.96 起，默认情况下启用基于行的 Bloom 过滤器。您可以选择禁用它们或更改某些表以使用行+列 Bloom 过滤器，具体取决于数据的特征以及如何将其加载到 HBase 中。

要确定 Bloom 过滤器是否会产生积极影响，请检查 RegionServer 度量标准中的`blockCacheHitRatio`值。如果启用了布隆过滤器，则`blockCacheHitRatio`的值应该增加，因为布隆过滤器正在过滤掉绝对不需要的块。

您可以选择为行或行+列组合启用 Bloom 过滤器。如果您通常扫描整行，则行+列组合不会提供任何好处。基于行的 Bloom 过滤器可以在行+列 Get 上运行，但不能相反。但是，如果您有大量的列级 Puts，这样每个 StoreFile 中可能存在一行，则基于行的过滤器将始终返回正结果并且不会带来任何好处。除非每行有一列，否则行+列布隆过滤器需要更多空间，以便存储更多键。当每个数据条目的大小至少为几千字节时，Bloom 过滤器最有效。

当您的数据存储在几个较大的 StoreFiles 中时，开销将减少，以避免在低级扫描期间额外的磁盘 IO 以查找特定行。

Bloom 过滤器需要在删除时重建，因此可能不适合具有大量删除的环境。

#### 119.4.2。启用布隆过滤器

在列族上启用布隆过滤器。您可以使用 HColumnDescriptor 的 setBloomFilterType 方法或使用 HBase API 来完成此操作。有效值为`NONE`，`ROW`（默认值）或`ROWCOL`。有关`ROW`与`ROWCOL`的更多信息，请参见[何时使用布隆过滤器](#bloom.filters.when)。另请参阅 [HColumnDescriptor](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html) 的 API 文档。

以下示例创建一个表，并在`colfam1`列族上启用 ROWCOL Bloom 过滤器。

```
hbase> create 'mytable',{NAME => 'colfam1', BLOOMFILTER => 'ROWCOL'} 
```

#### 119.4.3。配置 Bloom 过滤器的服务器范围行为

您可以在 _hbase-site.xml_ 中配置以下设置。

| 参数 | 默认 | 描述 |
| --- | --- | --- |
| io.storefile.bloom.enabled | 是 | 如果出现问题，设置为 no 以杀死服务器范围内的 bloom 过滤器 |
| io.storefile.bloom.error.rate | 0.01 | 布隆过滤器的平均误报率。折叠用于维持误报率。表示为百分比的十进制表示。 |
| io.storefile.bloom.max.fold | 7 | 保证最大折叠率。不需要更改此设置，不建议这样做。 |
| io.storefile.bloom.max.keys | 1.28 | 对于默认（单块）布隆过滤器，它指定最大键数。 |
| io.storefile.delete.family.bloom.enabled | 真正 | 主开关启用 Delete Family Bloom 过滤器并将其存储在 StoreFile 中。 |
| io.storefile.bloom.block.size | 131072 | 目标 Bloom 块大小。大约这个大小的布隆过滤器块与数据块交织。 |
| hfile.block.bloom.cacheonwrite | 假 | 为复合布隆过滤器的内联块启用写入高速缓存。 |

### 119.5。 ColumnFamily BlockSize

可以为表中的每个 ColumnFamily 配置 blocksize，默认为 64k。较大的单元值需要较大的块大小。 blocksize 与生成的 StoreFile 索引之间存在反比关系（即，如果块大小加倍，则结果索引应大致减半）。

有关详细信息，请参阅 [HColumnDescriptor](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html) 和 [Store](#store) 。

### 119.6。内存列家庭

ColumnFamilies 可以选择定义为内存中。数据仍然保留在磁盘上，就像任何其他 ColumnFamily 一样。内存块在[块缓存](#block.cache)中具有最高优先级，但不保证整个表将在内存中。

有关详细信息，请参阅 [HColumnDescriptor](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html) 。

### 119.7。压缩

生产系统应使用其 ColumnFamily 定义进行压缩。有关详细信息，请参阅 HBase 中的[压缩和数据块编码。](#compression)

#### 119.7.1。然而...

压缩会压缩磁盘上的数据 _。当它在内存中（例如，在 MemStore 中）或在线上（例如，在 RegionServer 和 Client 之间传输）时，它会膨胀。因此，虽然使用 ColumnFamily 压缩是最佳做法，但它不会完全消除过大的 Keys，超大的 ColumnFamily 名称或超大的列名称的影响。_

请参阅[尝试最小化模式设计技巧的行和列大小](#keysize)，以及 [KeyValue](#keyvalue) 以获取有关 HBase 内部存储数据的更多信息。

## 120\. HBase 一般模式

### 120.1。常量

当人们开始使用 HBase 时，他们倾向于编写如下代码：

```
Get get = new Get(rowkey);
Result r = table.get(get);
byte[] b = r.getValue(Bytes.toBytes("cf"), Bytes.toBytes("attr"));  // returns current version of value 
```

但特别是当内部循环（和 MapReduce 作业）时，将 columnFamily 和 column-names 重复转换为字节数组的成本非常高。最好对字节数组使用常量，如下所示：

```
public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...
Get get = new Get(rowkey);
Result r = table.get(get);
byte[] b = r.getValue(CF, ATTR);  // returns current version of value 
```

## 121.写给 HBase

### 121.1。批量加载

如果可以，请使用批量加载工具。参见[批量加载](#arch.bulk.load)。否则，请注意以下内容。

### 121.2。表创建：预创建区域

默认情况下，HBase 中的表最初是使用一个区域创建的。对于批量导入，这意味着所有客户端都将写入同一区域，直到它足够大，可以拆分并在集群中分布。加速批量导入过程的有用模式是预先创建空白区域。在这方面要保守一些，因为太多的地区实际上会降低性能。

使用 HBase API 预创建拆分有两种不同的方法。第一种方法是依赖默认的`Admin`策略（在`Bytes.split`中实现）...

```
byte[] startKey = ...;      // your lowest key
byte[] endKey = ...;        // your highest key
int numberOfRegions = ...;  // # of regions to create
admin.createTable(table, startKey, endKey, numberOfRegions); 
```

另一种使用 HBase API 的方法是自己定义分割...

```
byte[][] splits = ...;   // create your own splits
admin.createTable(table, splits); 
```

使用 HBase Shell 可以通过指定拆分选项来创建表，从而实现类似的效果。

```
# create table with specific split points
hbase>create 't1','f1',SPLITS => ['\x10\x00', '\x20\x00', '\x30\x00', '\x40\x00']

# create table with four regions based on random bytes keys
hbase>create 't2','f1', { NUMREGIONS => 4 , SPLITALGO => 'UniformSplit' }

# create table with five regions based on hex keys
create 't3','f1', { NUMREGIONS => 5, SPLITALGO => 'HexStringSplit' } 
```

有关理解键空间和预创建区域的相关问题，请参阅 [RowKeys 和 Region Splits](#rowkey.regionsplits) 之间的关系。有关手动预分割区域的讨论，请参见[手动区域分割决策](#manual_region_splitting_decisions)。有关使用 HBase Shell 预分割表的更多详细信息，请参见[使用 HBase Shell](#tricks.pre-split) 预分割表。

### 121.3。表创建：延迟日志刷新

使用预写日志（WAL）的 Puts 的默认行为是将立即写入`WAL`编辑。如果使用延迟日志刷新，则 WAL 编辑将保留在内存中直到刷新周期。好处是聚合和异步`WAL` - 写入，但潜在的缺点是如果 RegionServer 发生故障，则尚未刷新的编辑将丢失。然而，这比使用 Puts 完全不使用 WAL 更安全。

可以通过 [HTableDescriptor](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HTableDescriptor.html) 在表上配置延迟日志刷新。 `hbase.regionserver.optionallogflushinterval`的默认值为 1000ms。

### 121.4。 HBase 客户端：在 Puts 上关闭 WAL

经常请求禁用 WAL 以提高 Puts 的性能。这仅适用于批量加载，因为它会在区域服务器崩溃时通过删除 WAL 的保护来使您的数据处于危险之中。在发生崩溃时可以重新运行批量加载，几乎没有数据丢失的风险。

> 如果禁用 WAL 而不是批量加载，则数据存在风险。

通常，最好使用 WAL for Puts，而使用批量加载技术时，加载吞吐量是一个问题。对于正常的 Puts，您不太可能看到性能改善超过风险。要禁用 WAL，请参阅[禁用 WAL](#wal.disable) 。

### 121.5。 HBase 客户端：GroupServer 放置的组

除了使用 writeBuffer 之外，通过 RegionServer 对`Put`进行分组可以减少每个 writeBuffer flush 的客户端 RPC 调用次数。当前在 MASTER 上有一个实用程序`HTableUtil`可以执行此操作，但您可以复制该实用程序，也可以为仍在 0.90.x 或更早版本的版本实现自己的版本。

### 121.6。 MapReduce：跳过 Reducer

当从 MR 作业向 HBase 表写入大量数据时（例如，使用 [TableOutputFormat](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/mapreduce/TableOutputFormat.html) ），特别是从 Mapper 发出 Puts 的地方，跳过 Reducer 步骤。当使用 Reducer 步骤时，来自 Mapper 的所有输出（Puts）将被假脱机到磁盘，然后排序/改组到其他很可能是非节点的 Reducers。直接写入 HBase 效率更高。

对于将 HBase 用作源和接收器的摘要作业，写入将来自 Reducer 步骤（例如，汇总值然后写出结果）。这是与上述情况不同的处理问题。

### 121.7。反模式：一个热点区域

如果所有数据一次写入一个区域，则重新阅读有关处理时间序列数据的部分。

此外，如果您正在预分割区域并且所有数据仍然是 _ 仍然 _ 在单个区域中清理，即使您的密钥没有单调增加，请确认您的密钥空间实际上与分离策略一起使用。区域可能出现“分裂”的原因有多种，但不适用于您的数据。由于 HBase 客户端直接与 RegionServers 通信，因此可以通过 [RegionLocator.getRegionLocation](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/RegionLocator.html#getRegionLocation-byte:A-) 获得。

参见[表创建：预创建区域](#precreate.regions)，以及 [HBase 配置](#perf.configurations)

## 122.从 HBase 读

如果您遇到性能问题，邮件列表可以提供帮助。例如，这里是关于解决读取时间问题的一个很好的通用线程： [HBase 随机读取延迟＆gt; 100 毫秒](http://search-hadoop.com/m/qOo2yyHtCC1)

### 122.1。扫描缓存

例如，如果将 HBase 用作 MapReduce 作业的输入源，请确保 MapReduce 作业的输入 [Scan](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Scan.html) 实例将`setCaching`设置为大于默认值（即 1）的值。使用默认值意味着 map-task 将为处理的每个记录回调 region-server。例如，将此值设置为 500 将一次传输 500 行到要处理的客户端。缓存值很大是有成本/收益的，因为客户端和 RegionServer 的内存成本更高，因此更大并不总是更好。

#### 122.1.1。在 MapReduce 作业中扫描缓存

MapReduce 作业中的扫描设置值得特别注意。如果在客户端返回到 RegionServer 以获取下一组数据之前处理一批记录需要更长时间，则可能会在 Map 任务中导致超时（例如，UnknownScannerException）。发生此问题的原因是每行发生非平凡处理。如果快速处理行，请将缓存设置得更高。如果您更慢地处理行（例如，每行进行大量转换，写入），则将缓存设置得更低。

超时也可能发生在非 MapReduce 用例（即执行扫描的单线程 HBase 客户端）中，但通常在 MapReduce 作业中执行的处理往往会加剧此问题。

### 122.2。扫描属性选择

每当使用扫描处理大量行（特别是用作 MapReduce 源）时，请注意选择了哪些属性。如果调用`scan.addFamily`，则 _ 指定 ColumnFamily 中属性的所有 _ 将返回给客户端。如果只处理少量可用属性，则只应在输入扫描中指定那些属性，因为属性过度选择对大型数据集而言是一个非常重要的性能损失。

### 122.3。避免扫描寻求

当使用`scan.addColumn`显式选择列时，HBase 将调度搜索操作以在所选列之间进行搜索。当行包含很少的列而每列只有几个版本时，这可能效率很低。如果不寻求至少过去 5-10 列/版本或 512-1024 字节，则查找操作通常较慢。

为了机会性地向前看几个列/版本以查看在调度搜索操作之前是否可以找到下一列/版本，可以在 Scan 对象上设置新属性`Scan.HINT_LOOKAHEAD`。以下代码指示 RegionServer 在调度搜索之前尝试下一次迭代：

```
Scan scan = new Scan();
scan.addColumn(...);
scan.setAttribute(Scan.HINT_LOOKAHEAD, Bytes.toBytes(2));
table.getScanner(scan); 
```

### 122.4。 MapReduce - 输入拆分

对于使用 HBase 表作为源的 MapReduce 作业，如果存在“慢”映射任务似乎具有相同输入拆分的模式（即，为服务数据提供服务的 RegionServer），请参阅[案例研究中的故障排除案例研究＃1（单节点上的性能问题）](#casestudies.slownode)。

### 122.5。关闭 ResultScanners

这不是提高性能，而是 _ 避免 _ 性能问题。如果您忘记关闭 [ResultScanners](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/ResultScanner.html) ，可能会导致 RegionServers 出现问题。始终将 ResultScanner 处理包含在 try / catch 块中。

```
Scan scan = new Scan();
// set attrs...
ResultScanner rs = table.getScanner(scan);
try {
  for (Result r = rs.next(); r != null; r = rs.next()) {
  // process result...
} finally {
  rs.close();  // always close the ResultScanner!
}
table.close(); 
```

### 122.6。块缓存

[Scan](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Scan.html) 实例可以通过`setCacheBlocks`方法设置为使用 RegionServer 中的块缓存。对于输入扫描到 MapReduce 作业，这应该是`false`。对于频繁访问的行，建议使用块缓存。

通过在堆外移动块缓存来缓存更多数据。请参阅[堆外块缓存](#offheap.blockcache)

### 122.7。行密钥的最佳加载

执行仅需要行键的表[扫描](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Scan.html)时（无族，限定符，值或时间戳），使用`setFilter`将带有`MUST_PASS_ALL`运算符的 FilterList 添加到扫描仪。过滤器列表应包括 [FirstKeyOnlyFilter](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/filter/FirstKeyOnlyFilter.html) 和 [KeyOnlyFilter](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/filter/KeyOnlyFilter.html) 。使用此过滤器组合将导致 RegionServer 读取磁盘中的单个值以及单个行的客户端最小网络流量的最坏情况。

### 122.8。并发：监控数据传播

执行大量并发读取时，监视目标表的数据传播。如果目标表具有太少的区域，则可能从太少的节点提供读取。

See [Table Creation: Pre-Creating Regions](#precreate.regions), as well as [HBase Configurations](#perf.configurations)

### 122.9。布隆过滤器

启用 Bloom 过滤器可以节省您必须转到磁盘并有助于改善读取延迟。

[Bloom 过滤器](http://en.wikipedia.org/wiki/Bloom_filter)是在 [HBase-1200 Add bloomfilters](https://issues.apache.org/jira/browse/HBASE-1200) 中开发的。有关开发过程的描述 - 为什么是静态绽放而不是动态 - 以及有关 HBase 中的绽放的独特属性的概述，以及可能的未来方向，请参阅文档的 _ 开发过程 _ 部分 [HBase](https://issues.apache.org/jira/secure/attachment/12444007/Bloom_Filters_in_HBase.pdf) 中的 BloomFilters 附着于 [HBASE-1200](https://issues.apache.org/jira/browse/HBASE-1200) 。这里描述的布隆过滤器实际上是 HBase 中的第二版布隆。在高达 0.19.x 的版本中，HBase 根据[欧盟委员会一个实验室项目 034819](http://www.onelab.org) 所做的工作有一个动态绽放选项。 HBase bloom 工作的核心后来被引入 Hadoop 以实现 org.apache.hadoop.io.BloomMapFile。 HBase 绽放的第 1 版从未如此顺利。版本 2 是从头开始重写，但它再次从单实验室工作开始。

另请参见[布隆过滤器](#schema.bloom)。

#### 122.9.1。 Bloom StoreFile 足迹

布隆过滤器向`StoreFile`通用`FileInfo`数据结构添加条目，然后向`StoreFile`元数据部分添加两个额外条目。

##### `StoreFile``FileInfo`数据结构中的 BloomFilter

`FileInfo`有一个`BLOOM_FILTER_TYPE`条目，设置为`NONE`，`ROW`或`ROWCOL.`

##### `StoreFile`元数据中的 BloomFilter 条目

`BLOOM_FILTER_META`保存 Bloom Size，使用的哈希函数等。它的大小很小，并且在`StoreFile.Reader`加载时缓存

`BLOOM_FILTER_DATA`是实际的 bloomfilter 数据。按需获得。存储在 LRU 缓存中，如果已启用（默认情况下已启用）。

#### 122.9.2。布隆过滤器配置

##### `io.storefile.bloom.enabled`全局杀戮开关

如果出现问题，`Configuration`中的`io.storefile.bloom.enabled`将作为终止开关。默认= `true`。

##### `io.storefile.bloom.error.rate`

`io.storefile.bloom.error.rate` =平均误报率。默认值= 1％。每个布鲁德条目减少½（例如降至 0.5％）== +1 比特。

##### `io.storefile.bloom.max.fold`

`io.storefile.bloom.max.fold` =保证最低折叠率。大多数人应该单独留下这个。默认值= 7，或者可以折叠到原始大小的至少 1/128。有关此选项的含义的更多信息，请参阅 HBase 文档 [BloomFilters 的 _ 开发过程 _ 部分。](https://issues.apache.org/jira/secure/attachment/12444007/Bloom_Filters_in_HBase.pdf)

### 122.10。对冲读数

对冲读取是 HDFS 的一个特性，在 Hadoop 2.4.0 中引入了 [HDFS-5776](https://issues.apache.org/jira/browse/HDFS-5776) 。通常，为每个读取请求生成一个线程。但是，如果启用了对冲读取，则客户端会等待一些可配置的时间，如果读取未返回，则客户端会针对相同数据的不同块副本生成第二个读取请求。无论先使用哪个读取返回，都会丢弃另一个读取请求。

对冲读取是“......非常擅长消除异常数据节点，这反过来使它们成为延迟敏感设置的非常好的选择。但是，如果您正在寻找最大化吞吐量，对冲读取往往会产生负载放大，因为一般情况下变慢。简而言之，需要注意的是当您运行接近某个吞吐量阈值时，非优雅的性能下降。“ （引自 Ashu Pachauri 的 HBASE-17083）。

在启用对冲读取的情况下运行时要记住的其他问题包括：

*   它们可能导致网络拥塞。见 [HBASE-17083](https://issues.apache.org/jira/browse/HBASE-17083)

*   确保将线程池设置得足够大，以便池上的阻塞不会成为瓶颈（再次参见 [HBASE-17083](https://issues.apache.org/jira/browse/HBASE-17083) ）

（来自 HBASE-17083 的 Yu Li）

由于 HBase RegionServer 是 HDFS 客户端，因此您可以在 HBase 中启用对冲读取，方法是将以下属性添加到 R​​egionServer 的 hbase-site.xml 并调整值以适合您的环境。

对冲读数的配置

*   `dfs.client.hedged.read.threadpool.size` - 专用于服务对冲读取的线程数。如果将其设置为 0（默认值），则禁用对冲读取。

*   `dfs.client.hedged.read.threshold.millis` - 产生第二个读取线程之前等待的毫秒数。

例 39.对冲读取配置示例

```
<property>
  <name>dfs.client.hedged.read.threadpool.size</name>
  <value>20</value>  <!-- 20 threads -->
</property>
<property>
  <name>dfs.client.hedged.read.threshold.millis</name>
  <value>10</value>  <!-- 10 milliseconds -->
</property> 
```

使用以下指标调整群集上对冲读取的设置。有关详细信息，请参阅 [HBase 指标](#hbase_metrics)。

对冲读数的指标

*   hedgedReadOps - 已触发对冲读取线程的次数。这可能表明读取请求通常很慢，或者对冲读取的触发过快。

*   hedgeReadOpsWin - 对冲读取线程比原始线程快的次数。这可能表示给定的 RegionServer 在处理请求时遇到问题。

## 123.从 HBase 删除

### 123.1。使用 HBase 表作为队列

HBase 表有时用作队列。在这种情况下，必须特别注意定期对以这种方式使用的表格进行主要压缩。如[数据模型](#datamodel)中所述，将行标记为已删除会创建额外的 StoreFiles，然后需要在读取时对其进行处理。墓碑只能通过主要压缩来清理。

另见[压缩](#compaction)和 [Admin.majorCompact](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Admin.html#majorCompact-org.apache.hadoop.hbase.TableName-) 。

### 123.2。删除 RPC 行为

请注意`Table.delete(Delete)`不使用 writeBuffer。它将在每次调用时执行 RegionServer RPC。对于大量删除，请考虑`Table.delete(List)`。

见 [hbase.client.Delete](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html#delete-org.apache.hadoop.hbase.client.Delete-)

## 124\. HDFS

因为 HBase 在 [HDFS](#arch.hdfs) 上运行，所以了解它是如何工作以及它如何影响 HBase 是很重要的。

### 124.1。低延迟读取的当前问题

HDFS 的原始用例是批处理。因此，低延迟读取在历史上不是优先事项。随着 Apache HBase 的采用日益增多，这种情况正在发生变化，并且已经在开发中进行了一些改进。有关 HBase 的 HDFS 改进，请参阅 [Umbrella Jira Ticket。](https://issues.apache.org/jira/browse/HDFS-1599)

### 124.2。利用本地数据

由于 Hadoop 1.0.0（也是 0.22.1,0.23.1，CDH3u3 和 HDP 1.0）通过 [HDFS-2246](https://issues.apache.org/jira/browse/HDFS-2246) ，DFSClient 可以进行“短路”并直接从磁盘读取当数据是本地数据时，而不是通过 DataNode。对于 HBase 来说，这意味着 RegionServers 可以直接读取其机器的磁盘，而不必打开套接字与 DataNode 通信，前者通常要快得多。参见 JD 的 [Performance Talk](http://files.meetup.com/1350427/hug_ebay_jdcryans.pdf) 。另请参阅 [HBase，邮件#dev - 读取短路](http://search-hadoop.com/m/zV6dKrLCVh1)线程，以获得有关短路读取的更多讨论。

要启用“短路”读取，它将取决于您的 Hadoop 版本。在 [HDFS-347](https://issues.apache.org/jira/browse/HDFS-347) 中，Hadoop 2 中的原始短路读取补丁得到了很大改善。参见 [http://blog.cloudera.com/blog/2013/08/how-improved-short-circuit-local-reads-bring-better-performance-and-security-to-hadoop/](http://blog.cloudera.com/blog/2013/08/how-improved-short-circuit-local-reads-bring-better-performance-and-security-to-hadoop/) for 有关旧实现和新实现之间差异的详细信息。参见 [Hadoop 短路读取配置页面](http://archive.cloudera.com/cdh4/cdh/4/hadoop/hadoop-project-dist/hadoop-hdfs/ShortCircuitLocalReads.html)，了解如何启用后者，更好的短路版本。例如，这是一个最小配置。启用短路读取添加到 _hbase-site.xml_ ：

```
<property>
  <name>dfs.client.read.shortcircuit</name>
  <value>true</value>
  <description>
    This configuration parameter turns on short-circuit local reads.
  </description>
</property>
<property>
  <name>dfs.domain.socket.path</name>
  <value>/home/stack/sockets/short_circuit_read_socket_PORT</value>
  <description>
    Optional.  This is a path to a UNIX domain socket that will be used for
    communication between the DataNode and local HDFS clients.
    If the string "_PORT" is present in this path, it will be replaced by the
    TCP port of the DataNode.
  </description>
</property> 
```

注意托管共享域套接字的目录的权限;如果对 hbase 用户以外的其他人开放，dfsclient 会抱怨。

如果您运行的是旧的 Hadoop，没有 [HDFS-347](https://issues.apache.org/jira/browse/HDFS-347) ，但有 [HDFS-2246](https://issues.apache.org/jira/browse/HDFS-2246) ，则必须设置两个配置。首先，需要修改 hdfs-site.xml。将属性`dfs.block.local-path-access.user`设置为可以使用快捷方式的 _ 仅 _ 用户。这必须是启动 HBase 的用户。然后在 hbase-site.xml 中，将`dfs.client.read.shortcircuit`设置为`true`

服务 - 至少是 HBase RegionServers - 需要重新启动才能获取新配置。

> dfs.client.read.shortcircuit.buffer.size
> 
> 在高流量的 HBase 上运行时，此值的默认值太高。在 HBase 中，如果尚未设置此值，我们将其从默认值 1M 设置为 128k（自 HBase 0.98.0 和 0.96.1 起）。请参阅 Hadoop 2 上的 [HBASE-8143 HBase，本地短路读取（ssr）会导致 OOM](https://issues.apache.org/jira/browse/HBASE-8143) 。 HBase 中的 Hadoop DFSClient 将为 _ 分配一个这个大小的直接字节缓冲区，每个 _ 块已打开;鉴于 HBase 始终保持其 HDFS 文件打开，这可以快速加起来。

### 124.3。 HBase 与 HDFS 的性能比较

关于 dist-list 的一个相当常见的问题是为什么 HBase 不如批处理上下文中的 HDFS 文件那样高效（例如，作为 MapReduce 源或接收器）。简短的回答是 HBase 比 HDFS 做得更多（例如，读取 KeyValues，返回最新的行或指定的时间戳等），因此 HBase 在此处理环境中比 HDFS 慢 4-5 倍。还有改进的余地，随着时间的推移，这个差距会缩小，但在这个用例中 HDFS 总是会更快。

## 125.亚马逊 EC2

性能问题在 Amazon EC2 环境中很常见，因为它是一个共享环境。您将看不到与专用服务器相同的吞吐量。在 EC2 上运行测试时，出于同样的原因多次运行它们（即，它是共享环境，您不知道服务器上还发生了什么）。

如果您在 EC2 上运行并在 dist-list 上发布性能问题，请事先说明这一事实，因为 EC2 问题实际上是一类独立的性能问题。

## 126.配置 HBase 和 MapReduce

通常建议为 HBase 和 MapReduce 使用不同的集群。对此更好的限定是：不要将提供实时请求的 HBase 与重 MR 工作负载并置。 OLTP 和 OLAP 优化的系统具有相互冲突的要求，而另一个将失去另一个，通常是前者。例如，短暂的延迟敏感磁盘读取将不得不排在较长的读取后面，这些读取试图挤出尽可能多的吞吐量。写入 HBase 的 MR 作业也会产生刷新和压缩，这反过来会使 [Block Cache](#block.cache) 中的块无效。

如果需要处理 MR 中的实时 HBase 集群中的数据，可以使用 [CopyTable](#copy.table) 发送增量，或使用复制在 OLAP 集群上实时获取新数据。在最坏的情况下，如果您确实需要并置两者，请将 MR 设置为使用比您通常配置的更少的 Map 和 Reduce 插槽，可能只需一个。

当 HBase 用于 OLAP 操作时，最好以强化的方式设置它，例如将 ZooKeeper 会话超时配置得更高并为 MemStores 提供更多内存（因为工作负载是因为块缓存不会被使用很多通常很长的扫描）。

## 127.案例研究

有关性能和故障排除案例研究，请参阅 [Apache HBase 案例研究](#casestudies)。