前言
好久没写文章了, 明天之所以忽然灵机一动, 是因为昨天呈现了这样一个状况:
咱们公司的某个手机APP后端的用户(customer)微服务呈现内存泄露, 导致OutOfMemoryError, 然而因为通过咱们精心优化的openjdk容器参数, 这次故障对用户齐全无感知.
那么咱们是如何做到的呢?
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
总结
新的技术带来新的改革, 咱们须要以倒退的眼光对待"最佳实际, 最佳配置".
2016年, 针对虚机部署的Java的最优参数, 在明天来看, 并不一定仍是最优解.
三人行, 必有我师; 常识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.