如何做征信公司网站,wordpress的极限访问量,discuz网站开发,用旧电脑做网站第30讲 | Java程序运行在Docker等容器环境有哪些新问题#xff1f; 如今#xff0c;Docker 等容器早已不是新生事物#xff0c;正在逐步成为日常开发、部署环境的一部分。Java 能否无缝地运行在容器环境#xff0c;是否符合微服务、Serverless 等新的软件架构和场景#x…第30讲 | Java程序运行在Docker等容器环境有哪些新问题 如今Docker 等容器早已不是新生事物正在逐步成为日常开发、部署环境的一部分。Java 能否无缝地运行在容器环境是否符合微服务、Serverless 等新的软件架构和场景在一定程度上也会影响未来的技术栈选择。当然Java 对 Docker 等容器环境的支持也在不断增强自然地Java 在容器场景的实践也逐渐在面试中被涉及。我希望通过专栏今天这一讲能够帮你能做到胸有成竹。
今天我要问你的问题是Java 程序运行在 Docker 等容器环境有哪些新问题
典型回答
对于 Java 来说Docker 毕竟是一个较新的环境例如其内存、CPU 等资源限制是通过 CGroupControl Group实现的早期的 JDK 版本8u131 之前并不能识别这些限制进而会导致一些基础问题
如果未配置合适的 JVM 堆和元数据区、直接内存等参数Java 就有可能试图使用超过容器限制的内存最终被容器 OOM kill或者自身发生 OOM。
错误判断了可获取的 CPU 资源例如Docker 限制了 CPU 的核数JVM 就可能设置不合适的 GC 并行线程数等。
从应用打包、发布等角度出发JDK 自身就比较大生成的镜像就更为臃肿当我们的镜像非常多的时候镜像的存储等开销就比较明显了。
如果考虑到微服务、Serverless 等新的架构和场景Java 自身的大小、内存占用、启动速度都存在一定局限性因为 Java 早期的优化大多是针对长时间运行的大型服务器端应用。
考点分析
今天的问题是个针对特定场景和知识点的问题我给出的回答简单总结了目前业界实践中发现的一些问题。
如果我是面试官针对这种问题如果你确实没有太多 Java 在 Docker 环境的使用经验直接说不知道也算是可以接受的毕竟没有人能够掌握所有知识点嘛。
但我们要清楚有经验的面试官一般不会以纯粹偏僻的知识点作为面试考察的目的更多是考察思考问题的思路和解决问题的方法。所以如果有基础的话可以从操作系统、容器原理、JVM 内部机制、软件开发实践等角度展示系统性分析新问题、新场景的能力。毕竟变化才是世界永远的主题能够在新变化中找出共性与关键是优秀工程师的必备能力。
今天我会围绕下面几个方面展开
面试官可能会进一步问到有没有想过为什么类似 Docker 这种容器环境会有点“欺负”Java从 JVM 内部机制来说问题出现在哪里
我注意到有种论调说“没人在容器环境用 Java”不去争论这个观点正确与否我会从工程实践出发梳理问题原因和相关解决方案并探讨下新场景下的最佳实践。
知识扩展
首先我们先来搞清楚 Java 在容器环境的局限性来源Docker 到底有什么特别
虽然看起来 Docker 之类容器和虚拟机非常相似例如它也有自己的 shell能独立安装软件包运行时与其他容器互不干扰。但是如果深入分析你会发现Docker 并不是一种完全的虚拟化技术而更是一种轻量级的隔离技术。
上面的示意图展示了 Docker 与虚拟机的区别。从技术角度基于 namespaceDocker 为每个容器提供了单独的命名空间对网络、PID、用户、IPC 通信、文件系统挂载点等实现了隔离。对于 CPU、内存、磁盘 IO 等计算资源则是通过 CGroup 进行管理。如果你想了解更多 Docker 的细节请参考相关技术文档。 (https://medium.freecodecamp.org/a-beginner-friendly-introduction-to-containers-vms-and-docker-79a9e3e119b)
Docker 仅在类似 Linux 内核之上实现了有限的隔离和虚拟化并不是像传统虚拟化软件那样独立运行一个新的操作系统。如果是虚拟化的操作系统不管是 Java 还是其他程序只要调用的是同一个系统 API都可以透明地获取所需的信息基本不需要额外的兼容性改变。
容器虽然省略了虚拟操作系统的开销实现了轻量级的目标但也带来了额外复杂性它限制对于应用不是透明的需要用户理解 Docker 的新行为。所以有专家曾经说过“幸运的是 Docker 没有完全隐藏底层信息但是不幸的也是 Docker 没有隐藏底层信息”
对于 Java 平台来说这些未隐藏的底层信息带来了很多意外的困难主要体现在几个方面
第一容器环境对于计算资源的管理方式是全新的CGroup 作为相对比较新的技术历史版本的 Java 显然并不能自然地理解相应的资源限制。
第二namespace 对于容器内的应用细节增加了一些微妙的差异比如 jcmd、jstack 等工具会依赖于“/proc//”下面提供的部分信息但是 Docker 的设计改变了这部分信息的原有结构我们需要对原有工具进行修改(https://bugs.openjdk.java.net/browse/JDK-8179498)以适应这种变化。
从 JVM 运行机制的角度为什么这些“沟通障碍”会导致 OOM 等问题呢
你可以思考一下这个问题实际是反映了 JVM 如何根据系统资源内存、CPU 等情况在启动时设置默认参数。
这就是所谓的Ergonomics机制(https://docs.oracle.com/javase/10/gctuning/ergonomics.htm#JSGCT-GUID-DB4CAE94-2041-4A16-90EC-6AE3D91EC1F1)例如
JVM 会大概根据检测到的内存大小设置最初启动时的堆大小为系统内存的 1/64并将堆最大值设置为系统内存的 1/4。
而 JVM 检测到系统的 CPU 核数则直接影响到了 Parallel GC 的并行线程数目和 JIT complier 线程数目甚至是我们应用中 ForkJoinPool 等机制的并行等级。
这些默认参数是根据通用场景选择的初始值。但是由于容器环境的差异Java 的判断很可能是基于错误信息而做出的。这就类似我以为我住的是整栋别墅实际上却只有一个房间是给我住的。
更加严重的是JVM 的一些原有诊断或备用机制也会受到影响。为保证服务的可用性一种常见的选择是依赖“-XX:OnOutOfMemoryError”功能通过调用处理脚本的形式来做一些补救措施比如自动重启服务等。但是这种机制是基于 fork 实现的当 Java 进程已经过度提交内存时fork 新的进程往往已经不可能正常运行了。
根据前面的总结似乎问题非常棘手那我们在实践中如何解决这些问题呢
首先如果你能够升级到最新的 JDK 版本这个问题就迎刃而解了。
针对这种情况JDK 9 中引入了一些实验性的参数以方便 Docker 和 Java“沟通”例如针对内存限制可以使用下面的参数设置
-XX:UnlockExperimentalVMOptions
-XX:UseCGroupMemoryLimitForHeap注意这两个参数是顺序敏感的并且只支持 Linux 环境。而对于 CPU 核心数限定Java 已经被修正为可以正确理解“–cpuset-cpus”等设置无需单独设置参数。
如果你可以切换到 JDK 10 或者更新的版本问题就更加简单了。Java 对容器Docker的支持已经比较完善默认就会自适应各种资源限制和实现差异。前面提到的实验性参数“UseCGroupMemoryLimitForHeap”已经被标记为废弃。
与此同时新增了参数用以明确指定 CPU 核心的数目。
-XX:ActiveProcessorCountN如果实践中发现有问题也可以使用“-XX:-UseContainerSupport”关闭 Java 的容器支持特性这可以作为一种防御性机制避免新特性破坏原有基础功能。当然也欢迎你向 OpenJDK 社区反馈问题。
幸运的是JDK 9 中的实验性改进已经被移植到 Oracle JDK 8u131 之中你可以直接下载相应镜像(https://hub.docker.com/_/oracle-serverjre-8)并配置“UseCGroupMemoryLimitForHeap”后续很有可能还会进一步将 JDK 10 中相关的增强应用到 JDK 8 最新的更新中。
但是如果我暂时只能使用老版本的 JDK 怎么办
我这里有几个建议
明确设置堆、元数据区等内存区域大小保证 Java 进程的总大小可控。
例如我们可能在环境中这样限制容器内存
$ docker run -it --rm --name yourcontainer -p 8080:8080 -m 800M repo/your-java-container:openjdk那么就可以额外配置下面的环境变量直接指定 JVM 堆大小。
-e JAVA_OPTIONS-Xmx300m明确配置 GC 和 JIT 并行线程数目以避免二者占用过多计算资源。
-XX:ParallelGCThreads
-XX:CICompilerCount除了我前面介绍的 OOM 等问题在很多场景中还发现 Java 在 Docker 环境中似乎会意外使用 Swap。具体原因待查但很有可能也是因为 Ergonomics 机制失效导致的我建议配置下面参数明确告知 JVM 系统内存限额。
-XX:MaxRAMcat /sys/fs/cgroup/memory/memory.limit_in_bytes也可以指定 Docker 运行参数例如
--memory-swappiness0这是受操作系统Swappiness(https://en.wikipedia.org/wiki/Swappiness)机制影响当内存消耗达到一定门限操作系统会试图将不活跃的进程换出Swap out上面的参数有显式关闭 Swap 的作用。所以可以看到Java 在 Docker 中的使用从操作系统、内核到 JVM 自身机制需要综合运用我们所掌握的知识。
回顾我在专栏第 25 讲 JVM 内存区域的介绍JVM 内存消耗远不止包括堆很多时候仅仅设置 Xmx 是不够的MaxRAM 也有助于 JVM 合理分配其他内存区域。如果应用需要设置更多 Java 启动参数但又不确定什么数值合理可以试试一些社区提供的工具(https://github.com/cloudfoundry/java-buildpack-memory-calculator)但要注意通用工具的局限性。
更进一步来说对于容器镜像大小的问题如果你使用的是 JDK 9 以后的版本完全可以使用 jlink 工具定制最小依赖的 Java 运行环境将 JDK 裁剪为几十 M 的大小这样运行起来并不困难。
今天我从 Docker 环境中 Java 可能出现的问题开始分析了为什么容器环境对应用并不透明以及这种偏差干扰了 JVM 的相关机制。最后我从实践出发介绍了主要问题的解决思路希望对你在实际开发时有所帮助。
一课一练
关于今天我们讨论的题目你做到心中有数了吗今天的思考题是针对我提到的微服务和 Serverless 等场景 Java 表现出的不足有哪些方法可以改善 Java 的表现
请你在留言区写写你对这个问题的思考我会选出经过认真思考的留言送给你一份学习奖励礼券欢迎你与我一起讨论。
机制。最后我从实践出发介绍了主要问题的解决思路希望对你在实际开发时有所帮助。
一课一练
关于今天我们讨论的题目你做到心中有数了吗今天的思考题是针对我提到的微服务和 Serverless 等场景 Java 表现出的不足有哪些方法可以改善 Java 的表现
请你在留言区写写你对这个问题的思考我会选出经过认真思考的留言送给你一份学习奖励礼券欢迎你与我一起讨论。
你的朋友是不是也在准备面试呢你可以“请朋友读”把今天的题目分享给好友或许你能帮到他。