为什么 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
复制代码
总结
新的技术带来新的变革, 我们需要以发展的眼光看待"最佳实践, 最佳配置".

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
相关文章
|
1天前
|
Kubernetes Java 调度
Java容器技术:Docker与Kubernetes
Java容器技术:Docker与Kubernetes
12 0
|
16天前
|
存储 算法 Java
盘点Java集合(容器)概览,Collection和Map在开发中谁用的最多?
盘点Java集合(容器)概览,Collection和Map在开发中谁用的最多?
30 0
|
23天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
17 1
|
24天前
|
存储 Java 开发者
使用Docker容器化Java后台应用
【4月更文挑战第16天】本文介绍了如何使用Docker容器化Java后台应用。Docker作为开源应用容器引擎,提供一致运行环境,简化部署,增强可移植性。文章详细阐述了Docker的优势,包括环境一致性、隔离性、可移植性和资源效率。步骤包括安装Docker、创建Dockerfile、构建镜像、运行容器及管理容器。进阶部分涉及多阶段构建、数据持久化和网络配置,强调了Docker对现代Java开发的重要性。
|
27天前
|
安全 算法 Java
安全无忧:Java并发集合容器的应用与实践
安全无忧:Java并发集合容器的应用与实践
28 0
安全无忧:Java并发集合容器的应用与实践
|
1月前
|
存储 安全 算法
java多线程之并发容器集合
java多线程之并发容器集合
|
1月前
|
Java 持续交付 开发者
使用 Docker 容器化 Java Web 应用:提高开发和部署效率
【4月更文挑战第4天】Docker 作为轻量级容器技术,提升了 Java Web 应用的开发和部署效率。它提供类似生产环境的本地开发体验,减少环境配置时间,保证应用隔离性与稳定性。Docker 改善了部署流程,实现跨环境的无缝迁移,支持自动化构建、部署和扩展,并促进持续集成和持续部署,助力企业实现更高效、可靠的软件生命周期管理。
|
2月前
|
Java 容器
Java常用组件、容器与布局
Java常用组件、容器与布局
21 0
|
1天前
|
监控 Kubernetes Docker
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复
【5月更文挑战第9天】本文探讨了Docker容器中应用的健康检查与自动恢复,强调其对应用稳定性和系统性能的重要性。健康检查包括进程、端口和应用特定检查,而自动恢复则涉及重启容器和重新部署。Docker原生及第三方工具(如Kubernetes)提供了相关功能。配置检查需考虑检查频率、应用特性和监控告警。案例分析展示了实际操作,未来发展趋势将趋向更智能和高效的检查恢复机制。
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复
|
1天前
|
存储 安全 数据库
【Docker 专栏】Docker 容器内应用的状态持久化
【5月更文挑战第9天】本文探讨了Docker容器中应用状态持久化的重要性,包括数据保护、应用可用性和历史记录保存。主要持久化方法有数据卷、绑定挂载和外部存储服务。数据卷是推荐手段,可通过`docker volume create`命令创建并挂载。绑定挂载需注意权限和路径一致性。利用外部存储如数据库和云服务可应对复杂需求。最佳实践包括规划存储策略、定期备份和测试验证。随着技术发展,未来将有更智能的持久化解决方案。
【Docker 专栏】Docker 容器内应用的状态持久化