容器内就获取个cpu利用率,怎么就占用单核100%了呢

背景:这个是在centos7 + lxcfs 和jdk11 的环境上复现的

下面列一下我们是怎么排查并解这个问题的。

一、故障现象

oppo内核团队接到jvm的兄弟甘兄发来的一个案例,

现象是java的热点一直是如下函数,占比很高:

at sun.management.OperatingSystemImpl.getProcessCpuLoad(Native Method)

我们授权后登陆oppo云宿主,发现top显示如下:

   PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
617248 service 20 0 11.9g 8.6g 9300 R 93.8 2.3 3272:09 java
179526 service 20 0 11.9g 8.6g 9300 R 93.8 2.3 77462:17 java

二、故障现象分析

java的线程,能把cpu使用率打这么高,常见一般是gc或者跑jni的死循环,

从jvm团队反馈的热点看,这个线程应该在一直获取cpu利用率,而不是以前常见的问题。

从perf的角度看,该线程一直在触发read调用。

然后strace查看read的返回值,打印如下:

read(360, 0x7f6f9000b000, 4096)         = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)

接着查看360这个fd到底是什么类型:

# ll /proc/22345/fd/360
lr-x------ 1 service service 64 Feb 4 11:24 /proc/22345/fd/360 -> /proc/stat

发现是在读取/proc/stat ,和上面的java热点读取cpu利用率是能吻合的。

根据/proc/stat在容器内的挂载点,

# cat /proc/mounts  |grep stat
tmpfs /proc/timer_stats tmpfs rw,nosuid,size=65536k,mode=755 0 0
lxcfs /proc/stat fuse.lxcfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other 0 0
lxcfs /proc/stat fuse.lxcfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other 0 0
lxcfs /proc/diskstats fuse.lxcfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other 0 0

很奇怪的是,发现有两个挂载点都是/proc/stat,

尝试手工读取一下这个文件:

# cat /proc/stat
cpu 12236554 0 0 9808893 0 0 0 0 0 0
cpu0 10915814 0 0 20934 0 0 0 0 0 0
cpu1 1320740 0 0 9787959 0 0 0 0 0 0

说明bash访问这个挂载点没有问题,难道bash访问的挂载点和出问题的java线程访问的挂载点不是同一个?

查看对应java打开的fd的super_block和mount,

crash> files 22345 |grep -w 360
360 ffff914f93efb400 ffff91541a16d440 ffff9154c7e29500 REG /rootfs/proc/stat/proc/stat
crash> inode.i_sb ffff9154c7e29500
i_sb = 0xffff9158797bf000

然后查看对应进程归属mount的namespace中的挂载点信息:

crash> mount -n 22345 |grep lxcfs
ffff91518f28b000 ffff9158797bf000 fuse lxcfs /rootfs/proc/stat
ffff911fb0209800 ffff914b3668e800 fuse lxcfs /rootfs/var/lib/baymax/var/lib/baymax/lxcfs
ffff915535becc00 ffff914b3668e800 fuse lxcfs /rootfs/proc/cpuinfo
ffff915876b45380 ffff914b3668e800 fuse lxcfs /rootfs/proc/meminfo
ffff912415b56e80 ffff914b3668e800 fuse lxcfs /rootfs/proc/uptime
ffff91558260f300 ffff914b3668e800 fuse lxcfs /rootfs/proc/stat/proc/stat
ffff911f52a6bc00 ffff914b3668e800 fuse lxcfs /rootfs/proc/diskstats
ffff914d24617180 ffff914b3668e800 fuse lxcfs /rootfs/proc/swaps
ffff911de87d0180 ffff914b3668e800 fuse lxcfs /rootfs/sys/devices/system/cpu/online

由于cat操作较短,写一个简单的demo代码,open 之后不关闭并且read 对应的/proc/stat路径,发现它

访问的挂载点的super_blcok是 ffff914b3668e800,由于代码极为简单,在此不列出。

也就是进一步地确认了,出问题的java进程访问的是一个出问题的挂载点。

那为什么访问这个挂载点会返回ENOTCONN呢?

crash> struct file.private_data ffff914f93efb400
private_data = 0xffff911e57b49b80---根据fuse模块代码,这个是一个fuse_file crash> fuse_file.fc 0xffff911e57b49b80
fc = 0xffff9158797be000
crash> fuse_conn.connected 0xffff9158797be000
connected = 0

果然对应的fuse_conn的连接状态是0,从打点看,是在fuse_get_req返回的:

调用链如下:

sys_read-->vfs_read-->fuse_direct_read-->__fuse_direct_read-->fuse_direct_io
--->fuse_get_req--->__fuse_get_req static struct fuse_req *__fuse_get_req(struct fuse_conn *fc, unsigned npages,
bool for_background)
{
...
err = -ENOTCONN;
if (!fc->connected)
goto out;
...
}

下面要定位的就是,为什么出问题的java线程会没有判断read的返回值,而不停重试呢?

gdb跟踪一下:

(gdb) bt
#0 0x00007fec3a56910d in read () at ../sysdeps/unix/syscall-template.S:81
#1 0x00007fec3a4f5c04 in _IO_new_file_underflow (fp=0x7fec0c01ae00) at fileops.c:593
#2 0x00007fec3a4f6dd2 in __GI__IO_default_uflow (fp=0x7fec0c01ae00) at genops.c:414
#3 0x00007fec3a4f163e in _IO_getc (fp=0x7fec0c01ae00) at getc.c:39
#4 0x00007febeacc6738 in get_totalticks.constprop.4 () from /usr/local/paas-agent/HeyTap-Linux-x86_64-11.0.7/lib/libmanagement_ext.so
#5 0x00007febeacc6e47 in Java_com_sun_management_internal_OperatingSystemImpl_getSystemCpuLoad0 ()
from /usr/local/paas-agent/HeyTap-Linux-x86_64-11.0.7/lib/libmanagement_ext.so

确认了用户态在循环进入 _IO_getc的流程,然后jvm的甘兄开始走查代码,发现有一个代码非常可疑:

UnixOperatingSystem.c:get_totalticks 函数,会有一个循环如下:

static void next_line(FILE *f) {
while (fgetc(f) != '\n');
}

从fuse模块的代码看,fc->connected = 0的地方并不是很多,基本都是abort或者fuse_put_super调用导致,通过对照java线程升高的时间点以及查看journal的日志,进一步fuse的挂载点失效有用户态文件系统lxcfs退出,而挂载点还有inode在访问导致,导致又无法及时地回收这个super_block,新的挂载使用的是:

remount_container() {
export f=/proc/stat && test -f /var/lib/baymax/lxcfs/$f && (umount $f; mount --bind /var/lib/baymax/lxcfs/$f $f)

使用了bind模式,具体bind的mount的特性可以参照网上的资料。

并且在journal日志中,查看到了关键的:

: umount: /proc/stat: target is busy

这行日志表明,当时卸载时出现失败,不是所有的访问全部关闭。

三、故障复现

1.针对以上的怀疑点,写了一个很简单的demo如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define SCNd64 "I64d"
#define DEC_64 "%"SCNd64 static void next_line(FILE *f) {
while (fgetc(f) != '\n');//出错的关键点
} #define SET_VALUE_GDB 10
int main(int argc,char* argv[])
{
unsigned long i =1;
unsigned long j=0;
FILE * f=NULL;
FILE *fh;
unsigned long userTicks, niceTicks, systemTicks, idleTicks;
unsigned long iowTicks = 0, irqTicks = 0, sirqTicks= 0;
int n; if((fh = fopen("/proc/stat", "r")) == NULL) {
return -1;
} n = fscanf(fh, "cpu " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " "
DEC_64 " " DEC_64,
&userTicks, &niceTicks, &systemTicks, &idleTicks,
&iowTicks, &irqTicks, &sirqTicks); // Move to next line
while(i!=SET_VALUE_GDB)----------如果挂载点不变,则走这个大循环,单核cpu接近40%
{
next_line(fh);//挂载点一旦变化,返回 ENOTCONN,则走这个小循环,单核cpu会接近100%
j++;
} fclose(fh);
return 0;
}

执行以上代码,获得结果如下:

#gcc -g -o caq.o caq.c
一开始单独运行./caq.o,会看到cpu占用如下:
628957 root 20 0 8468 612 484 R 32.5 0.0 18:40.92 caq.o
发现cpu占用率时32.5左右,
此时挂载点信息如下:
crash> mount -n 628957 |grep lxcfs
ffff88a5a2619800 ffff88a1ab25f800 fuse lxcfs /rootfs/proc/stat
ffff88cf53417000 ffff88a4dd622800 fuse lxcfs /rootfs/var/lib/baymax/var/lib/baymax/lxcfs
ffff88a272f8c600 ffff88a4dd622800 fuse lxcfs /rootfs/proc/cpuinfo
ffff88a257b28900 ffff88a4dd622800 fuse lxcfs /rootfs/proc/meminfo
ffff88a5aff40300 ffff88a4dd622800 fuse lxcfs /rootfs/proc/uptime
ffff88a3db2bd680 ffff88a4dd622800 fuse lxcfs /rootfs/proc/stat/proc/stat
ffff88a2836ba400 ffff88a4dd622800 fuse lxcfs /rootfs/proc/diskstats
ffff88bcb361b600 ffff88a4dd622800 fuse lxcfs /rootfs/proc/swaps
ffff88776e623480 ffff88a4dd622800 fuse lxcfs /rootfs/sys/devices/system/cpu/online 由于没有关闭/proc/stat的fd,也就是进行大循环,然后这个时候重启lxcfs挂载:
#systemctl restart lxcfs
重启之后,发现挂载点信息如下:
crash> mount -n 628957 |grep lxcfs
ffff88a5a2619800 ffff88a1ab25f800 fuse lxcfs /rootfs/proc/stat
ffff88a3db2bd680 ffff88a4dd622800 fuse lxcfs /rootfs/proc/stat/proc/stat------------这个挂载点,由于fd未关闭,所以卸载肯定失败,可以看到super_block是重启前的
ffff887795a8f600 ffff88a53b6c6800 fuse lxcfs /rootfs/var/lib/baymax/var/lib/baymax/lxcfs
ffff88a25472ae80 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/cpuinfo
ffff88cf75ff1e00 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/meminfo
ffff88a257b2ad00 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/uptime
ffff88cf798f0d80 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/stat/proc/stat/proc/stat--------bind模式挂载,会新生成一个/proc/stat
ffff88cf36ff2880 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/diskstats
ffff88cf798f1f80 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/swaps
ffff88a53f295980 ffff88a53b6c6800 fuse lxcfs /rootfs/sys/devices/system/cpu/online
cpu立刻打到接近100%
628957 root 20 0 8468 612 484 R 98.8 0.0 18:40.92 caq.o

四、故障规避或解决

我们的解决方案是:

1.在fgetc 中增加判断。

2.由于lxcfs在3.1.2 的版本会有段错误导致退出的bug,尽量升级到4.0.0以上版本。

3.在lxcfs进行remount的时候,如果遇到umount失败,则延迟加重试,超过次数通过

日志告警系统抛出告警。

openjdk的bug的更多相关文章

  1. 码农飞升记-04-OracleJDK 与 OpenJDK 的区别和联系以及 OracleJDK builds 与其他 OpenJDK builds 的选择问题

    在前两篇 OracleJDK是什么?OracleJDK的版本怎么选择? 和 OpenJDK是什么? 中分别介绍了 OracleJDK 和 OpenJDK 的来历以及概念,那可能就有小伙伴要问了:那我到 ...

  2. Java 8 Lambda 揭秘

    再了解了Java 8 Lambda的一些基本概念和应用后, 我们会有这样的一个问题: Lambda表达式被编译成了什么?. 这是一个有趣的问题,涉及到JDK的具体的实现. 本文将介绍OpenJDK对L ...

  3. [翻译]Java排错指南 - 5 确定崩溃何地发生

    原文地址: https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/crashes001.html 这几天公司其他组遇到 ...

  4. 由「Metaspace容量不足触发CMS GC」从而引发的思考

    https://mp.weixin.qq.com/s/1VP7l9iuId_ViP1Z_vCA-w 某天早上,毛老师在群里问「cat 上怎么看 gc」. 好好的一个群 看到有 GC 的问题,立马做出小 ...

  5. JVM源码分析-JVM源码编译与调试

    要分析JVM的源码,结合资料直接阅读是一种方式,但是遇到一些想不通的场景,必须要结合调试,查看执行路径以及参数具体的值,才能搞得明白.所以我们先来把JVM的源码进行编译,并能够使用GDB进行调试. 编 ...

  6. 码农飞升记-03-OpenJDK是什么?

    目录 1.OpenJDK 概述 2.OpenJDK 的发展史 3.OpenJDK Community 1.角色定义 Participant(参与者) Contributor(贡献者) OpenJDK ...

  7. 我惊了!CompletableFuture居然有性能问题!

    你好呀,我是歪歪. 国庆的时候闲来无事,就随手写了一点之前说的比赛的代码,目标就是保住前 100 混个大赛的文化衫就行了. 现在还混在前 50 的队伍里面,稳的一比. 其实我觉得大家做柔性负载均衡那题 ...

  8. Linux 下编译openjdk

    操作系统ubuntu14.04 openjdk版本 7u4 openjdk7u4可以在https://jdk7.java.net/source.html下载   一.构建编译环境 sudo apt-g ...

  9. Elasticsearch升级1.5版本暴露jdk的bug

    把测试环境的Elasticsearch升级到1.5.1版本,启动的时候报错: [root@node2 elasticsearch-1.5.1]# bin/service/elasticsearch s ...

随机推荐

  1. Keil软件下用Jlink无法识别芯片

    Keil软件下用Jlink无法识别芯片 硬件:正点原子探索者 软件:keil J-Link固件版本:V9.40 J-Link V6.94b驱动:下载地址 跟着视频教程走,发现的第一个问题就是这个,记录 ...

  2. go-zero 微服务实战系列(一、开篇)

    前言 在社区中经常看到有人问有没有基于 go-zero 的比较完整的项目参考,该类问题本质上是想知道基于 go-zero 的项目的最佳实践.完整的项目应该是一个完整的产品功能,包含产品需求.架构设计. ...

  3. sklearn练习1 回归

    from sklearn.svm import SVR from sklearn.pipeline import make_pipeline from sklearn.preprocessing im ...

  4. Eureka属性配置

    一:Eureka Instance实例信息配置   里面的配置以"-"隔开 其实也支持驼峰命名代替"-" 首先是入门时的配置: server: port: 80 ...

  5. 2021蓝桥杯省赛C++A组试题E 回路计数 状态压缩DP详细版

    2021蓝桥杯省赛C++A组试题E 回路计数 状态压缩DP 题目描述 蓝桥学院由21栋教学楼组成,教学楼编号1到21.对于两栋教学楼a和b,当a和b互质时,a和b之间有一条走廊直接相连,两个方向皆可通 ...

  6. python:selenium测试登录在chrome中闪退

    问题描述:使用selenium.webdriver时测试网页,进行自动登录测试总是在登录成功时闪退.使用指定驱动器位置的方式chrome也会闪退 1.正常使用chrome驱动打开一个网页,正常访问 f ...

  7. vue大型电商项目尚品汇(后台篇)day05

    今天继续是对后台管理部分的一个操作,但是快要结束了,今天结束,明天会进入一个从Vue以来,另外一个名声显著的东西了,一只耳闻从未见识,而且十分的炫酷 他就是------数据可视化Echarts,迫不及 ...

  8. Vue3.0系列——「vue3.0学习手册」第一期

    一.项目搭建 vite是尤大大开发的一款意图取代webpack的工具.其实现原理是利用ES6的import发送请求加载文件的特性.拦截这些请求,做一些编译,省去webpack冗长的打包时间.并将其与R ...

  9. python采集A站m3u8视频格式视频

    基本开发环境 (https://jq.qq.com/?_wv=1027&k=NofUEYzs) Python 3.6 Pycharm 相关模块的使用 (https://jq.qq.com/?_ ...

  10. JDBCTools 第一个版本

    JDBCToolV1: package com.dgd.test; import com.alibaba.druid.pool.DruidDataSourceFactory; import javax ...