关于运维:为什么-java-容器推荐使用-ExitOnOutOfMemoryError

4次阅读

共计 2491 个字符,预计需要花费 7 分钟才能阅读完成。

前言

好久没写文章了, 明天之所以忽然灵机一动, 是因为昨天呈现了这样一个状况:

咱们公司的某个手机 APP 后端的用户 (customer) 微服务呈现内存泄露, 导致 OutOfMemoryError, 然而因为通过咱们精心优化的 openjdk 容器参数, 这次故障对用户 齐全无感知.

那么咱们是如何做到的呢?

HeapDumpOnOutOfMemoryError VS ExitOnOutOfMemoryError

咱们都晓得, 在传统的虚拟机上部署的 Java 实例. 为了更好地剖析问题, 个别都是要加上: -XX:+HeapDumpOnOutOfMemoryError这个参数的. 加这个参数后, 如果遇到内存溢出, 就会主动生成 HeapDump, 前面咱们能够拿到这个 HeapDump 来更准确地剖析问题.

然而, “ 小孩儿, 时代变了!”

容器技术的倒退, 给传统运维模式带来了微小的挑战, 这个挑战是革命性的:

  1. 传统的利用都是 ” 永恒存在的 ” vs 容器 pod 是 ” 短暂长期的存在 ”
  2. 传统利用扩缩容绝对艰难 vs 容器扩缩容丝般顺滑
  3. 传统利用运维模式关注点是:” 定位问题 ” vs 容器运维模式是: “ 疾速复原 ”
  4. 传统利用一个实例报 HeapDumpError 就会少一个 vs 容器 HeapDump shutdown 后能够主动启动, 已达到指定正本数

简略总结一下, 在应用容器平台后, 咱们的工作偏向于:

  1. 遇到故障疾速失败
  2. 遇到故障疾速复原
  3. 尽量做到用户对故障 ” 无感知 ”

所以, 针对 Java 利用容器, 咱们也要优化以满足这种需要, 以 OutOfMemoryError 故障为例:

  1. 遇到故障疾速失败, 即尽可能 ” 疾速退出, 疾速终结 ”
  2. 有问题 java 利用容器实例退出后, 新的实例迅速启动填补;
  3. “ 疾速退出, 疾速终结 ”, 同时配合 LB, 退出和冷启动的过程中用户申请不会散发进来.

-XX:+ExitOnOutOfMemoryError就正好满足这种需要:

传递此参数时,抛出 OutOfMemoryError 时 JVM 将立刻退出。如果您想终止应用程序,则能够传递此参数。

细节

让咱们从新回顾故障: “ 咱们公司的某个手机 APP 后端的用户 (customer) 微服务呈现内存泄露, 导致 OutOfMemoryError”

该 customer 利用概述如下:

  1. 无状态
  2. 通过 Deployment 部署, 有 6 个正本
  3. 通过 SVC 提供服务

残缺的过程如下:

  1. 6 个正本, 其中 1 个呈现OutOfMomoryError
  2. 因为正本的 jvm 参数配置有: -XX:+ExitOnOutOfMemoryError, 该实例的 JVM(PID 为 1)立刻退出.
  3. 因为 pid 1 过程退出, 此时 pod 立即出于 Terminating 状态, 并且变为:Terminated
  4. 同时, customer 的 SVC 负载平衡会将该正本从 SVC 负载平衡中移除, 用户申请不会被散发到该节点.
  5. K8S 检测到正本数和 Deployment replicas 不统一, 启动 1 个新的正本.
  6. 待新的局部 Readiness Probe 探测通过, customer 的 SVC 负载平衡将这个新的正本退出到负载平衡中, 接管用户申请.

在此过程中, 用户基本上是对后盾故障 ” 无感知 ” 的.

当然, 要做到这些, 其实 JVM 参数以及启动脚本中, 还有很多细节和门道. 如: 启动脚本应该是: exec java ....$*

有机会再写文章分享.

新的疑难

上边一章, 咱们解释了 ” 为什么 Java 容器举荐应用 ExitOnOutOfMemoryError 而非 HeapDumpOnOutOfMemoryError”, 然而仔细的小伙伴也会发现, 新的配置也会带来新的问题, 比方:

  1. JVM 从 fullgc -> OutOfMemoryError 这段时间内, 用户的体验还是会降落的, 怎么会是 ” 故障无感知 ” 呢?
  2. 用 ”ExitOnOutOfMemoryError” 代替 ”HeapDumpOnOutOfMemoryError”, 那我怎么定位该问题的根因并解决? 2 个参数一起用不是更香么?

这些其实能够通过其余伎俩来解决:

  1. JVM 从 fullgc -> OutOfMemoryError 这段时间内, 用户的体验还是会降落的, 怎么会是 ” 故障无感知 ” 呢?

    1. 答: 配置正当的 Readiness Probe, 只有Readiness Probe 探测失败, K8S 就会主动将这个节点从 SVC 中摘除. 那么正当的 Readiness Probe 在这里指的就是利用不可用时, Readiness Probe探测必然是失败的. 所以个别不能是探测某个端口是否在监听, 而是应该是探测对应的 api 是否失常. 如下方.
    2. 答: 通过 Prometheus JVM Exporter + Prometheus + AlertManger, 配置正当的 AlertRule. 如: “ 过来 X 工夫, GC total time>5s” 告警, 告警后人工染指提前解决.
  2. 用 ”ExitOnOutOfMemoryError” 代替 ”HeapDumpOnOutOfMemoryError”, 那我怎么定位该问题的根因并解决? 2 个参数一起用不是更香么?

    1. 答: 目标是为了 ” 疾速退出, 疾速终结 ”. 毕竟做 HeapDump 也是须要工夫的, 这段时间内可能就会造成体验的降落. 所以, 只有 ”ExitOnOutOfMemoryError”, 退出地越快越好.
    2. 答: 至于剖析问题, 能够通过其余伎俩剖析, 如嵌入 ”Tracing agent” 做 Tracing 的监控, 通过剖析故障时的 traces 定位根因.
    3. Prometheus Alertrule gctime 告警后, 人工通过 jcmd 等命令手动做 heapdump.
readinessProbe:
  httpGet:
    path: /actuator/info
    port: 8088
    scheme: HTTP
  initialDelaySeconds: 60
  timeoutSeconds: 3
  periodSeconds: 10
  successThreshold: 1
  failureThreshold: 3

总结

新的技术带来新的改革, 咱们须要以倒退的眼光对待 ” 最佳实际, 最佳配置 ”.

2016 年, 针对虚机部署的 Java 的最优参数, 在明天来看, 并不一定仍是最优解.

三人行, 必有我师; 常识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.

正文完
 0