ACK Net Exporter 与 sysAK 出击:一次深水区的网络疑难问题排查经历

作者:谢石、碎牙

不久前的一个周五的晚上,某客户A用户体验提升群正处在一片平静中,突然,一条简短的消息出现,打破了这种祥和:

“我们在ACK上创建的集群,网络经常有几百毫秒的延迟。"

偶发,延迟,几百毫秒。这三个关键字迅速集中了我们紧张的神经,来活儿了, 说时迟,那时快,我们马上就进入到了客户的问题攻坚群。

问题的排查过程

常规手段初露锋芒

客户通过钉钉群反馈之前已经进行了基本的排查,具体的现象如下:

  1. 不同的容器之间进行rpc调用时出现延迟,大部份请求在较快,客户的测试方法中,30min可以出现几十次超过100ms的延迟。
  2. 延迟的分布最大有2s,vpc方面已经进行了抓包分析,看到了间隔200ms~400ms的重传报文与出事报文在比较接近的时间里出现在node中。

30min内出现几十次的频率,确实是比较离谱的,从客户提供的信息中,我们发现了一个非常熟悉的现象:

正常发送的报文和重传的报文发出的时间相差400ms,他们在NC物理机/MOC卡上相差400ms,但是几乎同时在ecs节点中被抓包捕捉到。

在这里插入图片描述

这样的现象曾经出现过,比较常见的原因就是,ecs节点处理网络数据包的中断下半部动作慢了,按照经验通常是100ms到500ms之间,如下图所示:

  1. 在第一个NC抓包时机的时候,第一个正常的数据包到达了,并且进入了ecs。
  2. ecs的中断下半部处理程序ksoftirqd并没有及时完成处理,因为某些原因在等待。
  3. 等待时间超过了客户端的RTO,导致客户端开始发起重传,此时重传的报文也到了第一个NC抓包时机。
  4. 正常报文和重传的报文都到达了ecs内部,此时ksoftirqd恢复正常并开始收包。
  5. 正常报文和重传报文同时到达tcpdump的第二次抓包时机,因此出现了上述的现象。

在这里插入图片描述

出现了这种现象,我们的第一反应就是,肯定是有什么原因导致了节点上存在软中断工作进程的调度异常。随后我们联系客户进行复现,同时开始观察节点的CPU消耗情况(由于客户的操作系统并不是alinux,所以只能够移植net-exporter中断调度延迟检测工具net_softirq进行捕捉),在客户复现的几乎同时,我们观察到了现象:

  1. 部分CPU存在极高的sys占用率,显示占用CPU较高的进程竟然是:kubelet。
  2. 存在比较明显的软中断调度延迟,毫无疑问,也是kubelet造成的。

到这里,问题就变成了,为什么kubelet会占用这个高的sys状态的CPU。

在这里插入图片描述

sys上下文的CPU调用,通常是由于系统调用操作时,内核进行操作产生的。通过对kubelet进程进行pprof的profiling采集,我们验证了这一点,kubelet一定是在大量进行syscall,从而让内核不停的为他打工,进而干扰了ksofirqd的调度。

为了进一步定位元凶,我们使用opensnoop进行了一段时间的捕捉,查看kubelet的文件打开行为,在短短的10s中,kubelet进行了10w次以上的文件打开操作,捞了一部分kubelet尝试打开的文件,结果发现他们的格式大概是类似于这样的格式:

/sys/fs/cgroup/cpuset/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podd9639dfe_2f78_40f1_a508_af09ca0c6c90.slice/docker-bcc4b36adcd83ce909541dbcf8d16828275e94ba13eafeae77ed24543ca82aad.scope/uid_0/pid_673/cpuset.cpus

看这个路径,很明显是一个cgroupfs的文件,作为容器技术的基石,cgroup子系统的文件记录着容器运行状态的关键信息,kubelet去去读cgroup信息看起来非常合理,只是10s内进行10w次的读取操作,怎么看也不是一个合理的行为,我们对比了kubelet的版本,发现客户虽然操作系统是特殊的,但是kubelet却是很寻常,没有什么特别,然后我们对比了正常的集群中的cgroup,发现正常集群中的文件数量要远远小于客户有问题的集群:

# 客户集群的文件数量
[root@localhost ~]# find  /sys/fs/cgroup/cpu/ -name "*" | wc -l
182055

# 正常的集群的文件数量
[root@localhost ~]# find  /sys/fs/cgroup/cpu/ -name "*" | wc -l
3163

那么文件到底多在哪里呢?

我们开始对比客户的cgroup子系统与正常集群之间的差异,果然,客户集群的cgroup子系统中的文件颇有玄机,对比如下:

# 客户集群的文件
/sys/fs/cgroup/cpuset/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podd9639dfe_2f78_40f1_a508_af09ca0c6c90.slice/docker-bcc4b36adcd83ce909541dbcf8d16828275e94ba13eafeae77ed24543ca82aad.scope/uid_0/pid_673/cpuset.cpus

# 正常集群对应路径
[root@localhost ~]# ls -l /sys/fs/cgroup/systemd/kubepods.slice/kubepods-besteffort.slice/kubepods-besteffort-pod2315540d_43ae_46b2_a251_7eabe02f4456.slice/docker-261e8046e4333881a530bfd441f1776f6d7eef4a71e35cd26c7cf7b992b61155.scope/
总用量 0
-rw-r--r-- 1 root root 0 2月  27 19:57 cgroup.clone_children
-rw-r--r-- 1 root root 0 2月  27 13:34 cgroup.procs
-rw-r--r-- 1 root root 0 2月  27 19:57 notify_on_release
-rw-r--r-- 1 root root 0 2月  27 19:57 pool_size
-rw-r--r-- 1 root root 0 2月  27 19:57 tasks

很明显,客户的cgroup子系统中,比我们正常的ACK集群要多了两层,而uid/pid这样的格式,很明显是用于做用户和进程的区分,应该是客户有意为之,随后我们和客户进行了沟通,但是不巧的是,客户对这些路径的来龙去脉也不清楚,但是提供了一个很关键的信息,客户运行的容器是androd容器。虽然没有真正接触过android容器,但是很容易联想到,android对多个用户和多个Activity进行资源上的隔离,于是为了证明这些多出来的cgroup文件是由于android容器创建,我们通过eBPF进行了简单的捕捉,果然,能看到容器内的进程在进行cgroupfs的操作!

在这里插入图片描述

可以看到,pid为210716的init进程在进行cgroup的初始化动作,这个init进程很明显是客户android容器中的1号进程,随后我们查看了客户提供的操作系统内核源码,果然,客户的patch中存在对这部分不一样的cgroup层级的兼容。。

看起来问题似乎有了解释,客户的业务创建了大量非预期的cgroup文件,导致kubelet在读取这部分文件时在内核态占据了大量的计算资源,从而干扰到了正常的收包操作。

那么怎么解决呢?很明显,客户的业务改造是一件难以推动的事情,重担只能落在了kubelet上,随后kubelet组件经过多轮修改和绑定与net_rx中断错开的CPU,解决了这种频繁的偶发超时问题。

棘手问题阴魂不散

按照一般的常见剧本,问题排查到了这里,提供了绑核的解决方案后,应该是喜大普奔了,但是每年总有那么几个不常见的剧本。在进行了绑核操作之后,延迟的发生确实成了小概率事件,然而,依然有诡异的延迟以每天十多次的概率阴魂不散。

上一个现象是由于有任务在内核执行时间过久影响调度导致,我们在net_softirq的基础上,引入了内核团队的排查利器sysAK,通过sysAK的nosched工具尝试一步到位,直接找到除了kubelet之外,还有哪些任务在捣乱。

在经历漫长的等待后,发现这次问题已经和kubelet无关,新的偶发延迟现象中,大部份是具有这样的特征:

  1. 延迟大概在100ms左右,不会有之前超过500ms的延迟。
  2. 延迟发生时,不再是ksoftirqd被调度干扰,而是ksoftirqd本身就执行了很久,也就是说,ksoftirqd才是那个捣乱的任务。。。

在这里插入图片描述

上图中可以发现,在延迟发生时,ksoftirqd本身就执行了很久,而kubelet早已经润到了和网络rx无关的CPU上去了。

遇到这样的偶发问题,多年的盲猜经验让我尝试从这些偶发延迟中找一下规律,在对比了多个维度之后,我们发现这些延迟出现大致有两个特征:

  1. 出现在固定的核上,一开始是0号CPU,我们将0号CPU也隔离开之后,发现换成了24号CPU出现了一样的现象,看起来与CPU本身无关。
  2. 出现的时间差比较固定,经过我们对比发现,差不多每隔3小时10分钟左右会有一波偶发超时,而在此期间,流量并没有较大的波动。

这样奇怪的延迟,很明显已经不再是一个单纯的网络问题,需要更加有力的抓手来帮助我们定位。

硬核方法鞭辟入里

排查到这一步,已经不再是一个单纯的网络问题了,我们找到内核团队的同学们一起排查,面对这种周期性的大部分进程包括内核关键进程都出现的卡顿,我们通过sysAK nosched捕捉到了软中断执行时间过久的内核态信息:

在这里插入图片描述

从堆栈信息中可以发现ksoftirqd本身并没有执行好事很久的操作,通常让人怀疑的就是net_rx_action的内核态收包动作慢了,但是经过多轮验证,我们发现当时收包的动作并没有出现明显的变化,于是我们把目光集中在了page的分配和释放操作中。

在阅读了__free_pages_ok的代码后,我们发现了在释放page的过程中是有获取同步锁的操作,同时会进行中断的关闭,那么,如果对于page所在的zone的锁的争抢过程出现了卡顿,就会导致__free_pages_ok本身执行变慢!

static void __free_pages_ok(struct page *page, unsigned int order)
{
  unsigned long flags;
  int migratetype;
  unsigned long pfn = page_to_pfn(page);

  if (!free_pages_prepare(page, order, true))
    return;

  migratetype = get_pfnblock_migratetype(page, pfn);
    // 这里在进行进行关中断
  local_irq_save(flags);
  __count_vm_events(PGFREE, 1 << order);
  free_one_page(page_zone(page), page, pfn, order, migratetype);
  local_irq_restore(flags);
}

static void free_one_page(struct zone *zone,
        struct page *page, unsigned long pfn,
        unsigned int order,
        int migratetype)
{
    // 这里有一个同步锁
  spin_lock(&zone->lock);
  if (unlikely(has_isolate_pageblock(zone) ||
    is_migrate_isolate(migratetype))) {
    migratetype = get_pfnblock_migratetype(page, pfn);
  }
  __free_one_page(page, pfn, zone, order, migratetype, true);
  spin_unlock(&zone->lock);
}

考虑到这一点,我们打算使用sysAK irqoff来追踪是否存在我们推测的情况。在经历了好几个三小时周期的尝试后,我们终于看到了预测中的信息:

在这里插入图片描述

从上图可以很明显的查看到,在一次ksoftirqd出现延迟的同时,有一个用户进程在长时间的持有zone->lock!!!

到了这里,内核周期性产生收包进程执行时间过久的元凶也找到了,如下图:

在这里插入图片描述

当客户的icr_encoder周期性执行到pagetypeinfo_showfree_print方法时,会长时间的持有zone->lock 的锁,尽管不在相同的cpu上,但是持有page关联的zone的锁仍然会导致此时其他关联的进程产生延迟。

pagetypeinfo_showfree_print这个函数是/proc/pagetyeinfo文件注册在procfs中的方法。

当有用户态的读取/proc/pagetyeinfo操作时,内核会执行pagetypeinfo_showfree_print来获取当前page的分配数据,我们在本地环境进行测试时,发现直接访问这个文件,并不会产生比较大的延迟:

在这里插入图片描述

那么为什么读取/proc/pagetyeinfo在客户的环境中会产生这么大的耗时呢?经过对比观察,我们发现了原因:

在这里插入图片描述

上图是在客户环境中出现延迟后的pagetypeinfo的结果,可以发现一个很夸张的区别,那就是某些类型的page的数量已经到到了10w以上的数字。

而分析pagetypeinfo_showfree_print的代码可以发现,内核在进行统计时实际上是会在抢占锁的同时去遍历所有的page!!

static void pagetypeinfo_showfree_print(struct seq_file *m,
          pg_data_t *pgdat, struct zone *zone)
{
  int order, mtype;

  for (mtype = 0; mtype < MIGRATE_TYPES; mtype++) {
    for (order = 0; order < MAX_ORDER; ++order) {
          // 在持有zone->lock的情况遍历各个order的area->free_list
      area = &(zone->free_area[order]);
      list_for_each(curr, &area->free_list[mtype]) {
        if (++freecount >= 100000) {
          overflow = true;
          break;
        }
      }
      seq_printf(m, "%s%6lu ", overflow ? ">" : "", freecount);
      spin_unlock_irq(&zone->lock);
            // 可能这个问题已经有人发现,因此特地增加了支持内核态抢占的调度时机
      cond_resched();
      spin_lock_irq(&zone->lock);
    }
  }
}

从代码中不难发现,每次进行遍历之后就会进行unlock,而在下一次遍历之前会进行lock,当page数量过多的时候,就会产生占据锁过久的情况,从而引发上述的问题。

与此同时,也有一些不规律的偶发延迟的出现,我们也通过net-exporter和sysAK定位到了两个不那么规律的根因:

  1. 由于ipvs老版本的estimation_timer导致的偶发延迟,目前内核团队已经修复,详见:https://github.com/alibaba/cloud-kernel/commit/265287e4c2d2ca3eedd0d3c7c91f575225afd70f
  2. 由于多numa引发的page migration,导致进程卡顿进而产生延迟,多个类似的案例出现,后面会对这种场景进行详细的分析。

问题的背后

后续客户通过这些方式解决了大多数的延迟:

  1. 针对kubelet进行绑核操作,与net_rx的中断对应的cpu错开。
  2. 通过cronjob定期进行内存碎片的回收。

经过优化后的网络,偶发延迟的出现已经大大减少,满足了交付标准。

一个看似不经意问题的背后,其实大有玄机。在这个问题的排查中,涉及到的技术栈包括内核网络、内核内存管理、procfs虚拟文件系统等技术领域,当然更离不开阿里云内部多个技术团队的通力合作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/4431.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C++模板基础(四)

函数模板&#xff08;四&#xff09; ● 函数模板的实例化控制 – 显式实例化定义&#xff1a; template void fun(int) / template void fun(int) //header.h template<typename T> void fun(T x) {std::cout << x << std::endl; }//main.cpp #include&quo…

Python(白银时代)——文件操作

文件的基本操作 概念 在计算机中&#xff0c;文件是以 二进制 的方式保存在磁盘上的 文本文件 和 二进制文件 文本文件&#xff08;用记事本打开能直接能看懂的&#xff09; 可以使用 文本编辑软件查看 本质上还是二进制的,比如 Python的源码文件 二进制文件&#xff08;用…

并发编程(六)-AbstractExecutorService源码分析

一、AbstractExecutorService简介AbstractExecutorService是一个抽象类&#xff0c;实现了ExecutorService接口&#xff0c;提供了线程池的基本实现。它是Java Executor框架的核心类&#xff0c;提供了线程池的基本操作&#xff0c;如提交任务、管理线程池、执行任务等。自定义…

阻塞队列(BlockingQueue)的实现和使用

阻塞队列&#xff08;BlockingQueue&#xff09; 文章目录阻塞队列&#xff08;BlockingQueue&#xff09;阻塞队列的梗概解耦合和削峰填谷java代码实现一个阻塞队列阻塞队列的梗概 众所周知&#xff0c;队列是一种数据结构&#xff0c;符合先进先出的结构&#xff0c;先进先出…

【动态绘图】python可视化--丝滑版

✅作者简介&#xff1a;在读博士&#xff0c;伪程序媛&#xff0c;人工智能领域学习者&#xff0c;深耕机器学习&#xff0c;交叉学科实践者&#xff0c;周更前沿文章解读&#xff0c;提供科研小工具&#xff0c;分享科研经验&#xff0c;欢迎交流&#xff01;&#x1f4cc;个人…

鼎桥通信,拥抱基础创新的“高灵活性”时代

作者 | 曾响铃 文 | 响铃说 伴随数智化转型成为时代变革大方向&#xff0c;一批走在时代前端的数智化转型企业应运而生&#xff0c;不断丰富5G、物联网等新兴技术的应用场景&#xff0c;构建万智互联的产业生态。作为国内通信领域的引领者&#xff0c;鼎桥通信技术有限公司&a…

AF染料试剂Alexa fluor 680 PEG Biotin,AF680 PEG Biotin,荧光强度稳定利于多种荧光标记

文章关键词&#xff1a;AF染料试剂&#xff0c;AF680&#xff0c;PE-Biotin衍生物Alexa fluor 680 PEG Biotin&#xff0c;AF680 PEG Biotin | Alexa fluor 680-PEG-生物素| CAS&#xff1a;N/A | 纯度&#xff1a;95%试剂参数信息&#xff1a; CAS&#xff1a;N/A 外观&am…

docker使用

dokcer 安装 # 1、yum 包更新到最新 yum update # 2、安装需要的软件包&#xff0c; yum-util 提供yum-config-manager功能&#xff0c;另外两个是devicemapper驱动依赖的 yum install -y yum-utils device-mapper-persistent-data lvm2 # 3、 设置yum源 yum-config-manager …

精确性和准确性是两码事儿

准确性(Accuracy)是与正确答案的接近程度&#xff0c;精确性(Precision)是对这个答案的分辨率。 假设&#xff0c;你问我&#xff0c;”现在几点了?” 我抬头看看太阳&#xff0c;然后估算了一下&#xff0c;回答道 “现在是上午 10 点 35 分 22.131 秒” 我给出的是一个足…

Nacos配置中心优雅配置JSON数据格式

在我业务开发中&#xff0c;需要在配置中心配置Json数据&#xff0c;返回给前端。因Nacos默认不支持Json格式配置&#xff0c;需要搭配监听器获取配置中心Json数据&#xff0c;返回给客户端。二、搭配Nacos配置Josn数据1. bootstrap.ymlserver:port: 9000 spring:application:n…

Vue使用ElementUI对table的指定列进行合算

前言 最近有一个想法&#xff0c;就是记录自己花销的时候&#xff0c;table中有一项内容是花销的金额。然后想在table的底部有一项内容是该金额的总计。 然后我就顺着elementui的table组件寻找相关的demo&#xff0c;还真发现了一个这样的demo。 对于这个demo&#xff0c;官方…

SSH框架整合教程

工程目录结构如下&#xff1a; 本工程只介绍SSH整合的基本流程&#xff0c;所以没有写接口 1. 导入jar包 <dependencies><!--hibernate包--><dependency><groupId>org.hibernate</groupId><artifactId>hibernate-core</artifactId>…

【各种安装2】

各种安装2一、八阶段-第四章-案例导入说明1.安装MySQL1.1.准备目录1.2.运行命令1.3.修改配置1.4.重启2.导入SQL3.导入Demo工程3.1.分页查询商品3.2.新增商品3.3.修改商品3.4.修改库存3.5.删除商品3.6.根据id查询商品3.7.根据id查询库存3.8.启动4.导入商品查询页面4.1.运行nginx…

Linux线程同步与互斥(二)/生产消费者模型

⭐前言&#xff1a;本文会先后讲解生产消费者模型、条件变量和基于阻塞队列的生产消费者模型。 1.生产消费者模型 什么是生产消费者模型&#xff1f; 认识生产消费者模型 使用学生&#xff08;消费者&#xff09;&#xff0c;超市&#xff0c;供货商&#xff08;生产者&…

C++ 26 常用算法

目录 一、概述 1.1 常用遍历算法 1.1.1 算法简介 1.1.2 for_each遍历算法 1.1.3 transform遍历算法 1.2 常用查找算法 1.2.1 算法简介 1.2.2 find 查找算法 1.2.3 find_if 查找算法 1.2.4 adjacent_find 查找算法 1.2.5 binary_search 查找算法 1.2.6 count 查找算法…

【面试题】JS的一些优雅写法 reduce和map

大厂面试题分享 面试题库 前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 web前端面试题库 VS java后端面试题库大全 JS的一些优雅写法 reduce 1、可以使用 reduce 方法来实现对象数组中根据某一key值求和 …

LFM雷达实现及USRP验证【章节3:连续雷达测距测速】

第一章介绍了在相对速度为0时候的雷达测距原理 目录 1. LFM测速 1.1 雷达测速原理 1.2 Chrip信号测速 2. LFM测速代码实现 参数设置 仿真图像 matlab源码 代码分析 第一章介绍了在相对速度为0时候的雷达测距原理&#xff0c;第二章介绍了基于LFM的雷达测距原理及其实现…

数据结构第十一期——线段树的原理和应用

目录 一、前言 二、线段树的概念 1、区间最值问题RMQ (Range Minimum/Maximum Query) &#xff08;1&#xff09;暴力法 &#xff08;2&#xff09;高效的办法&#xff1a;线段树 &#xff08;3&#xff09;把数列放在二叉树上 &#xff08;4&#xff09;查询最小值的复…

43-二叉树练习-LeetCode236二叉树的最近公共祖先

题目 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&#xff08;一个节点也可以是它…

一款全新的基于GPT4的Python神器,关键还免费

chartgpt大火之后&#xff0c;随之而来的就是一大类衍生物了。 然后&#xff0c;今天要给大家介绍的是一款基于GPT4的新一代辅助编程神器——Cursor。 它最值得介绍的地方在于它免费&#xff0c;我们可以直接利用它来辅助我们编程&#xff0c;真正做到事半功倍。 注意&#…