我们都知道MySQL中的错误日志和慢查询日志可以帮助大家快速定位问题。但有的时候,日志记录的信息太少,或者没有记录你感兴趣的信息,有的时候记录的问题太多,大量的无效信息干扰了你的排查。因此,本文引入一个新的思路——探针技术。该技术可以在系统的关键节点插入一些探针来采集信息,而不影响MySQL的运行,不破坏现场环境。理论上,探针可以插入到MySQL或Linux内核中任何函数的导入导出中,参数等其他细节都可以很容易地访问到,资源损失很小,一旦移除探针也不会丢失。就像医生给病人拍片一样,在不影响病人健康的情况下,可以实时观察病人的内部情况,为分析病因提供依据和支持。Part1探针原理本文介绍的探针就像调试程序时的断点,只不过断点是交互的,以字节码的形式运行在内核虚拟机(BPF)中。1.异常异常是控制流的突然变化,用于响应处理器状态的某些变化。理解异常有助于理解探测技术。当下图所示的处理器执行时,会发生一个重要的变化,我们称之为事件。事件可能与当前指令直接相关,例如页面错误异常、算术溢出、尝试除以0。也可能无关紧要,例如定时器生成I/O完成的信号。在任何情况下,处理器都会通过异常表进行间接过程调用,以专门的异常处理程序进行处理。异常可以分为四类:中断、陷阱、故障和中止。作为来自处理器外部I/O设备(鼠标、键盘、网卡等)的信号的结果,中断异步发生。硬件中断不是由任何特定指令引起的,从这个意义上说,它是异步的。其余异常类型(陷阱、故障、中止)作为执行当前指令的结果同步发生。我们称这种指令为故障指令。陷阱是程序员“主动”触发的故意异常,就好像他在代码中埋下了陷阱一样。trap最常见的使用者是进程发起系统调用,通过INT从用户态trap到内核态。故障是由错误条件引起的,可以由故障处理程序纠正。当故障发生时,处理器将控制转移到故障处理程序。例如,当发生页面错误时,错误处理程序可以将相应的页面从磁盘中间交换到物理内存中。终止,不可恢复的致命错误的结果,通常是一些硬件错误。程序员通常在调试代码的时候,会在程序中添加断点,让程序在我们想要的地方停止。调试器可以随意控制程序的运行,主要依靠软件中断。软件断点是X86系统中的INT3指令。当程序执行到INT3指令时,会触发软件中断。这就是上面提到的陷阱。与我们在VisualStudio和GDB中的交互断点不同,如果程序在发生trap时自动执行预定义并处理运行情况的记录和统计,则不会影响程序的正常运行,达到观察MySQL的目的。2、探测器为了捕捉程序的运行状态,我们在程序中设置了一些“陷阱”,设置了处理程序,我们称之为探测器。有些探测器是在代码中预定义的,有些是在运行时动态添加的。1.静态探针静态探针是预先在程序中定义并编译到程序或内核中的。静态探测只会在启用时运行,如果不启用则不会运行。常见的静态探针包括内核中的跟踪点和USDT(UserlandStaticallyDefinedTracing)探针。跟踪点在代码中嵌入钩子以在运行时调用连接的探测器。它有两种状态“开”(探头连接)和“关”(探头未连接)。当跟踪点“关闭”时,它没有任何效果,只会增加一个小的时间惩罚(检查分支的条件)和空间惩罚。当跟踪点处于“打开”状态时,每次在调用者的执行上下文中执行跟踪点时都会调用附加的探测器。探测函数执行完毕后,返回给调用者。USDT类似于tracepoint,但它是在用户态,只需在代码中插入DTRACE_PROBE()即可。2.动态探针动态探针是应用程序不定义,在程序运行时动态添加的探针。动态探测类似于异常处理机制。当系统产生异常时,会跳转到执行相应的句柄。动态探针会在函数入口和出口处插入一些断点,当程序执行到断点时执行相应的句柄,从而达到观察应用程序的目的。这里的中断指的是陷阱(trap),也就是X86系统中的int3指令。KProbes是Linux内核探测器,可用于监控生产系统中的事件。您可以使用它来解决性能瓶颈、记录特定事件、追踪问题等。KProbes可以在内核代码中实时插入中断指令。虽然这听起来有点不可思议,但实际上KProbes做了很多安全保证,比如stop_machine保证其他CPU在被修改时停止执行。事实上,kprobes最大的风险是在一些调用非常频繁的函数中添加了探测。例如,在网络模块中,频繁的中断可能会造成一定的性能风险。KProbe需要定义pre-handler和post-handler。当要执行检测到的指令时,首先执行预处理程序。同样,后处理程序在执行探测指令后立即执行。uprobes是Linux在用户态下提供的动态探测,被纳入2012年7月发布的Linux3.5内核。uprobes与kprobes非常相似,都是在用户态下使用。3.BPFBPF(BerkeleyPacketFilter)最早是在BSD操作系统下开发的。它是TCP/IP数据包过滤的工业标签,由tcpdump使用。它的工作方式有点特殊:用户自定义数据包过滤表达式,然后注入到内核中的BPF中运行,这样做的好处是在内核中过滤而不是将数据包复制到用户态,避免复制一个从内核态到用户态的数据量大,因此具有更好的性能。后来又出现了eBPF(extendBPF)。eBPF有自己的语言。用户自己编写程序,编译后通过BPF调用注入到内核的BPF虚拟机中运行。他们可以安全地访问内核内存,这使得内核可编程。运行在内核中往往具有更高的性能,因为它不需要将数据拷贝到用户空间,所以BPF被很多性能跟踪工具使用。Part2使用探针观察MySQL上面介绍了相关的技术背景,然后介绍了使用跟踪工具观察bpftrace,bpftrace是一个使用BPF进行性能分析的前端工具。使用起来非常方便,类似于awk语言。由于MySQL运行在用户态,所以只有**USDT**和**uprobes**可以用来跟踪MySQL本身。1.使用USDT观察MySQLMySQL在系统的一些关键位置定义了USDT,参考文档mysqldDTraceProbeReference(DTrace是Solaris中的动态跟踪工具,bpftrace是Linux版本的DTrace)下面是记录脚本并跟踪慢速查询。#!/usr/bin/bpftraceBEGIN{printf("Tracingmysqldquerieslowerthan%dms.Ctrl-Ctoend.\n",$1);printf("%-10s%-6s%6s%s\n","TIME(ms)","PID","MS","QUERY");}usdt:/usr/sbin/mysqld:mysql:query__start{@query[tid]=str(arg0);@start[tid]=nsecs;}usdt:/usr/sbin/mysqld:mysql:query__done/@start[tid]/{$dur=(nsecs-@start[tid])/1000000;if($dur>$1){printf("%-10u%-6d%6d%s\n",elapsed/1000000,pid,$dur,@query[tid]);}delete(@query[tid]);delete(@start[tid]);}解释**开始**在脚本开始运行时执行,并打印一些提示信息。usdt:/usr/sbin/mysqld:mysql:query__start是在**query__start**函数中添加的一个探针。程序执行到这里时,记录第一个参数arg0(query),以及当前时间和时间戳。并将这些信息保存在BPF映射中。当执行query__done探针时,它会记录当前时间减去开始时间(从BPFmap中获取),即查询所花费的时间,如果超过阈值则打印出来。如果说MySQL查询日志慢的话,这里的好处就是不用重启MySQL,可以实时修改阈值。而且在MySQL的高级版本中会去掉usdt,需要在编译时设置参数-DENABLE_DTRACE=1来支持usdt。2、使用uprobes观察MySQL不同于usdt,需要提前在代码中设置观察点。Uprobes可以在程序中动态添加,可以插入到任意函数的导入导出位置。下面展示了使用uprobes探针跟踪dispatch_command并打印出慢查询语句。#!/usr/bin/bpftraceBEGIN{printf("Tracingmysqldquerieslowerthan%dms.Ctrl-Ctoend.\n",$1);printf("%-10s%-6s%6s%s\n","TIME(ms)""PID","MS","QUERY");}uprobe:/usr/sbin/mysqld:*dispatch_command*{$COM_QUERY=3;if(arg2==$COM_QUERY){@query[tid]=str(*arg1);@start[tid]=nsecs;}}uretprobe:/usr/sbin/mysqld:*dispatch_command*/@start[tid]/{$dur=(nsecs-@start[tid])/1000000;if($dur>$1){printf("%-10u%-6d%6d%s\n",elapsed/1000000,pid,$dur,@query[tid]);}delete(@query[tid]);delete(@start[tid]);}在这里运行,使用参数10,表示慢查询阈值为10ms名字,员工。last_name,titles.title931451083224select*fromemployees12534810831727select*fromsalariesbpftrace还提供了一个直方图工具,可以记录所有查询耗时,最后打印出耗时分布直方图。#!/usr/bin/bpftraceBEGIN{printf("TracingMySQLquery...HitCtrl-Ctoend.\n");}uprobe:/usr/sbin/mysqld:*dispatch_command*{@start[tid]=nsecs;}uretprobe:/usr/sbin/mysqld:*dispatch_command*/@start[tid]/{@usecs=hist((nsecs-@start[tid])/1000000);delete(@start[tid]);}END{clear(@start);}sudo./histo.btAttaching4probes...TracingMySQLquery...HitCtrl-Ctoend.^C@usecs:[0]10|@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@|[1]0||[2,4)0||[4,8)0||[8,16)0||[16,32)0||[32,64)0||[64,128)0||[128,256)1|@@@@|[256,512)1|@@@@|[512,1K)0||[1K,2K)1|@@@@@|(左右滑动查看代码)例子比较简单,多个探针即可同时插入,利用BPF图共享信息,实现更强的观测能力。除了这两种探测,你还可以使用tracepoints和KProbe来分析内核状态,比如网络操作和磁盘I/O。当然,这需要你对程序很熟悉,不然你不知道这些探针加在什么地方。好的。
