OceanBase 4.x改装:另一种全链路追踪的尝试

本文作者:夏克

OceanBase 社区文档贡献者,曾多次参与 OceanBase 技术征文比赛,获得优秀名次。从事金融行业核心系统设计开发工作多年,服务于某交易所子公司,现阶段负责国产数据库调研。

本文为 OceanBase 第七期技术征文活动「小鱼快跑|OceanBase 4.1 上手体验」的优秀投稿文章之一,分享给大家,欢迎大家评论区一起交流。

前些天看了一篇《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 跟踪点:在应用程序中,通过添加类似于以下代码的宏定义,在代码中定义自己的跟踪点;

    #include <sys/sdt.h>int main() {
    DTRACE_PROBE(MyProvider, myprobe); return 0;}

  • 编写 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,具体步骤省略。只记录一些必要的命令。

mkdir -p /home/frank/data/clogmkdir -p /home/frank/data/slog/servermkdir -p /home/frank/data/sstable
./observer -r 127.0.0.1:2882:2881 -p 2881 -P 2882 -z zone1 -n obcluster -c 1 -d /home/frank/data -i lo -l INFO -o ob_trx_idle_timeout=120,__min_full_resource_pool_memory=2147483648,enable_syslog_recycle=True,enable_syslog_wf=True,max_syslog_file_count=4,memory_limit=6G,datafile_size=20G,log_disk_size=24G,system_memory=1G,cpu_count=16 -l ERROR
obclient -h 127.0.0.1 -P 2881 -uroot

注意,值得一提的是我是用 WLS 的 ubuntu 22,编译时 pthread 库存在版本问题,解决方式如下:

  • 报错:ld.lld: error: undefined symbol: pthread_mutex_consistent_np

    Consolidate compiler generated dependencies of target obcdc_staticConsolidate compiler generated dependencies of target obcdc[ 97%] Built target obcdc_static[ 97%] Linking CXX executable observer[ 97%] Linking CXX shared library libobcdc.sold.lld: error: undefined symbol: pthread_mutexattr_setrobust_np>>> referenced by proc_mutex.c>>> proc_mutex.o:(proc_mutex_pthread_create) in archive ../../../deps/3rd/usr/local/oceanbase/deps/devel/lib/libapr-1.a>>> did you mean: pthread_mutexattr_setrobust_np@GLIBC_2.4>>> defined in: /lib/x86_64-linux-gnu/libc.so.6
    ld.lld: error: undefined symbol: pthread_mutex_consistent_np>>> referenced by proc_mutex.c>>> proc_mutex.o:(proc_mutex_pthread_acquire) in archive ../../../deps/3rd/usr/local/oceanbase/deps/devel/lib/libapr-1.a>>> referenced by proc_mutex.c>>> proc_mutex.o:(proc_mutex_pthread_tryacquire) in archive ../../../deps/3rd/usr/local/oceanbase/deps/devel/lib/libapr-1.a>>> did you mean: pthread_mutex_consistent_np@GLIBC_2.4>>> defined in: /lib/x86_64-linux-gnu/libc.so.6
    ld.lld: error: undefined symbol: sys_siglist>>> referenced by signals.c>>> signals.o:(apr_signal_description_get) in archive ../../../deps/3rd/usr/local/oceanbase/deps/devel/lib/libapr-1.a
    ld.lld: error: undefined symbol: pthread_yield>>> referenced by thread.c>>> thread.o:(apr_thread_yield) in archive ../../../deps/3rd/usr/local/oceanbase/deps/devel/lib/libapr-1.aclang-11: error: linker command failed with exit code 1 (use -v to see invocation)make[2]: *** [src/observer/CMakeFiles/observer.dir/build.make:154: src/observer/observer] Error 1make[1]: *** [CMakeFiles/Makefile2:10163: src/observer/CMakeFiles/observer.dir/all] Error 2make[1]: *** Waiting for unfinished jobs....[ 97%] Built target obcdcmake: *** [Makefile:166: all] Error 2

  • 解决方法:用系统的 libapr 0.7.0 库替换依赖包中的 libapr 0.6.5 库。

二、BCC环境搭建

****▋编译内核

# 编译内核apt-get install flex bison libssl-dev libelf-dev dwarvesgit clone https://github.com/microsoft/WSL2-Linux-Kernel.gitcd WSL2-Linux-KernelKERNEL_VERSION=$(uname -r | cut -d '-' -f 1)git checkout linux-msft-wsl-$KERNEL_VERSION
cp Microsoft/config-wsl .configmake oldconfig && make preparemake scriptsmake modulessudo make modules_install
sudo mv /lib/modules/$KERNEL_VERSION-microsoft-standard-WSL2+/ /lib/modules/$KERNEL_VERSION-microsoft-standard-WSL2

****▋内核参数设置

CONFIG_BPF=yCONFIG_BPF_SYSCALL=y# [optional, for tc filters]CONFIG_NET_CLS_BPF=m# [optional, for tc actions]CONFIG_NET_ACT_BPF=mCONFIG_BPF_JIT=y# [for Linux kernel versions 4.1 through 4.6]CONFIG_HAVE_BPF_JIT=y# [for Linux kernel versions 4.7 and later]CONFIG_HAVE_EBPF_JIT=y# [optional, for kprobes]CONFIG_BPF_EVENTS=y# Need kernel headers through /sys/kernel/kheaders.tar.xzCONFIG_IKHEADERS=y

****▋编译安装BCC

sudo apt install liblzma-devsudo apt install libclang-14-devgit clone https://github.com/iovisor/bcc.gitmkdir bcc/build; cd bcc/buildcmake ..makesudo make install
cmake -DPYTHON_CMD=python3 .. # build python3 bindingpushd src/python/makesudo make installpopd

一、修改 OceanBase 源码

因为是例程,因此这部分只增加了一个探针,探针位置放在处理 SQL 的入口处 stmt_query。

文件路径 src/sql/ob_sql.cpp,共修改两行代码。

  • 增加头文件,包含必要的静态跟踪点;
  • 增加探针;
  • 注意,目前 DTRACE_PROBE 函数最多支持 12 个参数。
  • 重新编译 observer

二、查看探针

通过 tplist/tplist-bpfcc 和 readelf 两种方式可以看到对应的探针已经埋入,与代码对应 Provider 是 OceanBase,Name 是 stmt_query。

$ tplist-bpfcc -l /home/frank/github/oceanbase/build_debug/src/observer/observer# 注意:要使用绝对路径
$ readelf -n observer

三、编写 eBPF 观测代码

这里使用 BCC 的 python 框架编写观测代码,当然,也可使用 C、Golang 编写。

#!/usr/bin/python
from __future__ import print_functionfrom bcc import BPF, USDTfrom bcc.utils import printbimport sys
if len(sys.argv) < 2:    print("USAGE: OceanBase_latency PID")    exit()pid = sys.argv[1]debug = 0
# load BPF programbpf_text = """#include <uapi/linux/ptrace.h>int do_trace(struct pt_regs *ctx) {    uint64_t addr;    char query[128];    bpf_usdt_readarg(1, ctx, &addr);    bpf_probe_read_user(&query, sizeof(query), (void *)addr);    bpf_trace_printk("%s\\n", query);    return 0;};"""
# enable USDT probe from given PIDu = USDT(pid=int(pid))u.enable_probe(probe="stmt_query", fn_name="do_trace")if debug:    print(u.get_text())    print(bpf_text)
# initialize BPFb = BPF(text=bpf_text, usdt_contexts=[u])
# headerprint("%-18s %-16s %-6s %-6s %s" % ("TIME(s)", "COMM", "PID", "CPU", "QUERY"))
# format outputwhile 1:    try:        (task, pid, cpu, flags, ts, msg) = b.trace_fields()    except ValueError:        print("value error")        continue    except KeyboardInterrupt:        exit()    printb(b"%-18.9f %-16s %-6d %-6d %s" % (ts, task, pid, cpu, msg))

*注解:

  • 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 更小。

相关推荐
kejijianwen2 小时前
JdbcTemplate常用方法一览AG网页参数绑定与数据寻址实操
服务器·数据库·oracle
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
高兴就好(石5 小时前
DB-GPT部署和试用
数据库·gpt
这孩子叫逆5 小时前
6. 什么是MySQL的事务?如何在Java中使用Connection接口管理事务?
数据库·mysql
Karoku0666 小时前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
码农郁郁久居人下6 小时前
Redis的配置与优化
数据库·redis·缓存
MuseLss7 小时前
Mycat搭建分库分表
数据库·mycat
Hsu_kk7 小时前
Redis 主从复制配置教程
数据库·redis·缓存
DieSnowK7 小时前
[Redis][环境配置]详细讲解
数据库·redis·分布式·缓存·环境配置·新手向·详细讲解
程序猿小D8 小时前
第二百三十五节 JPA教程 - JPA Lob列示例
java·数据库·windows·oracle·jdk·jpa