为什么 java 容器推荐使用 ExitOnOutOfMemoryError 而非 HeapDumpOnOutOfMemoryError ?

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
简介: 为什么 java 容器推荐使用 ExitOnOutOfMemoryError 而非 HeapDumpOnOutOfMemoryError ?

前言

好久没写文章了, 今天之所以突然心血来潮, 是因为昨天出现了这样一个情况:

我们公司的某个手机 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" 告警, 告警后人工介入提前处理.
  1. 用 "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
YAML

总结

新的技术带来新的变革, 我们需要以发展的眼光看待 " 最佳实践, 最佳配置 ".

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

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
4月前
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
4月前
|
Java Linux Maven
java依赖冲突解决问题之容器加载依赖jar包如何解决
java依赖冲突解决问题之容器加载依赖jar包如何解决
|
19天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
2月前
|
消息中间件 NoSQL Kafka
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
Flink-10 Flink Java 3分钟上手 Docker容器化部署 JobManager TaskManager Kafka Redis Dockerfile docker-compose
57 4
|
2月前
|
Kubernetes Cloud Native 流计算
Flink-12 Flink Java 3分钟上手 Kubernetes云原生下的Flink集群 Rancher Stateful Set yaml详细 扩容缩容部署 Docker容器编排
Flink-12 Flink Java 3分钟上手 Kubernetes云原生下的Flink集群 Rancher Stateful Set yaml详细 扩容缩容部署 Docker容器编排
80 3
|
4月前
|
安全 算法 Java
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
这篇文章讨论了Java集合类的线程安全性,列举了线程不安全的集合类(如HashSet、ArrayList、HashMap)和线程安全的集合类(如Vector、Hashtable),同时介绍了Java 5之后提供的java.util.concurrent包中的高效并发集合类,如ConcurrentHashMap和CopyOnWriteArrayList。
【Java集合类面试二】、 Java中的容器,线程安全和线程不安全的分别有哪些?
|
4月前
|
Java 容器
【Java集合类面试一】、 Java中有哪些容器(集合类)?
这篇文章列出了Java中的四大类集合接口:Set、List、Queue和Map,以及它们的常用实现类,如HashSet、TreeSet、ArrayList、LinkedList、ArrayDeque、HashMap和TreeMap。
【Java集合类面试一】、 Java中有哪些容器(集合类)?
|
4月前
|
Java 测试技术 数据库
容器镜像解析问题之解析 Java 应用依赖时识别 jar 包如何解决
容器镜像解析问题之解析 Java 应用依赖时识别 jar 包如何解决
31 0
|
4月前
|
存储 安全 Java
【Java 第四篇章】流程控制、容器
本文档详细介绍了Java中的流程控制、集合类型、数组声明及容器的声明与遍历等内容。在流程控制部分,包括了if、if...else、if...else if...else、switch等语句的使用方法,并提供了具体示例。接着,文档对比分析了Java中单列集合(如HashSet、LinkedHashSet、TreeSet等)与双列集合(如HashMap、LinkedHashMap、Hashtable等)的特点及底层实现原理。此外,还介绍了如何声明与初始化数组,并提供了多种循环结构的使用示例。最后,通过具体的代码示例展示了不同集合类型的声明、基本操作(如添加、删除、更新、查找)以及遍历方法。
24 0
|
5月前
|
Java Scala 流计算
实时计算 Flink版产品使用问题之Docker镜像中的Java路径和容器内的Java路径不一致,是什么导致的
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。