暂无图片
暂无图片
4
暂无图片
暂无图片
暂无图片

「OceanBase 征文」OceanBase 4.0 改装:另一种全链路追踪的尝试

原创 夏克 2023-03-07
455

图片.png

引子

前些天看了一篇《OceanBase 4.0 解读:全链路追踪要解决什么问题?从一条慢SQL说起》的文章,不得不说这是目前为止OceanBase 4.0 最吸引我的功能。在分布式数据库系统中,由于系统规模变大、组件数量增多、系统拓扑结构变得更加复杂,因此对系统进行监测和调试变得更加困难。可观测/追踪技术在这种情况下具有重要的作用,它可以帮助我们监测和调试系统,快速发现问题并解决它们。可以说全链路追踪技术对运维体验来说是质的改进。恰巧我最近也在研究就可观测技术,与OpenTracing模型不同,本文将使用一种灵活而强大的linux内核技术——eBPF,来实现对OceanBase追踪与观测的尝试。

概要

大部分的DBA可能没有使用过eBPF技术,说起来算是一个比较小众的技术领域,一些Linux内核研发人员可能比较熟悉。在一些开源数据库中,如MySQL、PostgreSQL中已经使用了相关技术,可以参考我最近的一篇文章《PostgreSQL + eBPF实现数据库服务可观测》。eBPF出现的早期主要是用于网络抓包和网络包的过滤,类似tcpdump、wareshark之类的抓包工具,后来用来替代内核模块的部分功能,以非侵入式的方式拓展内核模块的功能(主要是监控、过滤、负载均衡等)。eBPF技术还是比较复杂的,因此很难用较短的篇幅将它讲透彻。本文主要是抛砖引玉,通过一个例子介绍如何使用eBPF对OceanBase进行观测。例程中会修改OceanBase源码,目的是埋入一些探针,通过eBPF内核程序采集应用数据,并提供给eBPF用户程序进行展示。

说说eBPF

什么是 eBPF?

eBPF(extended Berkeley Packet Filter)是一种在Linux内核中运行的虚拟机,可以动态地编写并注入内核级别的代码,用于网络、安全、性能等方面的监测和调试。

eBPF最初是为网络分析设计的,但现在已被广泛应用于系统性能分析、安全审计和容器监控等领域。它通过在内核中运行用户代码来收集和分析数据,而不需要修改内核或加载内核模块,因此具有高度灵活性和可移植性。

eBPF程序可以在内核态和用户态之间进行交互,从而允许用户从内核中获得关于系统和应用程序的详细信息。这些信息可以用于调试、优化和监控系统性能、网络流量、安全审计等方面。

eBPF已经成为Linux生态系统中不可或缺的一部分,被广泛应用于云原生、容器化、网络监控等场景中,成为了开发人员和运维人员的得力工具之一。

图片.png

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,需要分为以下几个步骤:

  1. 在应用程序中添加USDT跟踪点:在应用程序中,通过添加类似于以下代码的宏定义,在代码中定义自己的跟踪点:
#include <sys/sdt.h> int main() { DTRACE_PROBE(MyProvider, myprobe); return 0; }
  1. 编写eBPF程序:编写一个eBPF程序,用于捕获和处理USDT跟踪点生成的事件。可以使用libbpf等工具来简化eBPF程序的编写和管理。
  2. 加载eBPF程序:通过BPF_MAP_TYPE_PERF_EVENT_ARRAY类型的BPF map,将eBPF程序与USDT跟踪点关联起来,从而在跟踪点生成事件时,将事件发送到eBPF程序中处理。可以使用BCC等工具来简化eBPF程序的加载和管理。
  3. 分析数据:在eBPF程序中,可以使用BPF_PERF_OUTPUT类型的BPF map,将处理后的事件发送到用户空间,并使用BCC等工具进行数据分析和可视化。

总之,eBPF的USDT可以让用户在应用程序中定义自己的跟踪点,从而实现更精细的性能分析和调试。使用USDT包括:添加跟踪点、编写eBPF程序、加载eBPF程序和分析数据四个步骤。

环境准备

由于篇幅原因这部分尽量省去比较繁琐的环境搭建步骤。——OceanBase文章中最多的应该就是部署了_,大家可以自行查阅。

OceanBase环境搭建

我这里是手动编译,并使用命令行启动observer,具体步骤省略。只记录一些必要的命令。

mkdir -p /home/frank/data/clog mkdir -p /home/frank/data/slog/server mkdir -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_static Consolidate compiler generated dependencies of target obcdc [ 97%] Built target obcdc_static [ 97%] Linking CXX executable observer [ 97%] Linking CXX shared library libobcdc.so ld.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.a clang-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 1 make[1]: *** [CMakeFiles/Makefile2:10163: src/observer/CMakeFiles/observer.dir/all] Error 2 make[1]: *** Waiting for unfinished jobs.... [ 97%] Built target obcdc make: *** [Makefile:166: all] Error 2
  • 解决方法:用系统的libapr 0.7.0 库替换依赖包中的libapr 0.6.5库。

图片.png

图片.png

BCC环境搭建

  • 编译内核
# 编译内核 apt-get install flex bison libssl-dev libelf-dev dwarves git clone https://github.com/microsoft/WSL2-Linux-Kernel.git cd WSL2-Linux-Kernel KERNEL_VERSION=$(uname -r | cut -d '-' -f 1) git checkout linux-msft-wsl-$KERNEL_VERSION cp Microsoft/config-wsl .config make oldconfig && make prepare make scripts make modules sudo make modules_install sudo mv /lib/modules/$KERNEL_VERSION-microsoft-standard-WSL2+/ /lib/modules/$KERNEL_VERSION-microsoft-standard-WSL2
  • 内核参数设置
CONFIG_BPF=y CONFIG_BPF_SYSCALL=y # [optional, for tc filters] CONFIG_NET_CLS_BPF=m # [optional, for tc actions] CONFIG_NET_ACT_BPF=m CONFIG_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.xz CONFIG_IKHEADERS=y
  • 编译安装BCC
sudo apt install liblzma-dev sudo apt install libclang-14-dev git clone https://github.com/iovisor/bcc.git mkdir bcc/build; cd bcc/build cmake .. make sudo make install cmake -DPYTHON_CMD=python3 .. # build python3 binding pushd src/python/ make sudo make install popd

例程演示

修改OceanBase源码

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

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

  • 增加头文件,包含必要的静态跟踪点。

图片.png

  • 增加探针,

图片.png

  • 注意:目前DTRACE_PROBE函数最多支持12个参数。

图片.png

  • 重新编译observer

查看探针

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

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

图片.png

$ readelf -n observer

图片.png

编写eBPF观测代码

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

#!/usr/bin/python from __future__ import print_function from bcc import BPF, USDT from bcc.utils import printb import sys if len(sys.argv) < 2: print("USAGE: OceanBase_latency PID") exit() pid = sys.argv[1] debug = 0 # load BPF program bpf_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 PID u = USDT(pid=int(pid)) u.enable_probe(probe="stmt_query", fn_name="do_trace") if debug: print(u.get_text()) print(bpf_text) # initialize BPF b = BPF(text=bpf_text, usdt_contexts=[u]) # header print("%-18s %-16s %-6s %-6s %s" % ("TIME(s)", "COMM", "PID", "CPU", "QUERY")) # format output while 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))

注解:

  1. bpf_text :该变量保存的是在内核空间执行的C代码,主要功能是捕获并打印query信息。
  2. u.enable_probe(probe="stmt_query", fn_name="do_trace"):stmt_query是OceanBase中我们埋下的probe观察点。do_trace是观察点出发时要执行的跟踪函数。
  3. 上面代码的功能是,当OceanBase进程收到sql时触发观察点,并将获取的sql文本从传递给观察者。
    当然,理论上可以观测任意用户埋下的探针,这里只为说明技术应用场景,不以提供完整的观测系统为目的。

运行范例

  1. 启动observer
  2. 启动观测例程:sudo python3 ob_query.py pidof observer

图片.png
可以看到,probe捕获了经过观测点的所有SQL。

注意:

  1. 需要root权限执行,sudo。
  2. 参数是observer的进程id,pidof observer

拓展

PostgreSQL

PostgreSQL 9.3以后的版本已实现dtrace,埋下了57个探针,通过编译时开启–enable-dtrace选项使其生效。

frank@LAPTOP-4OF1323N:~/pgsql/bin$ tplist-bpfcc -l /home/frank/pgsql/bin/postgres b'/home/frank/pgsql/bin/postgres' b'postgresql':b'clog__checkpoint__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'clog__checkpoint__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'multixact__checkpoint__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'multixact__checkpoint__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'subtrans__checkpoint__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'subtrans__checkpoint__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'twophase__checkpoint__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'twophase__checkpoint__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'transaction__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'transaction__commit' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'transaction__abort' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__checkpoint__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__checkpoint__sync__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__checkpoint__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'wal__buffer__write__dirty__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'wal__buffer__write__dirty__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'wal__switch' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'checkpoint__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'checkpoint__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'wal__insert' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__flush__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__flush__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__read__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__read__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__write__dirty__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__write__dirty__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__sync__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__sync__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'buffer__sync__written' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'deadlock__found' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lock__wait__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lock__wait__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__wait__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__wait__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__acquire' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__condacquire' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__condacquire__fail' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__acquire__or__wait__fail' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__acquire__or__wait' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'lwlock__release' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'smgr__md__read__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'smgr__md__read__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'smgr__md__write__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'smgr__md__write__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__parse__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__parse__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__rewrite__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__rewrite__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__plan__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__plan__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__execute__start' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'query__execute__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'statement__status' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'sort__done' b'/home/frank/pgsql/bin/postgres' b'postgresql':b'sort__start'

比如可以通过query__execute__startquery__execute__done配合来跟着sql执行的时间。通过lock__wait__startlock__wait__done记录锁等待时间。等等。。诸如此类的观测与跟着,同样,如果有必要可以修改PostgreSQL的源码来实现更多的观测点。

DeepFlow

DeepFlow 是云杉网络 (opens new window)开源的一款高度自动化的可观测性平台,是为云原生应用开发者建设可观测性能力而量身打造的全栈、全链路、高性能数据引擎。DeepFlow 使用 eBPF、WASM、OpenTelemetry 等新技术,创新的实现了 AutoTracing、AutoMetrics、AutoTagging、SmartEncoding 等核心机制,帮助开发者提升埋点插码的自动化水平,降低可观测性平台的运维复杂度。利用 DeepFlow 的可编程能力和开放接口,开发者可以快速将其融入到自己的可观测性技术栈中。

图片.png

eunomia-bpf

图片.png

总结

作为改善 OceanBase 易用性的重要一环,我们也将在未来的 4.x 中着力提升运维体验,新增包括 ASH(Active Session History)、Realtime SQL Plan Monitor、Logical Plan Manager 等在内的更多功能。希望本文能给OceanBase的研发团队提供一些有价值的参考。由于篇幅有限,本人水平有限,因此关于eBPF相关技术并没有深入介绍,USDT只是eBPF的冰山一角,其强大的功能足以实现一套完整、复杂的分布式数据库可观测系统。另外,从观察机制上分析eBPF大部分的观测是非侵入式的,在内核空间实现的,从性能的消耗上看应该比OpenTracing更小。

最后修改时间:2023-03-10 16:26:55
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论