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

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 好久没写文章了, 今天之所以突然心血来潮, 是因为昨天出现了这样一个情况:我们公司的某个手机APP后端的用户(customer)微服务出现内存泄露, 导致OutOfMemoryError, 但是

前言
好久没写文章了, 今天之所以突然心血来潮, 是因为昨天出现了这样一个情况:
我们公司的某个手机APP后端的用户(customer)微服务出现内存泄露, 导致OutOfMemoryError, 但是因为经过我们精心优化的openjdk容器参数, 这次故障对用户完全无感知. :muscle::muscle::muscle:
那么我们是如何做到的呢?
HeapDumpOnOutOfMemoryError VS ExitOnOutOfMemoryError
我们都知道, 在传统的虚拟机上部署的Java实例. 为了更好地分析问题, 一般都是要加上: -XX:+HeapDumpOnOutOfMemoryError这个参数的. 加这个参数后, 如果遇到内存溢出, 就会自动生成HeapDump, 后面我们可以拿到这个HeapDump来更精确地分析问题.
但是, "大人, 时代变了!"
容器技术的发展, 给传统运维模式带来了巨大的挑战, 这个挑战是革命性的:

传统的应用都是"永久存在的" vs 容器pod是"短暂临时的存在"
传统应用扩缩容相对困难 vs 容器扩缩容丝般顺滑
传统应用运维模式关注点是:"定位问题" vs 容器运维模式是: "快速恢复"
传统应用一个实例报HeapDumpError就会少一个 vs 容器HeapDump shutdown后可以自动启动, 已达到指定副本数
...

简单总结一下, 在使用容器平台后, 我们的工作倾向于:

遇到故障快速失败
遇到故障快速恢复
尽量做到用户对故障"无感知"

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

遇到故障快速失败, 即尽可能"快速退出, 快速终结"
有问题java应用容器实例退出后, 新的实例迅速启动填补;
"快速退出, 快速终结", 同时配合LB, 退出和冷启动的过程中用户请求不会分发进来.

-XX:+ExitOnOutOfMemoryError就正好满足这种需求:
传递此参数时,抛出OutOfMemoryError时JVM将立即退出。 如果您想终止应用程序,则可以传递此参数。
细节
让我们重新回顾故障: "我们公司的某个手机APP后端的用户(customer)微服务出现内存泄露, 导致OutOfMemoryError"
该customer应用概述如下:

无状态
通过Deployment部署, 有6个副本
通过SVC提供服务

完整的过程如下:

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

在此过程中, 用户基本上是对后台故障"无感知"的.
当然, 要做到这些, 其实JVM参数以及启动脚本中, 还有很多细节和门道. 如: 启动脚本应该是: exec java ....$*
有机会再写文章分享.
新的疑问
上边一章, 我们解释了"为什么Java容器推荐使用ExitOnOutOfMemoryError而非HeapDumpOnOutOfMemoryError", 但是细心的小伙伴也会发现, 新的配置也会带来新的问题, 比如:

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

这些其实可以通过其他手段来解决:

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

答: 配置合理的Readiness Probe, 只要Readiness Probe探测失败, K8S就会自动将这个节点从SVC中摘除. 那么合理的Readiness Probe在这里指的就是应用不可用时, Readiness Probe探测必然是失败的. 所以一般不能是探测某个端口是否在监听, 而是应该是探测对应的api是否正常. 如下方.
答: 通过Prometheus JVM Exporter + Prometheus + AlertManger, 配置合理的AlertRule. 如: "过去X时间, GC total time>5s"告警, 告警后人工介入提前处理.

用"ExitOnOutOfMemoryError"代替"HeapDumpOnOutOfMemoryError", 那我怎么定位该问题的根因并解决? 2个参数一起用不是更香么?

答: 目的是为了"快速退出, 快速终结". 毕竟做HeapDump也是需要时间的, 这段时间内可能就会造成体验的下降. 所以, 只有"ExitOnOutOfMemoryError", 退出地越快越好.
答: 至于分析问题, 可以通过其他手段分析, 如嵌入"Tracing agent"做Tracing的监控, 通过分析故障时的traces定位根因.
Prometheus Alertrule gctime告警后, 人工通过jcmd等命令手动做heapdump.

readinessProbe:
httpGet:

path: /actuator/info
port: 8088
scheme: HTTP

initialDelaySeconds: 60
timeoutSeconds: 3
periodSeconds: 10
successThreshold: 1
failureThreshold: 3
复制代码
总结
新的技术带来新的变革, 我们需要以发展的眼光看待"最佳实践, 最佳配置".

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
4月前
|
Kubernetes Cloud Native Java
云原生之旅:从容器到微服务的演进之路Java 内存管理:垃圾收集器与性能调优
【8月更文挑战第30天】在数字化时代的浪潮中,企业如何乘风破浪?云原生技术提供了一个强有力的桨。本文将带你从容器技术的基石出发,探索微服务架构的奥秘,最终实现在云端自由翱翔的梦想。我们将一起见证代码如何转化为业务的翅膀,让你的应用在云海中高飞。
|
4月前
|
Java Linux Maven
java依赖冲突解决问题之容器加载依赖jar包如何解决
java依赖冲突解决问题之容器加载依赖jar包如何解决
|
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 包如何解决
30 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版的一些典型使用合集。
|
5月前
|
Java 应用服务中间件 持续交付
Java面试题:简述Docker等容器化技术的原理及其在Java应用部署中的作用。
Java面试题:简述Docker等容器化技术的原理及其在Java应用部署中的作用。
73 0