文章目录
- 项目背景
- 问题初现
- 问题排查
- 问题定位
- 问题解决
项目背景
基于k8s的容器化kafka PaaS管理平台,业务团队申请kafka,通过一系列操作,封装crd,调用operator创建集群,当然还包括其他功能、topic管理、group管理、监控告警、集群扩容、分区管理等等。
后台会对每个集群启动定时任务,扫描kafka的元数据变化,主要是使用zk客户端Curator。
问题初现
在集群增长到一定数量后,有一天,突然再访问PaaS平台就报错了,报错信息竟然是OOM:unable to create new native thread
原因可能是:
-
线程数过多,无法再申请新的线程
-
线程数超过机器每个对进程的线程数的限制
问题排查
首先查看程序占用的线程数
由于种种原因,用了比较笨的方法:
jstack 1 | grep 'java.lang.Thread.State' | wc -l
得到的数量是2300+,而容器对每个pod的线程数限制为2000,这就是问题所在
通过观察之后,大部分线程都是以“Curator-”为前缀的,进一步过滤:
jstack 1 |grep "Curator" |wc -l
得到的数量是1800+
所以可以定位到:这些线程,都是和zk客户端有关系。
问题定位
首先看是哪个地方创建的线程,根据一顿搜索,找到了创建线程池的代码,其中线程池核心数的取值是这样的:
Math.max(Runtime.getRuntime().availableProcessors(), 16);
平平无奇,毫无波澜,但是这就是罪魁祸首
我们项目的Dockerfile如下:
FROM java:8
WORKDIR /
ADD target/xadd-kafka-console.jar app.jar
RUN bash -c 'cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && touch /app.jar'
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-server","-Xms4096m","-Xmx4096m","-XX:NewSize=1500m","-XX:+UseConcMarkSweepGC","-XX:CMSInitiatingOccupancyFraction=70","-jar","/app.jar"]
没有指定具体的java版本,这么写,到容器内会拉取哪个版本呢?
root@xadd-consumer-bbb464c4-qp7q8:/# java -version
openjdk version "1.8.0_111"
OpenJDK Runtime Environment (build 1.8.0_111-8u111-b14-2~bpo8+1-b14)
OpenJDK 64-Bit Server VM (build 25.111-b14, mixed mode)
root@xadd-consumer-bbb464c4-qp7q8:/#
root@xadd-consumer-bbb464c4-qp7q8:/#
答案是较低版本的:1.8.0_111
低版本的jdk对容器环境支持并不友好,使用代码Runtime.getRuntime().availableProcessors()获取到的CPU数量是宿主机的真实核数,而不是pod所分配的核数,我们K8S node节点的CPU核数为100左右,所以导致线程池的核心线程数是100(这是一个很大的坑,并不容易发现)
这个问题解决了,还有一个问题:为什么线程数会一直不断上涨呢?
查看项目代码:
通过查看项目代码发现,每个Curator客户端都需要一个线程池作为参数,也就是每次有新的集群创建,都会创建一个新的线程池,这个无法避免,但是可以将核心线程数改小,保证线程数的增长在可控范围内
问题解决
-
替换基础为openjdk:8u332-jdk
-
修改核心线程数为4(CPU limit为2)
一个集群会创建4个线程 以集群接入速度来看 目前是可以接受的。