
OceanBase 4.x改装:另一种全链路追踪的尝试
前些天看了一篇《OceanBase 4.0 解读:全链路追踪要解决什么问题?从一条慢SQL说起》的文章,不得不说这是目前为止 OceanBase 4.x 最吸引我的功能。在分布式数据库系统中,由于系统规模变大、组件数量增多、系统拓扑结构变得更加复杂,因此对系统进行监测和调试变得更加困难。可观测/追踪技术在这种情况下具有重要的作用,它可以帮助我们监测和调试系统,快速发现问题并解决它们。可以说全链路追踪技术对运维体验来说是质的改进。
恰巧我最近也在研究可观测技术,与 OpenTracing 模型不同,本文将使用一种灵活而强大的 linux 内核技术——eBPF,来实现对 OceanBase 追踪与观测的尝试。
大部分的 DBA 可能没有使用过 eBPF 技术,说起来算是一个比较小众的技术领域,一些 Linux 内核研发人员可能比较熟悉。在一些开源数据库中,如 MySQL、PostgreSQL 中已经使用了相关技术。eBPF 出现的早期主要是用于网络抓包和网络包的过滤,类似 tcpdump、wireshark 之类的抓包工具,后来用来替代内核模块的部分功能,以非侵入式的方式拓展内核模块的功能(主要是监控、过滤、负载均衡等)。eBPF 技术还是比较复杂的,因此很难用较短的篇幅将它讲透彻。
本文主要是抛砖引玉,通过一个例子介绍如何使用 eBPF 对 OceanBase 进行观测。例程中会修改 OceanBase 源码,目的是埋入一些探针,通过 eBPF 内核程序采集应用数据,并提供给 eBPF 用户程序进行展示。
一、什么是 eBPF?
eBPF(extended Berkeley Packet Filter)是一种在 Linux 内核中运行的虚拟机,可以动态地编写并注入内核级别的代码,用于网络、安全、性能等方面的监测和调试。
eBPF 最初是为网络分析设计的,但现在已被广泛应用于系统性能分析、安全审计和容器监控等领域。它通过在内核中运行用户代码来收集和分析数据,而不需要修改内核或加载内核模块,因此具有高度灵活性和可移植性。
eBPF 程序可以在内核态和用户态之间进行交互,从而允许用户从内核中获得关于系统和应用程序的详细信息。这些信息可以用于调试、优化和监控系统性能、网络流量、安全审计等方面。
eBPF 已经成为 Linux 生态系统中不可或缺的一部分,被广泛应用于云原生、容器化、网络监控等场景中,成为了开发人员和运维人员的得力工具之一。
二、eBPF 的跟踪方式
eBPF 有几种常用的追踪方式,包括:
- Kprobe:Kprobe 是一种内核级别追踪方式,可以在内核函数入口或出口处插入跟踪点,从而收集内核函数的参数和返回值等信息。Kprobe 可以用于跟踪内核函数的性能和行为,帮助进行系统性能分析和故障排除。
- Uprobe:Uprobe是一种用户空间追踪方式,可以在用户空间程序的函数入口或出口处插入跟踪点,从而收集用户空间程序的性能数据和其他有用信息。与 Kprobe 类似,Uprobe 也可以用于跟踪用户空间程序的性能和行为,帮助进行系统性能分析和故障排除。
- Tracepoint:Tracepoint 是一种内核级别追踪方式,它会在内核代码中预定义的位置插入跟踪点,从而收集内核函数的性能数据和其他有用信息。Tracepoint 的优势在于它可以在不修改内核源代码的情况下进行跟踪。
- Perf:Perf 是一种内核级别追踪工具,可以用于在内核中插入跟踪点,从而收集性能数据和其他有用信息。Perf 可以用于跟踪 CPU 事件、内存事件、磁盘 I/O 事件等。
- USDT(User-Level Statically Defined Tracing):USDT 是一种用户空间追踪方式,可以通过在应用程序中插入特殊的跟踪点,从而在运行时收集应用程序的性能数据和其他有用信息。USDT 使用静态定义的方式在应用程序中插入跟踪点,从而可以在应用程序不需要重新编译的情况下进行追踪。
理论上 eBPF 可以通过多种手段对基础设施,操作系统和应用进行全方位的非侵入式观测。后面的例程将使用 USDT 对 OceanBase 实现追踪与观测(USDT 只是 eBPF 技术的很小一部分功能)。
三、USDT
USDT(User Statically Defined Tracepoints)是 eBPF 的一种特性,可以让用户在应用程序中定义自己的跟踪点,从而实现更精细的性能分析和调试。使用 eBPF USDT,需要分为以下几个步骤:
- 在应用程序中添加 USDT 跟踪点:在应用程序中,通过添加类似于以下代码的宏定义,在代码中定义自己的跟踪点;
- 编写 eBPF 程序:编写一个 eBPF 程序,用于捕获和处理 USDT 跟踪点生成的事件。可以使用 libbpf 等工具来简化 eBPF 程序的编写和管理;
- 加载 eBPF 程序:通过 BPF_MAP_TYPE_PERF_EVENT_ARRAY 类型的 BPF map,将 eBPF 程序与 USDT 跟踪点关联起来,从而在跟踪点生成事件时,将事件发送到 eBPF 程序中处理。可以使用 BCC 等工具来简化 eBPF 程序的加载和管理。
- 分析数据:在 eBPF 程序中,可以使用 BPF_PERF_OUTPUT 类型的 BPF map,将处理后的事件发送到用户空间,并使用 BCC 等工具进行数据分析和可视化。
总之,eBPF 的 USDT 可以让用户在应用程序中定义自己的跟踪点,从而实现更精细的性能分析和调试。使用 USDT 包括:添加跟踪点、编写 eBPF 程序、加载 eBPF 程序和分析数据四个步骤。
由于篇幅原因这部分尽量省去比较繁琐的环境搭建步骤。感兴趣的同学可以自行官网查阅。
一、OceanBase 环境搭建
我这里是手动编译,并使用命令行启动 observer,具体步骤省略。只记录一些必要的命令。
注意,值得一提的是我是用 WLS 的 ubuntu 22,编译时 pthread 库存在版本问题,解决方式如下:
- 报错:ld.lld: error: undefined symbol: pthread_mutex_consistent_np
- 解决方法:用系统的 libapr 0.7.0 库替换依赖包中的 libapr 0.6.5 库。
二、BCC环境搭建
编译内核
内核参数设置
编译安装BCC
一、修改 OceanBase 源码
因为是例程,因此这部分只增加了一个探针,探针位置放在处理 SQL 的入口处 stmt_query。
文件路径 src/sql/ob_sql.cpp,共修改两行代码。
- 增加头文件,包含必要的静态跟踪点;
- 增加探针;
- 注意,目前 DTRACE_PROBE 函数最多支持 12 个参数。
- 重新编译 observer
二、查看探针
通过 tplist/tplist-bpfcc 和 readelf 两种方式可以看到对应的探针已经埋入,与代码对应 Provider 是 OceanBase,Name 是 stmt_query。
三、编写 eBPF 观测代码
这里使用 BCC 的 python 框架编写观测代码,当然,也可使用 C、Golang 编写。
*注解:
- bpf_text :该变量保存的是在内核空间执行的C代码,主要功能是捕获并打印query信息。
- u.enable_probe(probe="stmt_query", fn_name="do_trace"):stmt_query是OceanBase中我们埋下的probe观察点。do_trace是观察点出发时要执行的跟踪函数。
- 上面代码的功能是,当OceanBase进程收到sql时触发观察点,并将获取的sql文本传递给观察者。
当然,理论上可以观测任意用户埋下的探针,这里只为说明技术应用场景,不以提供完整的观测系统为目的。
四、运行范例
Step1:启动 observer
Step2:启动观测例程:sudo python3 ob_query.py pidof observer
可以看到,probe 捕获了经过观测点的所有 SQL。
*注意:
- 需要root权限执行,sudo。
- 参数是observer的进程id,pidof observer
作为改善 OceanBase 易用性的重要一环,未来的 4.x 将着力提升运维体验,新增包括 ASH(Active Session History)、Realtime SQL Plan Monitor、Logical Plan Manager 等在内的更多功能,希望本文能给 OceanBase的研发团队提供一些有价值的参考。
由于篇幅有限,本人水平有限,因此关于 eBPF 相关技术并没有深入介绍,USDT 只是 eBPF 的冰山一角,其强大的功能足以实现一套完整、复杂的分布式数据库可观测系统。另外,从观察机制上分析 eBPF 大部分的观测是非侵入式的,在内核空间实现的,从性能的消耗上看应该比 OpenTracing 更小。
文章转载自公众号:OceanBase
