jvm部分

jvm 部分包括了运行 Elasticsearch 的 JVM 进程一些很关键的信息。最重要的,它包括了垃圾回收的细节,这对你的 Elasticsearch 集群的稳定性有着重大影响。

垃圾回收入门

在我们描述统计值之前,先上一门速成课程讲解垃圾回收以及它对 Elasticsearch 的影响是非常有用的。如果你对 JVM 的垃圾回收很熟悉,请跳过这段。

Java 是一门 垃圾回收 语言,也就是说程序员不用手动管理内存分配和回收。程序员只管写代码,然后 Java 虚拟机(JVM)按需分配内存,然后在稍后不再需要的时候清理这部分内存。

当内存分配给一个 JVM 进程,它是分配到一个大块里,这个块叫做 堆 。JVM 把堆分成两组,用 代 来表示

新生代(或者伊甸园)

新实例化的对象分配的空间。新生代空间通常都非常小,一般在 100 MB–500 MB。新生代也包含两个 幸存 空间。

老生代

较老的对象存储的空间。这些对象预计将长期留存并持续上很长一段时间。老生代通常比新生代大很多。Elasticsearch 节点可以给老生代用到 30 GB。

当一个对象实例化的时候,它被放在新生代里。当新生代空间满了,就会发生一次新生代垃圾回收(GC)。依然是"存活"状态的对象就被转移到一个幸存区内,而"死掉"的对象被移除。如果一个对象在多次新生代 GC 中都幸存了,它就会被"终身"置于老生代了。

类似的过程在老生代里同样发生:空间满的时候,发生一次垃圾回收,死掉的对象被移除。

不过,天下没有免费的午餐。新生代、老生代的垃圾回收都有一个阶段会“停止时间”。在这段时间里,JVM 字面意义上的停止了程序运行,以便跟踪对象图,收集死亡对象。在这个时间停止阶段,一切都不会发生。请求不被服务,ping 不被回应,分片不被分配。整个世界都真的停止了。

对于新生代,这不是什么大问题;那么小的空间意味着 GC 会很快执行完。但是老生代大很多,而这里面一个慢 GC 可能就意味着 1 秒乃至 15 秒的暂停——对于服务器软件来说这是不可接受的。

JVM 的垃圾回收采用了 非常 精密的算法,在减少暂停方面做得很棒。而且 Elasticsearch 非常努力的变成对 垃圾回收友好 的程序,比如内部智能的重用对象,重用网络缓冲,以及默认启用 Doc Values 功能。但最终,GC 的频率和时长依然是你需要去观察的指标。因为它是集群不稳定的头号嫌疑人。

一个经常发生长 GC 的集群就会因为内存不足而处于高负载压力下。这些长 GC 会导致节点短时间内从集群里掉线。这种不稳定会导致分片频繁重定位,因为 Elasticsearch 会尝试保持集群均衡,保证有足够的副本在线。这接着就导致网络流量和磁盘 I/O 的增加。而所有这些都是在你的集群努力服务于正常的索引和查询的同时发生的。

总而言之,长时间 GC 总是不好的,需要尽可能的减少。

因为垃圾回收对 Elasticsearch 是如此重要,你应该非常熟悉 node-stats API 里的这部分内容:


      "jvm": {
        "timestamp": 1657854496146,
        "uptime_in_millis": 858680867,
        "mem": {
          "heap_used_in_bytes": 1025882384,
          "heap_used_percent": 52,
          "heap_committed_in_bytes": 1958739968,
          "heap_max_in_bytes": 1958739968,
          "non_heap_used_in_bytes": 346164192,
          "non_heap_committed_in_bytes": 356843520,
					...

 "pools": {
            "young": {
              "used_in_bytes": 721420288,
              "max_in_bytes": 0,
              "peak_used_in_bytes": 1166016512,
              "peak_max_in_bytes": 0
            },
            "old": {
              "used_in_bytes": 286038520,
              "max_in_bytes": 1958739968,
              "peak_used_in_bytes": 1245947392,
              "peak_max_in_bytes": 1958739968
            },
            "survivor": {
              "used_in_bytes": 18423576,
              "max_in_bytes": 0,
              "peak_used_in_bytes": 71303168,
              "peak_max_in_bytes": 0
            }
          }
        },
				

        "gc": {
          "collectors": {
            "young": {
              "collection_count": 12523,
              "collection_time_in_millis": 202427
            },
            "old": {
              "collection_count": 0,
              "collection_time_in_millis": 0
            }
          }
        },

  1. jvm 部分首先列出一些和 heap 内存使用有关的常见统计值。

    • 你可以看到有多少 heap 被使用了,多少被指派了(当前被分配给进程的),以及 heap 被允许分配的最大值。理想情况下,heap_committed_in_bytes 应该等于 heap_max_in_bytes 。如果指派的大小更小,JVM 最终会被迫调整 heap 大小——这是一个非常昂贵的操作。如果你的数字不相等,阅读 堆内存:大小和交换 学习如何正确的配置它。

    • heap_used_percent 指标是值得关注的一个数字。Elasticsearch 被配置为当 heap 达到 75% 的时候开始 GC。如果你的节点一直 >= 75%,你的节点正处于 内存压力 状态。这是个危险信号,不远的未来可能就有慢 GC 要出现了。

    • 如果 heap 使用率一直 >=85%,你就麻烦了。Heap 在 90–95% 之间,则面临可怕的性能风险,此时最好的情况是长达 10–30s 的 GC,最差的情况就是内存溢出(OOM)异常。

  2. 新生代(young) 、 幸存区(survivor) 和 老生代(old) 部分分别展示 GC 中每一个代的内存使用情况。这些统计值很方便观察其相对大小,但是在调试问题的时候,通常并不怎么重要。

  3. gc 部分显示新生代和老生代的垃圾回收次数和累积时间。

    • 大多数时候你可以忽略掉新生代的次数:这个数字通常都很大。这是正常的。

    • 与之相反,老生代的次数应该很小,而且 collection_time_in_millis 也应该很小。这些是累积值,所以很难给出一个阈值表示你要开始操心了(比如,一个跑了一整年的节点,即使很健康,也会有一个比较大的计数)。这就是像 Marvel 这类工具很有用的一个原因。GC 计数的 时间趋势 是个重要的考虑因素。

    • GC 花费的时间也很重要。比如,在索引文档时,一系列垃圾生成了。这是很常见的情况,每时每刻都会导致 GC。这些 GC 绝大多数时候都很快,对节点影响很小:新生代一般就花一两毫秒,老生代花一百多毫秒。这些跟 10 秒级别的 GC 是很不一样的。

    • 我们的最佳建议是定期收集 GC 计数和时长(或者使用 Marvel)然后观察 GC 频率。你也可以开启慢 GC 日志记录,在 日志记录 小节已经讨论过。

Creative Commons License Flag Counter