由来

时间回到 2017 年,老东家要上 Kubernetes 了,有幸参与和学习(主要是学习)。当时遇到的一了所有 Java 容器化者都遇到的坑:JDK8 不为容器化设计综合症。最简单的例子是Runtime.getRuntime().availableProcessors()返回了主机的 CPU 数,而非期望的容器自身的cpu share/quota,或说 k8s 的 cpu request/limit

时间到了 2021 年,一切本该云淡风轻(虽然工资依然追不上CPI和房价)。虽然我在的项目还是使用 JDK8,但好歹也是 jdk 1.8.0_261 了,已经 backport 了很多容器化的特性到这个版本了。最近在做项目的性能优化,在 Istio 的泥潭苦苦挣扎中。

突然前方同学传来喜讯: 把 POD 的 cpu request 由 2 变 4 后,性能有明显的优化。我在羡慕嫉妒的同时,好奇地研究了一下原理。

原理

直线思维逻辑

Kubernetes 使用 cgroup 进行资源限制:

  • cpu request 对应于 cgroup 的 share 指标。在主机CPU不足,各容器需要争抢CPU情况下,指定各容器的优先级(数字大优先,比例化)
  • cpu limit 对应于 cgroup 的 limit 指标。这是硬限制,不能超。超了就卡慢线程。

那么问题来了,测试环境主机CPU 资源充足,不存在各容器需要争抢CPU 的情况。那么,为何调大 cpu request后,会明显优化性能?

可能性:

  1. 直线思维:Linux CFS Scheduler(任务调度器)实现不太好,在非各容器需要争抢CPU情况下,cpu request 仍然影响了调度
  2. 怀疑论者:新版本的 jdk8 只是依据 cpu request 来自动计算各默认配置,如各线程池。

作为一个只懂 java 的程序员,我关注后者。

求证

作为只懂写代码的程序员,没什么比运行的程序更能帮你说话了。起码,机器不会因为你和他关系好,或等着你给他通点气,或填个KPI,就跑你的程序快一点(不要和我说linux taskset),更不会生成一个和关系有关系的小报告。

回来吧,先看看 POD 的配置:

    resources:
limits:
cpu: "16"
requests:
cpu: "2"

进入 container:

$ cd /tmp
$ cat <<EOF > /tmp/Main.java
public class Main {
public static void main(String[] args) {
System.out.println("Runtime.getRuntime().availableProcessors() = " +
Runtime.getRuntime().availableProcessors());
}
}
EOF $ javac Main.java
$ java -cp . Main
Runtime.getRuntime().availableProcessors() = 2

加点CPU request :

    resources:
limits:
cpu: "16"
requests:
cpu: "4"

进入 container:

$ cd /tmp
$ java -cp . Main
Runtime.getRuntime().availableProcessors() = 4

可见,java 得到 cpu 数,来源于 容器配置的 cpu request 。

availableProcessors() 的影响

再看看 availableProcessors() 的影响。-XX:+PrintFlagsFinal 的作用是在 jvm 启动时打印计算后的默认配置。

# Request cpu=1 时
$ java -XX:+PrintFlagsFinal -cp . Main > req1.txt # Request cpu=4 时
$ java -XX:+PrintFlagsFinal -cp . Main > req4.txt
$ diff req1.txt req4.txt

2c2
< intx ActiveProcessorCount = -1 {product}
---
> intx ActiveProcessorCount := 4 {product}
59c59
< intx CICompilerCount := 2 {product}
---
> intx CICompilerCount := 3 {product}
305c305
< uintx MarkSweepDeadRatio = 5 {product}
---
> uintx MarkSweepDeadRatio = 1 {product}
312c312
< uintx MaxHeapFreeRatio = 70 {manageable}
---
> uintx MaxHeapFreeRatio = 100 {manageable}
325c325
< uintx MaxNewSize := 178913280 {product}
---
> uintx MaxNewSize := 178782208 {product}
336,337c336,337
< uintx MinHeapDeltaBytes := 196608 {product}
< uintx MinHeapFreeRatio = 40 {manageable}
---
> uintx MinHeapDeltaBytes := 524288 {product}
> uintx MinHeapFreeRatio = 0 {manageable}
360c360
< uintx NewSize := 11141120 {product}
---
> uintx NewSize := 11010048 {product}
371c371
< uintx OldSize := 22413312 {product}
---
> uintx OldSize := 22544384 {product}
389c389
< uintx ParallelGCThreads = 0 {product}
---
> uintx ParallelGCThreads = 4 {product}
690,691c690,691
< bool UseParallelGC = false {product}
< bool UseParallelOldGC = false {product}
---
> bool UseParallelGC := true {product}
> bool UseParallelOldGC = true {product}
738c738
< Runtime.getRuntime().availableProcessors() = 1
---
> Runtime.getRuntime().availableProcessors() = 4

可见,availableProcessors() 不但影响了 jvm 的 GC 线程数,JIT 线程数,甚至是 GC算法。更大问题是一些 servlet container(如 Jetty)和 Netty 默认也会使用这个数字去配置他们的线程池。

反证

如果还是觉得Linux CFS Scheduler(任务调度器)在主机CPU过剩时,调度还是受到了 cgroup share(cpu request)影响 这个可能性需要排除。那么在POD拉起后,直接使用 linux 终端,去修改 cgroup 的 share 文件,增加一倍,再测试,就可以知道。对,反模式是排除问题的常用方法。但我没做这个测试,因我不想太科学凡事留一线。

填坑

填坑是程序员的天职,无论你喜不喜欢,无论这个坑是你挖的,还是前度留下的。这个坑有几个填法:

  1. 修改 POD CPU request 为忙时使用量,即加大request,limit 不变
  2. 升级到 JDK11,使用期默认打开的PreferContainerQuotaForCPUCount参数,即 availableProcessors() 返回 CPU limit 数。
  3. 所有默认使用availableProcessors() 的地方,修改为显式指定,如GC线程数,Netty 线程数……
  4. CPU request/limit 不变,即 request 大大 小于 limit。但显式告诉 JVM 可以使用的 CPU 数。

国际习惯,我选用了 4。原因:

  • POD 如果配置了大的 request,相当于锁定独占了主机的资源。主机实际资源利用率一定降低。而这个 request 其实只是个忙时峰值需求,如启动时的编译,或电商的抢购。
  • 为所有默认使用availableProcessors() 的地方,修改为显式指定。这个工作量大,对未来未知的使用到 availableProcessors() 的地方不可控。
  • 升级 JDK11,不是我等程序员能定的

明白了我能做什么后,就 Just do it 了。

话说,从 JDK 8u191后,支持了-XX:ActiveProcessorCount=count参数,告诉JVM真正可用的CPU数。所以,只要:

java -XX:+PrintFlagsFinal -XX:ActiveProcessorCount=$POD_CPU_LIMIT -cp . Main
# 当然,如果觉得 $POD_CPU_LIMIT 太大,就自行调整吧

-XX:ActiveProcessorCount的说明见:https://www.oracle.com/java/t...

总结

很明显,这是个应该早几年就写的 Blog。现在估计你家已经不使用JDK8了。而一般直接到 JDK11 LTS 了。或者,本文想说的是一种求证问题的方法和态度。它或者不能直接给你带来什么好处,有时候,甚至很让一些人讨厌,影响你进升的大好前程。不过,一个行业如果要进步,还得依赖这种情怀。英文有个词:Nerd。专门形容这种态度。


扩展阅读

史前的修正 availableProcessors() 大法

在 JDK8 还没为容器化设计前,大神们只能先自行解决了。方法两种(层):

  1. mount bind 修改内核层 cpu 数的 system file
  2. 重载 gun libc 的 sysconf 函数
  3. 在 Linux 的动态 link .so 时重载 JVM_ActiveProcessorCount 函数,定制后返回

方法3相对简单。这里只说方法2:

参考: https://stackoverflow.com/que...

#include <stdlib.h>
#include <unistd.h> int JVM_ActiveProcessorCount(void) {
char* val = getenv("_NUM_CPUS");
return val != NULL ? atoi(val) : sysconf(_SC_NPROCESSORS_ONLN);
}

First, make a shared library of this:

gcc -O3 -fPIC -shared -Wl,-soname,libnumcpus.so -o libnumcpus.so numcpus.c

Then run Java as follows:

$ LD_PRELOAD=/path/to/libnumcpus.so _NUM_CPUS=2 java AvailableProcessors

方法1、2比较通用,对 JNI 等非 java 生态的同样有效,但实现需要了解一些 Linux。可以参考: https://geek-tips.imtqy.com/a...https://github.com/jvm-profil...

参考

https://christopher-batey.med...

https://www.batey.info/docker...

https://mucahit.io/2020/01/27...

https://blog.gilliard.lol/201...

https://cloud.google.com/run/...

https://stackoverflow.com/que...

https://www.oracle.com/java/t...

https://stackoverflow.com/que...

https://bugs.openjdk.java.net...

https://programmer.group/5ce1...

[转帖]Java 容器化的历史坑(史坑) - 资源限制篇的更多相关文章

  1. 利用Google开源Java容器化工具Jib构建镜像

    转载:https://blog.csdn.net/u012562943/article/details/80995373 一.前言 容器的出现让Java开发人员比以往任何时候都更接近“编写一次,到处运 ...

  2. Java容器化参数配置最佳实践

    Java是以VM为基础的,而云原生讲究的就是Native,天然的矛盾,虽然Quarkus是为GraalVM和HotSpot量身定制的K8s Native Java框架,生态原因切换成本太高,这种矛盾体 ...

  3. 新一代Java程序员必学的Docker容器化技术基础篇

    Docker概述 **本人博客网站 **IT小神 www.itxiaoshen.com Docker文档官网 Docker是一个用于开发.发布和运行应用程序的开放平台.Docker使您能够将应用程序与 ...

  4. 谷歌助力,快速实现 Java 应用容器化

    原文地址:梁桂钊的博客 博客地址:http://blog.720ui.com 欢迎关注公众号:「服务端思维」.一群同频者,一起成长,一起精进,打破认知的局限性. Google 在 2018 年下旬开源 ...

  5. jmx_prometheus_javaagent+prometheus+alertmanager+grafana完成容器化java监控告警(二)

    一.拓扑图 二.收集数据 2.1前期准备 创建共享目录,即为了各节点都创建该目录,有两个文件,做数据共享 /home/target/prom-jvm-demo 1.下载文件 jmx_prometheu ...

  6. Java 服务 Docker 容器化最佳实践

    转载自:https://mp.weixin.qq.com/s/d2PFISYUy6X6ZAOGu0-Kig 1. 概述 当我们在容器中运行 Java 应用程序时,可能希望对其进行调整参数以充分利用资源 ...

  7. 【转帖】使用容器化和 Docker 实现 DevOps 的基础知识

    使用容器化和 Docker 实现 DevOps 的基础知识 https://www.kubernetes.org.cn/6730.html 2020-02-24 15:20 灵雀云 分类:容器 阅读( ...

  8. Coding-Job:从研发到生产的容器化融合实践

    大家好,我是来自 CODING 的全栈开发工程师,我有幸在 CODING 参与了 Coding-Job 这个容器化的编排平台的研发.大家对 CODING 可能比较了解, Coding.net 是一个一 ...

  9. 国内最具影响力科技创投媒体36Kr的容器化之路

    本文由1月19日晚36Kr运维开发工程师田翰明在Rancher技术交流群的技术分享整理而成.微信搜索rancher2,添加Rancher小助手为好友,加入技术群,实时参加下一次分享~ 田翰明,36Kr ...

  10. 传统.NET 4.x应用容器化体验(1)

    我们都知道.NET Core应用可以跑在Docker上,那.NET Framework 4.x应用呢?借助阿里云ECS主机(Windows Server 2019 with Container版本), ...

随机推荐

  1. el-table 设置合并行或列时,显示错乱问题

    1. 需求效果图: 2. 接口数据格式: 点击查看代码 const list = [ { contractNo: "CAI-20220801001", contractItem: ...

  2. 中文语音识别转文字的王者,阿里达摩院FunAsr足可与Whisper相颉顽

    君不言语音识别技术则已,言则必称Whisper,没错,OpenAi开源的Whisper确实是世界主流语音识别技术的魁首,但在中文领域,有一个足以和Whisper相颉顽的项目,那就是阿里达摩院自研的Fu ...

  3. 从C++CLI工程的依赖库引用问题看.Net加载程序集机制

    问题 最近在为某第三方MFC项目写C++/CLI工程插件时遇到了如下一个问题: MFC的工程不允许把.Net的依赖程序集放到执行程序的目录(防止影响其稳定性),依赖库只能放到非执行程序子目录的其他目录 ...

  4. CSS之动画

    一.动画 动画类型 CSS3 可以创建动画,它可以取代许多网页动画图像.Flash 动画和 JavaScript 实现的效果. transform属性可以定义一些主要的动画属性, translate: ...

  5. 华为云 UCS (On-Premises):运行在您本地数据中心的CCE集群

    摘要:华为云分布式云原生UCS服务,是面向分布式云场景下的新一代云原生产品,提供UCS (Huawei Cloud).UCS (Partner Cloud).UCS (Multi-Cloud).UCS ...

  6. 了解库开发,我们从STM32标准库开发学起

    摘要:从STM32新建工程.编译下载程序出发,让新手由浅入深,尽享STM32标准库开发的乐趣. 自从CubeMX等图像配置软件的出现,同学们往往点几下鼠标就解决了单片机的配置问题.对于追求开发速度的业 ...

  7. 这8个JS 新功能,你应该去尝试一下

    摘要:本文主要介绍几个已经进入stage4的提案,这几个提案有望在2022年逐步纳入标准. 本文分享自华为云社区<2022 年你应该尝试的 8个 JavaScript 新功能>,作者:前端 ...

  8. A/B测试有哪些常见应用场景?——火山引擎DataTester科普

      更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 火山引擎 DataTester不仅对外提供服务,也是字节跳动内部所应用的A/B实验平台,它基于先进的底层算法, ...

  9. 火山引擎DataLeap数据调度实例的 DAG 优化方案 (二):功能设计

    针对上面存在的问题以及对需求的分析,我们可以进行如下的功能实现与设计: 首先是渲染方案的替换,将 svg 的渲染方案替换成 canvas 渲染,通过减少页面中 DOM 的数量,提高前端渲染性能. 其次 ...

  10. 火山引擎 DataLeap:数据秒级生产,揭秘电商实时数仓最佳实践!

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 一年一度的「三八大促」刚刚落下帷幕,各大电商平台纷纷推出补贴.营销等玩法,力图推动持续增长.而电商平台持续增长,离 ...