用 bpftrace 一行命令统计 MySQL 查询延迟分布,给出脚本
解读
国内互联网/金融/运营商的性能测试面试,越来越强调“现场定位”能力:给你一台线上机器,能否在 5 分钟内给出量化结论。bpftrace 作为 Linux 4.x+ 内核标配的 eBPF 前端,无需重启、无需插桩、无需 root 写文件,一行命令即可把内核态和用户态事件转成直方图,是面试官最爱考察的“杀手锏”工具。
本题表面考“写一行命令”,实则考察四点:
- 是否知道 MySQL 实际收发包的入口函数(国内 5.7/8.0 主流版本用
do_command或dispatch_command); - 是否会把“请求开始”和“请求结束”做成时间戳对,并把耗时纳秒转微秒;
- 是否熟悉 bpftrace 内置的
@start[tid]线程局部变量与hist()直方图; - 是否能在高并发场景下避免丢失事件(用
tid而不是pid做键)。
答出“一行”即可,但必须给出可落地的完整脚本,否则会被追问“你这一行在生产环境跑过吗?”
知识点
- eBPF 与 bpftrace:内核态安全虚拟机,bpftrace 把脚本编译成 eBPF 字节码,通过 kprobe/uprobe 插桩,结果汇总到用户态。
- MySQL 请求生命周期:连接线程
do_command()循环读取报文 → 解析 → 执行 → 写结果 → 回do_command()顶部。 - 时间测量:bpftrace 内建
nsecs返回自开机纳秒,精度足够;@start[tid]用线程 ID 做键,避免连接池复用线程导致串扰。 - 直方图函数:
hist(int64)自动按 2 的幂分桶,输出 ASCII 直方图,适合 SLA 分位点(P99、P999)目测。 - 国内生产限制:容器内需要
CAP_SYS_ADMIN;部分云主机内核编译时关掉了kprobe,需先cat /boot/config-$(uname -r) | grep KPROBE确认。 - 安全守则:只读事件,不写内存;采样结束及时
Ctrl-C退出,避免 bpf 程序常驻。
答案
一行可执行命令(含换行符转义,可直接复制到跳板机):
bpftrace -e 'kprobe:do_command { @start[tid] = nsecs; }
kretprobe:do_command / @start[tid] / {
@latency_us = hist((nsecs - @start[tid]) / 1000); delete(@start[tid]);
}'
执行后,持续采样;Ctrl-C 即可看到类似输出:
@latency_us:
[256, 512) 1734 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|
[512, 1K) 892 |@@@@@@@@@@@@@@@@@@@@@@ |
[1K, 2K) 301 |@@@@@@ |
...
桶单位是微秒,一眼可读出 P99 落在 1 ms 以内还是以外,直接对标业务 SLA。
拓展思考
- 若 MySQL 版本静态编译去除了
do_command符号,可改用dispatch_command或 SSL 下的ssl_do_handshake入口;亦可通过uprobe:/usr/sbin/mysqld:*dispatch_command*做用户态插桩,脚本结构不变。 - 想区分读/写,可在
do_command入参*com里判断第一个字节:0x03为 QUERY,再解析 SQL 首单词;bpftrace 支持str()读取用户态内存,但需bpftrace -e 'kprobe:do_command { @sql[str(arg1)] = hist(...); }'做聚合,注意性能开销。 - 对高并发 10w QPS 场景,直方图聚合在内核态,仍可能占满 512 k 指令数上限;可用
@start[tid, $cpu] = nsecs拆分到 CPU 维度,或定期clear(@start)防止哈希表爆炸。 - 国内部分银行使用 GreatSQL、Percona 分支,函数名相同;但阿里 RDS 内核经过热补丁,符号表被 strip,需提前
perf top抓热点函数名再替换脚本。 - 面试加分项:把结果实时推到 Prometheus。可让 bpftrace 输出 JSON:
bpftrace -f json -e '... { printf("{\"ts\":%d,\"lat\":%d}\n", nsecs/1e9, (nsecs-@start[tid])/1e3); }' | ./json_exporter,实现 eBPF 监控闭环。