Linux内核相关笔记
内核参数、内核与TCP、内核动态追踪等。
sysctl修改内核参数
sysctl 命令来自英文词组 system control 的缩写,其功能是配置系统内核参数。
sysctl 命令能够在 Linux 系统运行时动态地配置系统内核参数,包含 TCP/IP 堆栈和虚拟内存系统等选项,可让有经验的系统管理员更好地优化整台服务器性能,但请注意,配置结果仅在当前生效, 系统重启后参数将恢复到初始状态 , 要想永久生效 ,则需要将参数写入 /etc/sysctl.conf 系统文件。
查看所有:
bash
sysctl -a
查看特定参数:
bash
sysctl xxx
参数名与 /etc/sysctl.conf 等号左边部分一致。
示例:
如果希望屏蔽别人 ping 你的主机,则加入以下代码:
bash
net.ipv4.icmp_echo_ignore_all = 1
编辑完成后,请执行以下命令使变动立即生效:
bash
/sbin/sysctl -p
永久生效,重启机器后仍生效。
临时生效:
bash
/sbin/sysctl -w net.ipv4.route.flush=1
重启机器后失效。
内核参数与TCP
TCP连接的建立断开受哪些系统配置影响?阐释一些影响网络协议栈的linux内核参数的配置和使用。极客时间课程学习笔记摘要。
上图就是一个 TCP 连接的建立过程。TCP 连接的建立是一个从 Client 侧调用 connect(), 到 Server 侧 accept() 成功返回的过程。你可以看到,在整个 TCP 建立连接的过程中,各个行为都有配置选项来进行控制。
Client 调用 connect() 后,Linux 内核就开始进行三次握手。
首先 Client 会给 Server 发送一个 SYN 包,但是该 SYN 包可能会在传输过程中丢失,或者因为其他原因导致 Server 无法处理,此时 Client 这一侧就会触发超时重传机制。但是也不能一直重传下去,重传的次数也是有限制的,这就是 tcp_syn_retries 这个配置项来决定的。 假设 tcp_syn_retires 为 3,那么 SYN 包重传的策略大致如下:
在 Client 发出 SYN 后,如果过了 1 秒 ,还没有收到 Server 的响应,那么就会进行第一次重传;如果经过 2s 的时间还没有收到 Server 的响应,就会进行第二次重传;一直重传 tcp_syn_retries 次。
对于 tcp_syn_retries 为 3 而言,总共会重传 3 次,也就是说从第一次发出 SYN 包后,会 一直等待(1 + 2 + 4 + 8)秒,如果还没有收到 Server 的响应,connect() 就会产生 ETIMEOUT 的错误。
tcp_syn_retries 的默认值是 6,也就是说如果 SYN 一直发送失败,会在(1 + 2 + 4 + 8 + 16+ 32 + 64)秒,即 127 秒后产生 ETIMEOUT 的错误。
我们在生产环境上就遇到过这种情况,Server 因为某些原因被下线,但是 Client 没有被通知到,所以 Client 的 connect() 被阻塞 127s 才去尝试连接一个新的 Server, 这么长的超时等待时间对于应用程序而言是很难接受的。
所以通常情况下,我们都会将数据中心内部服务器的 tcp_syn_retries 给调小,这里推荐设置为 2,来减少阻塞的时间。因为对于数据中心而言,它的网络质量是很好的,如果得不到 Server 的响应,很可能是 Server 本身出了问题。在这种情况下,Client 及早地去尝试连接其他的 Server 会是一个比较好的选择,所以对于客户端而言,一般都会做如下调整:
net.ipv4.tcp_syn_retries = 2
有些情况下 1s 的阻塞时间可能都很久,所以有的时候也会将三次握手的初始超时时间从默认值 1s 调整为一个较小的值,比如 100ms,这样整体的阻塞时间就会小很多。这也是数据中心内部经常进行一些网络优化的原因。
如果 Server 没有响应 Client 的 SYN,除了我们刚才提到的 Server 已经不存在了这种情况外,还有可能是因为 Server 太忙没有来得及响应,或者是 Server 已经积压了太多的半连接(incomplete)而无法及时去处理。
半连接,即收到了 SYN 后还没有回复 SYNACK 的连接,Server 每收到一个新的 SYN 包,都会创建一个半连接,然后把该半连接加入到半连接队列(syn queue)中。syn queue 的长度就是 tcp_max_syn_backlog 这个配置项来决定的,当系统中积压的半连接个数超过了该值后,新的 SYN 包就会被丢弃。
对于服务器而言,可能瞬间会有非常多的新建连接,所以我们可以适当地调大该值,以免 SYN 包被丢弃而导致 Client 收不到SYNACK:
net.ipv4.tcp_max_syn_backlog = 16384
Server 中积压的半连接较多,也有可能是因为有些恶意的 Client 在进行 SYN Flood 攻 击。
典型的 SYN Flood 攻击如下:Client 高频地向 Server 发 SYN 包,并且这个 SYN 包的源 IP 地址不停地变换,那么 Server 每次接收到一个新的 SYN 后,都会给它分配一个半连接,Server 的 SYNACK 根据之前的 SYN 包找到的是错误的 Client IP, 所以也就无法收到 Client 的 ACK 包,导致无法正确建立 TCP 连接,这就会让 Server 的半连接队列耗尽,无法响应正常的 SYN 包。
为了防止 SYN Flood 攻击,Linux 内核引入了 SYN Cookies 机制。SYN Cookie 的原理是什么样的呢?
在 Server 收到 SYN 包时,不去分配资源来保存 Client 的信息,而是根据这个 SYN 包计算出一个 Cookie 值,然后将 Cookie 记录到 SYNACK 包中发送出去。
对于正常的连接, 该 Cookies 值会随着 Client 的 ACK 报文被带回来。然后 Server 再根据这个 Cookie 检查这个 ACK 包的合法性,如果合法,才去创建新的 TCP 连接。
通过这种处理,SYN Cookies 可以防止部分 SYN Flood 攻击。所以对于 Linux 服务器而言,推荐开启 SYN Cookies:
net.ipv4.tcp_syncookies = 1
Server 向 Client 发送的 SYNACK 包也可能会被丢弃,或者因为某些原因而收不到 Client 的响应,这个时候 Server 也会重传 SYNACK 包。同样地,重传的次数也是由配置选项来控制的,该配置选项是 tcp_synack_retries。
tcp_synack_retries 的重传策略跟我们在前面讲的 tcp_syn_retries 是一致的,所以我们就不再画图来讲解它了。它在系统中默认是 5,对于数据中心的服务器而言,通常都不需要这么大的值,推荐设置为 2 :
net.ipv4.tcp_synack_retries = 2
Client 在收到 Server 的 SYNACK 包后,就会发出 ACK,Server 收到该 ACK 后,三次握手就完成了,即产生了一个 TCP 全连接(complete),它会被添加到全连接队列 (accept queue)中。然后 Server 就会调用 accept() 来完成 TCP 连接的建立。
但是,就像半连接队列(syn queue)的长度有限制一样,全连接队列(accept queue) 的长度也有限制,目的就是为了防止 Server 不能及时调用 accept() 而浪费太多的系统资源。
全连接队列(accept queue)的长度是由 listen(sockfd, backlog) 这个函数里的 backlog 控制的,而该 backlog 的最大值则是 somaxconn。somaxconn 在 5.4 之前的内核中, 默认都是 128(5.4 开始调整为了默认 4096),建议将该值适当调大一些:
net.core.somaxconn = 16384
当服务器中积压的全连接个数超过该值后,新的全连接就会被丢弃掉。Server 在将新连接丢弃时,有的时候需要发送 reset 来通知 Client,这样 Client 就不会再次重试了。
不过, 默认行为是直接丢弃不去通知 Client。至于是否需要给 Client 发送 reset,是由 tcp_abort_on_overflow 这个配置项来控制的,该值默认为 0,即不发送 reset 给 Client。推荐也是将该值配置为 0:
net.ipv4.tcp_abort_on_overflow = 0
这是因为,Server 如果来不及 accept() 而导致全连接队列满,这往往是由瞬间有大量新建连接请求导致的,正常情况下 Server 很快就能恢复,然后 Client 再次重试后就可以建连成功了。也就是说,将 tcp_abort_on_overflow 配置为 0,给了 Client 一个重试的机会。 当然,你可以根据你的实际情况来决定是否要使能该选项。
accept() 成功返回后,一个新的 TCP 连接就建立完成了,TCP 连接进入到了 ESTABLISHED 状态:
上图就是从 Client 调用 connect(),到 Server 侧 accept() 成功返回这一过程中的 TCP 状态转换。这些状态都可以通过 netstat 或者 ss 命令来看。至此,Client 和 Server 两边就可以正常通信了。
接下来,我们看下 TCP 连接断开过程中会受哪些系统配置项的影响。
如上所示,当应用程序调用 close() 时,会向对端发送 FIN 包,然后会接收 ACK;对端也会调用 clsoe() 来发送 FIN,然后本端也会向对端回 ACK,这就是 TCP 的四次挥手过程。
首先调用 close() 的一侧是 active close(主动关闭);而接收到对端的 FIN 包后再调用 close() 来关闭的一侧,称之为 passive close(被动关闭)。
在四次挥手的过程中,有三 个 TCP 状态需要额外关注,就是上图中深红色的那三个状态:主动关闭方的 FIN_WAIT_2 和 TIME_WAIT,以及被动关闭方的 CLOSE_WAIT 状态。除了 CLOSE_WAIT 状态外,其余两个状态都有对应的系统配置项来控制。
我们首先来看 FIN_WAIT_2 状态,TCP 进入到这个状态后,如果本端迟迟收不到对端的 FIN 包,那就会一直处于这个状态,于是就会一直消耗系统资源。Linux 为了防止这种资源的开销,设置了这个状态的超时时间 tcp_fin_timeout,默认为 60s,超过这个时间后就会自动销毁该连接。
至于本端为何迟迟收不到对端的 FIN 包,通常情况下都是因为对端机器出了问题,或者是因为太繁忙而不能及时 close()。所以,通常我们都建议将 tcp_fin_timeout 调小一些,以尽量避免这种状态下的资源开销。对于数据中心内部的机器而言,将它调整为 2s 足以:
net.ipv4.tcp_fin_timeout = 2
我们再来看 TIME_WAIT 状态,TIME_WAIT 状态存在的意义是:最后发送的这个 ACK 包可能会被丢弃掉或者有延迟,这样对端就会再次发送 FIN 包。如果不维持 TIME_WAIT 这 个状态,那么再次收到对端的 FIN 包后,本端就会回一个 Reset 包,这可能会产生一些异常。
所以维持 TIME_WAIT 状态一段时间,可以保障 TCP 连接正常断开。TIME_WAIT 的默认 存活时间在 Linux 上是 60s(TCP_TIMEWAIT_LEN),这个时间对于数据中心而言可能还是有些长了,所以有的时候也会修改内核做些优化来减小该值,或者将该值设置为可通过 sysctl 来调节。
TIME_WAIT 状态存在这么长时间,也是对系统资源的一个浪费,所以系统也有配置项来限制该状态的最大个数,该配置选项就是 tcp_max_tw_buckets。对于数据中心而言,网络是相对很稳定的,基本不会存在 FIN 包的异常,所以建议将该值调小一些:
net.ipv4.tcp_max_tw_buckets = 10000
Client 关闭跟 Server 的连接后,也有可能很快再次跟 Server 之间建立一个新的连接,而由于 TCP 端口最多只有 65536 个,如果不去复用处于 TIME_WAIT 状态的连接,就可能在快速重启应用程序时,出现端口被占用而无法创建新连接的情况。所以建议你打开复用 TIME_WAIT 的选项:
net.ipv4.tcp_tw_reuse = 1
还有另外一个选项 tcp_tw_recycle 来控制 TIME_WAIT 状态,但是该选项是很危险的,因为它可能会引起意料不到的问题,比如可能会引起NAT 环境下的丢包问题。所以建议将该选项关闭:
net.ipv4.tcp_tw_recycle = 0
因为打开该选项后引起了太多的问题,所以新版本的内核就索性删掉了这个配置选项。
对于 CLOSE_WAIT 状态而言,系统中没有对应的配置项。但是该状态也是一个危险信号, 如果这个状态的 TCP 连接较多,那往往意味着应用程序有 Bug,在某些条件下没有调用 close() 来关闭连接。我们在生产环境上就遇到过很多这类问题。
所以,如果你的系统中存 在很多 CLOSE_WAIT 状态的连接,那你最好去排查一下你的应用程序,看看哪里漏掉了 close()。
至此,TCP 四次挥手过程中需要注意的事项也讲完了。
当然了,有些配置项也是可以根据你的服务器负载以及 CPU 和内存大小来做灵活配置的, 比如 tcp_max_syn_backlog、somaxconn、tcp_max_tw_buckets 这三项,如果你的物理内存足够大、CPU 核数足够多,你可以适当地增大这些值,这些往往都是一些经验值。
另外,我们这堂课的目的不仅仅是为了让你去了解这些配置项,最主要的是想让你了解其背后的机制,这样你在遇到一些问题时,就可以有一个大致的分析方向。
附图,部分重要参数汇总:
linux内核参数调优注释
#使用cookie 缓解syn flood攻击 必须设置为1
net.ipv4.tcp_syncookies = 1
#半连接的队列长度 调大容纳更多客户端syn请求 无标准 可以设置大一点
net.ipv4.tcp_max_syn_backlog = 262144
#客户端重试syn的次数 让客户端早点放弃重试 设置1-3 如果是内网设置1-2 如果是外网2-3
net.ipv4.tcp_syn_retries = 1
#收不到client回复 服务端重试次数 早点放弃 设置1-3 如果是内网设置1-2 如果是外网2-3
net.ipv4.tcp_synack_retries = 1
#全连接队列长度 超过长度会丢弃,可以设置大点
net.core.somaxconn = 65500
#收不到对端的FIN包 尽早放弃 设置1-3 如果是内网设置1-2 如果是外网2-3
net.ipv4.tcp_fin_timeout = 1
#限制TIME_WAIT状态数量 不能设置太大 可以几千到几万之间
net.ipv4.tcp_max_tw_buckets = 6000
#复用处于TIME_WAIT状态的连接 避免本机端口不足 必须设置为1
#允许作为客户端的新连接,在安全条件下使 用 TIME_WAIT 状态下的端口
net.ipv4.tcp_tw_reuse = 1
#要想使 tcp_tw_reuse 生效,还得把 timestamps 参数设置为 1,满足安全复用的先决条件(对方也要打开 tcp_timestamps )
net.ipv4.tcp_timestamps = 1
#新版本已废弃 会引起意料不到的问题,比如可能会引起NAT环境下的丢包问题 设置为0
#Linux 4.12 版本后,直接取消了这一参数 它并不要求 TIME_WAIT 状态存在 60 秒,很容易导致数据错乱,不建议设置为 1
net.ipv4.tcp_tw_recycle = 0
#所有协议类型读的最大套接字缓冲大小 单位字节 为了支撑10Gb/s 设置为16MB或更高
net.core.rmem_max = 16777216
#所有协议类型写的最大套接字缓冲大小
net.core.wmem_max = 16777216
#启用TCP接收缓冲的自动调整
net.ipv4.tcp_moderate_rcvbuf = 1
#为TCP读缓冲设置自动调优参数
net.ipv4.tcp_rmem = 4096 87380 16777216
#为TCP写缓冲设置自动调优参数
net.ipv4.tcp_wmem = 4096 65535 16777216
#接收缓冲区调节时,判断空闲内存的多少 数值大有利于更多地利用内存 具体多少合适,不确定
net.ipv4.tcp_mem = 94500000 915000000 927000000
#以下两个,有没有用,还不确定
net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
#增加每个cpu的网络设备积压队列长度 至少比tcp_max_syn_backlog大
net.core.netdev_max_backlog = 262144
#sack和fack能在高延时的网络中提高吞吐性能,以一定的cpu资源为代价
net.ipv4.tcp_sack = 1
net.ipv4.tcp_fack = 1
#打开tfo功能 减少握手的rtt 需要客户端和服务端同时支持,否则回退到传统的三次握手
net.ipv4.tcp_fastopen = 3
#Linux 系统为防止孤儿连接过多,导致系统资源长期被占用,就提供了 tcp_max_orphans 参数。如果孤儿连接数量大于它,新增的孤儿连接将不再走四次挥手,而是直接发送 RST 复位报文强制关闭。具体多少合适?还不清楚
net.ipv4.tcp_max_orphans = 3276800
#内核会定时重发 FIN 报文,重发次数默认为0表示8次
#不止对孤儿连接有效,对所有 FIN_WAIT1 状态下的连接都有效
net.ipv4.tcp_orphan_retries = 0
#扩充窗口大小 配置设为 1,此时窗口的最大值可以达到 1GB
net.ipv4.tcp_window_scaling = 1
#作为客户端的主机 扩大客户端的端口范围
net.ipv4.ip_local_port_range = 1024 65000
#默认7200秒 在某些需要快速检测连接断开的高可用性场景中,可能需要降低
#设置 TCP 连接在多长时间的空闲状态下开始发送 keep-alive 探测包
net.ipv4.tcp_keepalive_time = 30
#当设置为 1 时,表示启用 IP 转发功能,即允许 Linux 系统将接收到的 IP 数据包转发到其他目的地。这通常用于配置路由器或 NAT(网络地址转换)设备。如果设置为 0,则表示禁止 IP 转发,这是大多数默认的 Linux 系统设置,以避免不必要的带宽使用和潜在的安全风险。
net.ipv4.ip_forward = 0
#用于设置反向路径过滤(Reverse Path Filtering,简称 RPF)的默认行为。RPF 是一种安全特性,用于防止 IP 欺骗攻击,确保数据包的源地址是可达的,并且数据包的反向路径是正确的。大多数 Linux 发行版将此参数设置为 1,以提供额外的安全保护。但是,在某些网络配置中,如使用非对称路由或复杂的网络拓扑时,可能需要将其设置为 2 或 0。
net.ipv4.conf.default.rp_filter = 1
#默认为0 出于安全考虑,大多数现代系统默认禁用接受源路由数据包,以避免潜在的路由攻击和 IP 欺骗。
net.ipv4.conf.default.accept_source_route = 0
#SysRq 是一个内建于 Linux 内核的调试工具,它允许用户通过特定的键盘组合键发送低级系统命令,帮助诊断和修复系统问题。为了确保系统安全,可能需要完全禁用 SysRq 功能。
kernel.sysrq = 0
#当设置为 1 时,生成的核心转储文件名将会是 core.PID,其中 PID 是崩溃进程的进程 ID。这有助于区分同一程序可能产生的多个核心转储文件。
kernel.core_uses_pid = 1
#消息队列中单个消息的最大字节数 默认为65536 字节
kernel.msgmnb = 65536
#消息队列中单个消息的最大字节数 默认8192
kernel.msgmax = 65536
#用于定义单个共享内存段的最大尺寸 通常设置为物理内存的一半 在64位系统上,最大值可以设置为物理内存大小减去1字节 下面这个设置已经超过了最大值 效果是什么,还不清楚
kernel.shmmax = 68719476736
#控制着系统可以分配的共享内存段的总页数 通常设置为系统的物理内存大小除以页的大小。例如,如果系统有 16GB 的内存,那么 kernel.shmall 可以设置为 16GB * 1024 * 1024 / 4KB = 4194304 页 下面这个设置已经超过了最大值 效果是什么,还不清楚
kernel.shmall = 4294967296
#用于设置系统中可以打开的文件描述符的最大数量 并不是一个参数就能单独决定 可以设置大一点 确保本参数不是瓶颈
fs.file-max = 1024000
Linux动态追踪
极客时间学习课程,笔记摘要。
动态追踪技术,通过探针机制,来采集内核或者应用程序的运行信息,从而可以不用修改内核和应用程序的代码,就获得丰富的信息,帮你分析、定位想要排查的问题。
所谓动态追踪,就是在系统或应用程序正常运行时,通过内核中提供的探针来动态追踪它们的行为,从而辅助排查出性能瓶颈。
而在 Linux 系统中,常见的动态追踪方法包括 ftrace、perf、eBPF 以及 SystemTap 等。当你已经定位了某个内核函数,但不清楚它的实现原理时,就可以用 ftrace 来跟踪它的执行过程。
实际上,很多人只看到了 strace 简单易用的好处,却忽略了它对进程性能带来的影响。从原理上来说,strace 基于系统调用 ptrace 实现,这就带来了两个问题。
- 由于 ptrace 是系统调用,就需要在内核态和用户态切换。当事件数量比较多时,繁忙的切换必然会影响原有服务的性能;
- ptrace 需要借助 SIGSTOP 信号挂起目标进程。这种信号控制和进程挂起,会影响目标进程的行为。
所以,在性能敏感的应用(比如数据库)中,我并不推荐你用 strace (或者其他基于 ptrace 的性能工具)去排查和调试。
在 strace 的启发下,结合内核中的 utrace 机制, perf 也提供了一个 trace 子命令,是取代 strace 的首选工具。相对于 ptrace 机制来说,perf trace 基于内核事件,自然要比进程跟踪的性能好很多。
ftrace 和 perf 的功能已经比较丰富了,不过,它们有一个共同的缺陷,那就是不够灵活,没法像 DTrace 那样通过脚本自由扩展。
而 eBPF 就是 Linux 版的 DTrace,可以通过 C 语言自由扩展(这些扩展通过 LLVM 转换为 BPF 字节码后,加载到内核中执行)。下面这张图,就表示了 eBPF 追踪的工作原理:
从图中你可以看到,eBPF 的执行需要三步:
- 从用户跟踪程序生成 BPF 字节码;
- 加载到内核中运行;
- 向用户空间输出结果。
所以,从使用上来说,eBPF 要比我们前面看到的 ftrace 和 perf ,都更加繁杂。
实际上,在 eBPF 执行过程中,编译、加载还有 maps 等操作,对所有的跟踪程序来说都是通用的。把这些过程通过 Python 抽象起来,也就诞生了 BCC(BPF Compiler Collection)。
BCC 把 eBPF 中的各种事件源(比如 kprobe、uprobe、tracepoint 等)和数据操作(称为 Maps),也都转换成了 Python 接口(也支持 lua)。这样,使用 BCC 进行动态追踪时,编写简单的脚本就可以了。
不过要注意,因为需要跟内核中的数据结构交互,真正核心的事件处理逻辑,还是需要我们用 C 语言来编写。
eBPF 和 BCC 的使用,其实比 ftrace 和 perf 有更高的门槛。想用 BCC 开发自己的动态跟踪程序,至少要熟悉 C 语言、Python 语言、被跟踪事件或函数的特征(比如内核函数的参数和返回格式)以及 eBPF 提供的各种数据操作方法。
不过,因为强大的灵活性,虽然 eBPF 在使用上有一定的门槛,却也无法阻止它成为目前最热门、最受关注的动态追踪技术。
当然,BCC 软件包也内置了很多已经开发好的实用工具,默认安装到 /usr/share/bcc/tools/ 目录中,它们的使用场景如下图所示:
这些工具,一般都可以直接拿来用。而在编写其他的动态追踪脚本时,它们也是最好的参考资料。不过,有一点需要你特别注意,很多 eBPF 的新特性,都需要比较新的内核版本(如下图所示)。如果某些工具无法运行,很可能就是因为使用了当前内核不支持的特性。
除了前面提到的 ftrace、perf、eBPF 和 BCC 外,SystemTap 和 sysdig 也是常用的动态追踪工具。
SystemTap 也是一种可以通过脚本进行自由扩展的动态追踪技术。在 eBPF 出现之前,SystemTap 是 Linux 系统中,功能最接近 DTrace 的动态追踪机制。不过要注意,SystemTap 在很长时间以来都游离于内核之外(而 eBPF 自诞生以来,一直根植在内核中)。
所以,从稳定性上来说,SystemTap 只在 RHEL 系统中好用,在其他系统中则容易出现各种异常问题。当然,反过来说,支持 3.x 等旧版本的内核,也是 SystemTap 相对于 eBPF 的一个巨大优势。
sysdig 则是随着容器技术的普及而诞生的,主要用于容器的动态追踪。sysdig 汇集了一些列性能工具的优势,可以说是集百家之所长。我习惯用这个公式来表示 sysdig 的特点: sysdig = strace + tcpdump + htop + iftop + lsof + docker inspect。
而在最新的版本中(内核版本 >= 4.14),sysdig 还可以通过 eBPF 来进行扩展,所以,也可以用来追踪内核中的各种函数和事件。
这里总结了几个常见的动态追踪使用场景,并且分别推荐了适合的工具。你可以保存这个表格,方便自己查找并使用。
今天,我主要带你学习了 perf、eBPF 和 BCC 等动态追踪方法,并总结了不同场景中如何选择动态追踪方法。
在 Linux 系统中,常见的动态追踪方法,包括 ftrace、perf、eBPF 以及 SystemTap 等。在大多数性能问题中,使用 perf 配合火焰图是一个不错的方法。如果这满足不了你的要求,那么:
- 在新版的内核中,eBPF 和 BCC 是最灵活的动态追踪方法;
- 而在旧版本内核中,特别是在 RHEL 系统中,由于 eBPF 支持受限,SystemTap 往往是更好的选择。