Docker环境undertow线程数不足问题探究

背景

上篇Docker环境Spring Boot应用大量http请求超时,我们找到大量http请求超时原因:undertow的工作线程不足
留下一些疑问:undertow默认配置是怎样的?为什么其他微服务也使用默认参数,却有256个工作线程?

结论

k8s调度启动容器默认分配的cpu资源很小OpenJDK 1.8.0_181会感知容器资源限制
两个因素共同导致undertow获取可用cpu数少,从而undertow工作线程数少。

过程

undertow的默认线程配置是什么

Google没有搜到。我通过查看启动日志和源码加断点,发现默认配置是由io.undertow.Undertow.Builder#Builder类定义:

ioThreads = Math.max(Runtime.getRuntime().availableProcessors(), 2);
workerThreads = ioThreads * 8;

这2行代码,我们可以看出undertow工作线程数量计算公式:“可用cpu数”与2取大值,乘以8

验证获取可用cpu数

异常微服务只有16=2*8个线程,可能原因是Runtime.getRuntime().availableProcessors()获取的“可用cpu数”小于或等于2。

是不是这样?我们写一个类,输出获取的可用cpu数:

public class AvailCpu {public static void main(String[] args) {System.out.println(Runtime.getRuntime().availableProcessors());}
}

编译成class,手动copy到只有16个工作线程的微服务容器内和有256个工作线程的微服务容器内,
执行结果分别是1和32。

这就可以解释为什么那个微服务只有16个工作线程,而其他微服务使用默认参数却有256个工作线程:因为undertow获取的“可用cpu数”不一样。

可用cpu数为什么不一样

接下来我对比两个微服务的环境有什么差异。
为了方便描述,后续有16个工作线程的微服务简称微服务16,有256个线程的微服务简称微服务256

它们的JDK版本不一致

微服务16的JDK版本是OpenJDK 1.8.0_181,微服务256是OpenJDK 1.8.0_111,JDK版本微服务16 > 微服务256 。会不会是JDK版本不同导致的问题呢?(其实是这个原因,具体细节后面再描述。接下来,我走了不少弯路)

第一步,我基于微服务16的JDK镜像openjdk:8-jdk-alpine,加上AvailCpu.class,构造一个镜像进行测试。发现JDK的版本并不是OpenJDK 1.8.0_181,原因是openjdk:8-jdk-alpine这个tag已经指向最新的8-jdk-alpine版本。OpenJDK 1.8.0_181对应的镜像tag是openjdk:8u181-jdk-alpine。

第二步,使用openjdk:8u181-jdk-alpine镜像,加上AvailCpu.class,重新构造一个镜像进行测试。手动启动一个容器,发现输出的可用cpu数是正确的。难道不是JDK版本的问题?

服务器内核版本不一致

测试过程中,我发现测试服务器内核版本和部署微服务16服务器内核版本不一致。

是不是内核版本问题?以下是验证过程,

  1. 找到一个阿里云服务器,确认内核版本和生产环境一致。
  2. 折腾测试镜像构建,上传阿里云私服,下载到做测试的阿里云服务器。
  3. 手动启动一个镜像,发现输出的可用cpu数也正确。

结论是和内核版本没关系

接下来,又回到JDK版本不一致上。

我尝试网上下载OpenJDK 1.8.0_181的源码,源码不好搜,尝试好多链接也没下载到,遂放弃。

k8s启动容器和手动启动容器有差异

最后,我即将放弃之时,突然灵光一现,会不会是k8s启动容器和手动启动容器有差异?
接下来是验证过程,

  1. 重新构造一个测试镜像,上传到私服。
  2. 在k8s上基于这个测试镜像启动一个容器,输出可用cpu数是1。
  3. 手动基于这个测试镜像启动一个容器,输出可用cpu数是4。

果然是k8s启动容器和手动启动容器有差异。

我内心欣喜。

紧接着,我顺着2个方向查找原因:

  1. k8s调度启动容器默认参数是怎样的?
  2. 为什么OpenJDK 1.8.0_181在Docker环境获取可用cpu数是1?

方向1,我没有找到k8s启动容器的默认参数,但发现Docker启动容器有一些设置cpu资源的参数:–cpu-shares、–cpuset-cpus、–cpus、–cpu-period、–cpu-quota。

方向2,我发现JavaSE8u131+和JDK9 支持了对容器资源限制的自动感知能力。在微服务16容器内执行

java -XX:+PrintFlagsFinal 2>/dev/null|grep Container

输出

 bool PreferContainerQuotaForCPUCount           = true                                {product}bool UseContainerSupport                       = true                                {product}

说明OpenJDK 1.8.0_181会感知容器资源限制

那么k8s启动容器和手动启动容器资源限制到底有什么不同呢?
通过学习Docker利用的Linux底层技术cgroup,我了解到每个容器会产生/sys/fs/cgroup文件描述资源,cpu相关的文件夹是cpu和cpuacct。

  • cpu描述cpu资源
  • cpuacct描述cpu资源的使用统计情况。

通过对比k8s启动容器和手动启动容器/sys/fs/cgroup/cpu里面的文件,发现cpu.shares存在差异,

  • k8s启动容器,cpu.shares=2
  • 手动启动容器,cpu.shares=1024

手动启动容器docker run --cpu-shares=2 … ,验证发现获取到的可用cpu数=1。

至此,我们找到了undertow工作线程数不足的真实原因:k8s调度启动容器默认分配的cpu资源很小OpenJDK 1.8.0_181会感知容器资源限制,两个因素共同导致获取undertow获取的可用cpu数少,从而导致undertow工作线程数少

拓展

  1. 研究K8S启动容器的默认参数。
  2. 学习Docker的核心基础知识。
  3. 研究K8S与JVM怎么一起做资源限制。

参考资料

  1. 《Kubernetes之路 1 - Java应用资源限制的迷思》https://yq.aliyun.com/articles/562440
  2. 《Docker run参数参考》https://docs.docker.com/engine/reference/run/?spm=a2c4e.11153940.blogcont562440.8.35c7627aBRGAn3#cpu-period-constraint
  3. 《容器(docker)中运行java需关注的几个小问题》http://www.concurrent.work/docker/java/jvm/gc/pitfalls-about-running-java-inside-container/
  4. 《Docker 背后的内核知识——cgroups 资源限制》https://www.infoq.cn/article/docker-kernel-knowledge-cgroups-resource-isolation
  5. 《聊聊新版JDK对docker容器的支持》https://segmentfault.com/a/1190000014142950
  6. 《DOCKER基础技术:LINUX CGROUP》https://coolshell.cn/articles/17049.html

Docker环境undertow线程数不足问题探究相关推荐

  1. 多线程线程数设置多少合适

    前沿 大家都用过线程池,但是线程池数量设置为多少比较合理呢? 线程数的设置的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能,因此让我们一起去探索吧! 首先要考虑 ...

  2. tomcat线程释放时间_聊下并发和Tomcat线程数(错误更正)

    本文前半部分结论存在严重错误,请看最后2015-1-20更新部分. 最近一直在解决线上一个问题,表现是: Tomcat每到凌晨会有一个高峰,峰值的并发达到了3000以上,最后的结果是Tomcat线程池 ...

  3. Java线程池如何合理配置核心线程数

    我相信大家都用过线程池,但是线程池数量设置为多少比较合理呢? 线程数的设置的最主要的目的是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能,因此让我们一起去探索吧! 首先要考虑 ...

  4. 聊下并发和Tomcat线程数(错误更正)

    本文前半部分结论存在严重错误,请看最后2015-1-20更新部分. 最近一直在解决线上一个问题,表现是: Tomcat每到凌晨会有一个高峰,峰值的并发达到了3000以上,最后的结果是Tomcat线程池 ...

  5. tomcat优化——并发和Tomcat线程数

    最近一直在解决线上一个问题,表现是: Tomcat每到凌晨会有一个高峰,峰值的并发达到了3000以上,最后的结果是Tomcat线程池满了,日志看很多请求超过了1s. 服务器性能很好,Tomcat版本是 ...

  6. 记一次生产环境java服务mqtt连接线程数过多的处理过程

    项目介绍: 本项目是负责发放机设备发放商品的平台.发放机设备是厂商控制,发放机平台是我们公司负责开发和维护.发放机设备和平台是通过mtqq协议通信的. mqtt开发客户端使用的是org.eclipse ...

  7. 压测接口线程数设置_ZAT掌门性能压测巡检系统实战和落地

    项目背景 随着业务拓展,对于接口性能的要求也在上升,各部门也开始针对部分慢接口进行优化,从测试角度针对这些优化需求进行测试时不仅要保证对应接口的功能正常使用同时也要验证接口优化成果.在日常的开发工作中 ...

  8. Docker环境下Java应用的最大内存和堆内存的设置

    Docker环境下Java应用的最大内存和堆内存的设置 1.  设置应用允许使用的最大内存 通过docker run(创建一个新的容器并运行)命令中设置-m来进行设置.案例如下所示. docker r ...

  9. 如何在 Docker 环境下自动给 .NET 程序生成 Dump

    前言 之前"一线码农"大佬有写文章介绍了如何在 windows 下自动 dump,正好手里有个在 docker 环境下 dump 的需求,所以在参考大佬文章的基础上,有了本篇. 工 ...

最新文章

  1. Python3通过汉字输出拼音
  2. sjms-2 创建型模式
  3. OpenCV3学习笔记二:图像的掩膜操作
  4. 数据挖掘 点击更多 界面_8(更多)技巧,可快速改善用户界面
  5. 【Python CheckiO 题解】Second Index
  6. (1).数据结构概述
  7. C# DES加密/解密类
  8. MyBatis使用小案例
  9. 大众考虑投资中国汽车零部件供应商 潜在目标包括国轩高科
  10. html2canvas 在qq保存失败_QQ的截图功能,没想到这么好用!
  11. oracle数据库卡住了无法保存,Oracle数据库使用NFS存储,启动报错提示无法锁定文件...
  12. It's a beautiful world!
  13. ROSBridge - ROS系统与非ROS外部系统的通信的C++客户端实现
  14. jQuery漂浮横幅图片广告代码
  15. C# WPF 低仿网易云音乐(PC)歌词控件
  16. SysML实践指南第二版(中文翻译:刘亚龙)第三章 SysML介绍
  17. LINUX 下播放 DVD 全攻略 (关键字 DVD-ROM LiViD fifo OMS)
  18. 李峋的爱心表白代码来了
  19. 纯净的linux是没有装vim的,vim安装方式
  20. Navicat for MySQL 安装以及初始创建连接,新建数据表

热门文章

  1. 360°环视性能倍增,瑞芯微首发全景环视芯片方案
  2. C语言数据结构实现——一元多项式的基本运算
  3. Nordic Thingy
  4. 一键安装ghost轻博客
  5. 学习如逆水行舟,不进则退
  6. 微信小程序支付(建行支付)
  7. WAP协议研究笔记—WAP传输协议
  8. @Transaction
  9. Mysql密码忘记恢复
  10. 什么是c语言系统调用,什么是系统调用?为什么要用系统调用?