亚马逊AWS官方博客

基于 IoT 数据平台案例看 EMR HBase GC 优化

在当今数字化时代,物联网(IoT)设备的普及使得数据的产生与处理变得愈加复杂。我们正在运营一个全球服务的 IoT 数据平台,面临着众多挑战。在这个平台上,数百万设备持续不断地上报数据,写入并发量高达 12,000 次每秒。为了满足实时读取的需求,我们希望保持与本地 HDFS 性能一致,同时分时任务能够异步返回。

系统配置与性能要求

基于用户之前自建集群的规模,我们配置了以下 EMR 资源:

  • Master 节点:m6g.4xlarge * 3
  • Core 节点:r6g.4xlarge * 20
  • BucketCache:每个节点配置 1.2TB 缓存空间,共 24TB,以满足用户对低延迟读取性能的要求。

在前期的 YCSB 压测中,相同配置的 HBase 性能表现良好。然而,当我们进入实际业务测试阶段时,Grafana 仪表盘上出现了 GC 时间超过 1 分钟的报警,表明系统遭遇了垃圾回收(GC)性能瓶颈。这不仅导致了 Kafka 写入积压,还使得读取端超时,从而开启了一段漫长的调优之路。

理解 GC 时间长的含义

Java 垃圾回收(GC)是 Java 虚拟机(JVM)管理内存的重要机制。HBase 作为一种分布式列存储数据库,在大数据领域被广泛应用。随着数据量的增加,HBase 集群的内存使用量也随之上升,导致垃圾回收变得频繁且耗时。

当 HBase 集群内存不足时,JVM 会频繁触发垃圾回收,以清理不再使用的对象。如果内存中的活跃对象数量较多,GC 需要花费更多时间来处理这些对象,从而导致 GC 时间延长。这种频繁且长时间的 GC 停顿会直接影响客户端请求的响应时间,从而影响整体集群性能。因此,GC 时间过长通常是集群内存不足和资源争抢的一种表现。

分析 HBase 内存分布

为了更好地理解当前集群内存状况,我们需要查看 HBase 的内存分布情况:

  • JVM 内存:主要分配给 MemStore 和 BlockCache。
    • MemStore:用于暂时存储写入 HBase 的数据,直到达到阈值后触发 Flush 操作,将数据写入 HDFS。
    • BlockCache:用于缓存读取的数据块,为堆内缓存。
  • Java Direct Memory:大部分用于 BucketCache,用于缓存数据块,位于堆外。当 HBase 使用 Amazon S3 作为底层存储时,可以通过预热 BucketCache 来提升读取性能。BucketCache 本身是 L2 缓存,其在内存中占用的空间主要用于数据块元信息管理。随着 BucketCache 大小的增加,内存占用也呈正比上升。

调优策略

针对以上 HBase 的内存分布理解,以下是团队针对 HBase 集群性能优化的具体调优策略及其实施过程:

1. 打印 GC 日志

为了有效分析和优化 HBase 集群的垃圾回收(GC)性能,首先需要启用详细的 GC 日志记录。这一过程涉及修改集群参数,以确保能够记录每次 GC 事件的详细信息。具体的参数设置包括:

```bash
-verbose:gc 
-XX:+PrintGCDetails 
-XX:+PrintGCDateStamps 
-Xloggc:/var/log/hbase/gclog-%t.txt 
-XX:+UseGCLogFileRotation 
-XX:NumberOfGCLogFiles=100 
-XX:GCLogFileSize=1024M
```

这些参数的作用在于记录每次 GC 的详细信息、时间戳和触发原因,同时启用日志文件轮换功能,以避免单个日志文件过大。通过分析这些 GC 日志,我们可以识别出内存使用模式、GC 停顿时间及其他潜在的性能瓶颈,从而为后续的优化提供数据支持。

2. 调整 MemStore 配置

为了提升 HBase 的写入性能并减少 flush 操作的频率,调整了与 MemStore 相关的一些关键参数。首先,将 `hbase.hregion.memstore.flush.size` 从默认值 128 MB 提升至 256 MB,这样可以有效减少 flush 操作的发生频率,从而提高写入性能。然而,这一调整也意味着会占用更多的堆内存。

其次,将 `hbase.regionserver.global.memstore.size` 从 0.4 提升至 0.7,这样在内存总量不变的情况下,可以确保 MemStore 拥有足够的空间来处理高并发的写入请求。通过这两项调整,HBase 集群在面对大量数据写入时,能够显著提升性能并减少潜在的延迟。

3. 优化 GC 参数

针对 GC 性能问题,进行了多项关键参数的优化。首先,将 `InitiatingHeapOccupancyPercent` 从 60% 下调至 45%,使得垃圾回收能够更早触发,从而避免内存占用过高导致的性能下降。此外,将 `ParallelGCThreads` 从 16 提升至 24,以增加并行 GC 的线程数,加快垃圾回收速度。

在新生代比例方面,将 `-XX:NewRatio` 从 35 改为 2,增加年轻代在堆内存中的占比,使得系统能够分配更多的内存给年轻代,减少对象晋升到老年代的频率,从而降低 Full GC 的发生率。

使用 G1 垃圾回收器(`-XX:+UseG1GC`),设置最大暂停时间目标为 100ms(`-XX:MaxGCPauseMillis=100`),优化 GC 的停顿时间。

通过这些调整,可以有效减少对象晋升到老年代的频率,从而降低 Full GC 的发生率,提升系统整体性能。

4. Cache 调优

缓存配置对 GC 性能有直接影响。在监测到缓存占用过多内存后,进行了以下调整:将 `hfile.block.cache.size` 从 0.2 降低到 0.1,以减少 BlockCache 对堆内存的占用。同时,将 `hbase.bucketcache.size` 从 600 GB 降低到 500 GB,以减少 BucketCache 对堆外内存的占用,但仍保留较大的缓存空间以优化读取性能。

此外,我们去除了 `-XX:MaxDirectMemorySize=48g` 的设置。原先设置了堆外内存上限为 48 GB,而取消该参数后,JVM 默认将 Direct Memory 设置为堆内存大小的 40%(即约 19.2 GB,当 Xmx=31G 时)。这一调整使得 JVM 可以自适应管理 Direct Memory,从而简化了配置并减少了潜在的问题。

5. 集群配置变更

在参考其他成功案例和咨询支持团队后,我们对 HBase 集群配置也进行了更新。根据类似场景案例,增加了节点数量并缩小了单节点规格,目标是减少单节点内存负载压力,通过更多节点分担工作量,从而缓解单节点 JVM 堆内存和垃圾回收压力。

总结

面对 GC 时间过长的问题,我们调优措施包括:

  • 优化内存配置:评估当前内存配置是否满足实际需求,并考虑增加 JVM 和 Direct Memory 的分配。
  • 优化 BucketCache 配置:缩小 BucketCache 配置与最大堆外内存占用,留出更多内存给 JVM。
  • 监控与日志:结合普罗米修斯,grafana 与日志实时跟踪 GC 情况及内存使用情况,发现问题及时调整。

通过这些措施,我们有效降低了 GC 时间(最终时间稳定在 10 秒之下),提高系统整体性能,从而更好地支持百万设备的数据上报与处理需求。我们为基于托管 EMR HBase IoT 数据平台打下了坚实的基础,使其能够高效、稳定地运行,为全球终端用户提供优质稳定的服务。

本篇作者

赵安蓓

亚马逊云科技解决方案架构师,负责基于 AWS 云平台的解决方案咨询和设计,机器学习 TFC 成员。在数据处理与建模领域有着丰富的实践经验,特别关注医疗领域的机器学习工程化与运用。