项目背景

基于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个线程 以集群接入速度来看 目前是可以接受的。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐