欢迎参与 8 月 1 日中午 11 点的线上分享,了解 GreptimeDB 联合处理指标和日志的最新方案! 👉🏻 点击加入

Skip to content
On this page
技术
2025-4-9

Rust 在 Android 的编程实践「PLUS 版」

在智能设备开发领域,Android 平台的生态多样性为开发者带来了独特的挑战。本文将以车载环境为典型场景,深入探讨使用 Rust 语言进行 Android 原生服务开发时遇到的交叉编译技术难题,并分享我们在实战中积累的解决方案。

在智能设备开发领域,Android 平台的生态多样性为开发者带来了独特的挑战。本文将以车载环境为典型场景,**深入探讨使用 Rust 语言进行 Android 原生服务开发时遇到的交叉编译技术难题,**并分享我们在实战中积累的解决方案。

p.s. 本篇文章是之前版本的增强版,最新更新以本次发表的内容为主。

交叉编译

1.1 为什么要交叉编译

在嵌入式设备开发中(车机场景),GreptimeDB Edge 通常以服务的形式部署在 Android 环境上,这要求我们将其编译成适用于 Android 平台的可执行文件。初步的方案可能是购置一款 Android 开发板,安装 Rust 工具链进行编译工作。

然而,这种做法可能会面临以下挑战:

  • 环境配置复杂:在 Android 开发板上配置 Rust 编译环境可能比较复杂(作者实际也没配置过);
  • 编译效率低下:多数 Android 开发板的 CPU 性能较弱,编译大型项目时速度缓慢,效率低下;
  • 版本差异风险:本地 Android API 版本可能与目标设备上的 API 版本存在差异,甚至 CPU 架构也可能不同,从而导致兼容性问题。

相对而言,交叉编译提供了一个更为高效的替代方案。它允许开发者在一个系统平台上(例如 x86 的 PC)编译出适用于另一种系统平台(例如 ARM 移动设备)的可执行文件,这在目标系统上直接编译困难时尤其有用。

1.2 Rust 对交叉编译的支持

Rust 对交叉编译的支持非常出色,加之 Android NDK 提供了必要的工具链和库,进一步简化了交叉编译的过程。由于我们的开发或编译环境通常是 macOS 或 Linux,所以选择通过交叉编译的方式来生成 Android 可执行文件是一个理想的解决方案。

首先,我们需要大致了解一下 Rust 编译过程——Rustc 先把 Rust 代码编译为 LLVM-IR,然后再由 LLVM 将 LLVM-IR 编译为各个平台的二进制,最终由 Linker 链接在一起,生成最终的二进制文件。

Rustc 是 Rust 的编译器,以 LLVM 作为后端(也可以说 Rustc 是 LLVM 的前端)。

下面是一个简化版的 Rust 编译架构图:

(图 1:Rust 编译架构图)
(图 1:Rust 编译架构图)

1.3 GreptimeDB 的交叉编译

接下来,以开源版本的 GreptimeDB 为例, 一步一步向大家展示如何在 x86 Linux 上进行交叉编译,生成 aarch64-linux-android 架构的可执行文件。

首先安装 Android NDK,下载地址在这里。此外设置一个环境变量,方便后续操作,如下所示:

bash
export ANDROID_NDK_HOME=<YOUR_NDK_ROOT>

# 示例
# export ANDROID_NDK_HOME=/home/fys/soft/ndk/android-ndk-r25c

接下来,从 GitHub 上拉取 GreptimeDB 的源码:

bash
git clone https://github.com/GreptimeTeam/greptimedb.git --depth 1

然后,添加 Target 到 Rust 工具链是实现跨平台编译的关键步骤。这允许 Rustc 将中间表示层 LLVM-IR 代码编译成目标平台的机器语言。在这个例子中,目标平台架构是 aarch64-linux-android,在 GreptimeDB 项目根目录下执行以下命令:

shell
rustup target add aarch64-linux-android

Rust 平台支持详见这里

这时候,尝试编译可能会报错: “-lgcc” 找不到。原因是:Android NDK 的 libgcc.a 已经被 libunwind.a 替代,解决方案是复制一份 libunwind.a 并重命名为 libgcc.a,详见 Rust blog

bash
# 具体路径可能随着ndk版本的不同,需要改动。
cd $ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/lib/clang/17/lib/linux/aarch64/
cp libunwind.a libgcc.a

在仅涉及 Rust 语言的项目中,开发者通常需要配置链接器(Linker)和归档器(AR)。然而,Rust 支持在构建脚本(build.rs)中执行构建任务,因此在 Rust 项目的编译过程中,可能需要集成 C 和 C++ 等其他语言的编译工作。这通常需要向编译工具(如 cccmake)提供一些必要的信息,包括编译器路径(CC 和 CXX)、库文件和头文件的位置等,这一过程往往较为复杂。

cargo-ndk 这个项目帮我们解决了大部分问题。通过执行以下命令,就可以编译出适用于 aarch64-linux-android 平台的 GreptimeDB 二进制程序:

bash
cargo ndk --platform 30 -t aarch64-linux-android build --bin greptime --release

此外,针对那些不兼容特定目标平台的库,处理起来确实较为棘手。一种解决方案是替换为兼容的库;如果涉及到功能并非必需,可以使用 feature guard ,在编译阶段将其去掉。

在编译过程中,如果遇到错误提示缺少 protobuf 库或其他,正确安装即可。

1.4 问题与挑战

之前遇到一个问题,当开启 LTO 优化时,交叉编译 GreptimeDB 就会失败。

报错信息如下所示:

plaintext
  = note: ld.lld: error: duplicate symbol: pthread_atfork
          >>> defined at crtbegin.c
          >>>            /home/fys/soft/ndk/android-ndk-r26d/toolchains/llvm/prebuilt/linux-x86_64/bin/../sysroot/usr/lib/aarch64-linux-android/23/crtbegin_dynamic.o:(pthread_atfork)
          >>> defined at build_jemalloc.6cd863fbc26b10-cgu.0
          >>>            /home/fys/source/build_jemalloc/target/aarch64-linux-android/nightly/deps/build_jemalloc-c1434931e7fc5ee2.build_jemalloc.6cd863fbc26b10-cgu.0.rcgu.o:(.text.pthread_atfork+0x0)
          clang-17: error: linker command failed with exit code 1 (use -v to see invocation)

当 API level >= 21 时,Android 会提供一个 pthread_atfork 的声明。而 tikv-jemallocator 中也有一个 pthread_atfork 的声明,都是强符号类型,当开启 LTO 优化时,就会导致了符号冲突。解决方案:将 tikv-jemallocatorpthread_atfork 设置为弱符号类型。

最新版本的 tikv-jemallocator 已经解决了这个问题,详见这里

Backtrace on Android

在开发 GreptimeDB Edge 项目的过程中,我们观察到 Rust 语言的标准库的 backtrace 在 Android 环境中无法提供预期的堆栈信息。具体来说,当程序 panic 时,相关的堆栈信息未能正确捕获,而是显示为 unknown,这为问题的诊断带来了极大的困扰。

2.1 问题复现

为了复现这一问题,我们编写了一个简化的示例程序。

  1. main 方法中触发了一个 panic,模拟程序出现异常:
shell
fn main() {
    panic!("Panic here.");
}
  1. 指定 rust-toolchainstable 1.81 或者更低的版本:
shell
[toolchain]
channel = "1.81"
  1. 交叉编译, 生成在 Android 上运行的二进制文件。在此过程中,我们可以回顾并巩固上一节的内容:
bash
export ANDROID_NDK_HOME=<YOUR_NDK_ROOT>
rustup target add aarch64-linux-android
cargo ndk --platform 28 -t aarch64-linux-android build --release
  1. 将二进制 push 到 Android 虚拟机,执行:
bash
RUST_BACKTRACE=full ./<二进制文件路径>
  1. 执行结果表明,未能成功获取到预期的 backtrace 信息。问题复现了!
bash
thread 'main' panicked at src/main.rs:2:5:
        Panic here
        stack backtrace:
           0:     0x5d908f7a7535 - <unknown>
           1:     0x5d908f7b336b - <unknown>
           2:     0x5d908f7a617f - <unknown>
           3:     0x5d908f7a8541 - <unknown>
           4:     0x5d908f7a817e - <unknown>
           5:     0x5d908f7a8fe8 - <unknown>
           6:     0x5d908f7a8ed3 - <unknown>
           7:     0x5d908f7a7a09 - <unknown>
           8:     0x5d908f7a8b94 - <unknown>
           9:     0x5d908f7b2753 - <unknown>
          10:     0x5d908f7a1e0c - <unknown>
          11:     0x5d908f7a1db3 - <unknown>
          12:     0x5d908f7a1da9 - <unknown>
          13:     0x5d908f7a4eb9 - <unknown>
          14:     0x5d908f7a1e35 - <unknown>
          15:     0x7d6d9c64478d - <unknown>

2.2 解决方案

我们先介绍一下解决方案,以便对问题原因不感兴趣的的小伙伴可以跳过下一节。

升级 Rust 工具链版本: 建议将 rust-toolchain 版本升级至 1.82 或更高。这个问题已经在 1.82 中被修复了(下一小节会介绍修复方法)。

自定义 Panic Hook: Rust 支持通过注册自定义的 panic hook 函数来替代默认行为。若无法升级 Rust 版本,可利用 backtrace-rs 库设置自定义 panic hook 函数。

Rust 默认的 panic hook 函数可能无法满足特定环境下的需求,例如,在 Android 平台上,可能倾向于将 panic 信息输出到文件或 logcat, 而默认的 panic hook 函数只是把 panic 信息输出到标准错误中。因此,很多场景下都需要我们自定义 panic hook 函数。

下面提供一个实现示例:

rust
pub fn set_panic_hook() {
    #[cfg(windows)]
    const LINE_ENDING: &str = "\r\n";
    #[cfg(not(windows))]
    const LINE_ENDING: &str = "\n";

    std::panic::set_hook(Box::new(move |panic| {
        let backtrace = backtrace::Backtrace::new();

        let Some(l) = panic.location() else {
            log::error!(
                "Panic: {:?}, backtrace: {}{:#?}",
                panic, LINE_ENDING, backtrace
            );
            return;
        };

        log::error!(
            "Panic: {:?}, file: {}, line: {}, col: {}, backtrace: {}{:#?}",
            panic,
            l.file(),
            l.line(),
            l.column(),
            LINE_ENDING,
            backtrace,
        );
    }));
}

输出的堆栈信息如下所示(在编译选项中去掉了 debug info, 且保留了符号表):

bash
pub fn set_panic_hook() {
    #[cfg(windows)]
    const LINE_ENDING: &str = "\r\n";
    #[cfg(not(windows))]
    const LINE_ENDING: &str = "\n";

    std::panic::set_hook(Box::new(move |panic| {
        let backtrace = backtrace::Backtrace::new();

        let Some(l) = panic.location() else {
            log::error!(
                "Panic: {:?}, backtrace: {}{:#?}",
                panic, LINE_ENDING, backtrace
            );
            return;
        };

        log::error!(
            "Panic: {:?}, file: {}, line: {}, col: {}, backtrace: {}{:#?}",
            panic,
            l.file(),
            l.line(),
            l.column(),
            LINE_ENDING,
            backtrace,
        );
    }));
}

补充说明:输出的堆栈信息与编译选项也有关系。如果把二进制中的符号表和 debug info 都去掉,会生成 unknown 的堆栈。如果保留 debug info,堆栈信息将更详细,但二进制的体积会增加很多。

2.3 问题剖析

接下来,我们将基于 Rust 1.81,探究一下之前提出的问题。

前置知识

  • Rust 标准库的 backtrace 依赖了 backtrace-rs 库,并以 git submodule 的形式集成到了 Rust 标准库中,详见这里

  • backtrace-rs 在编译构建时,会判断 Android 的 API 版本。如果大于等于 21,则会启用 dl_iterate_phdr 特性。详见这里(注:backtrace-rs 的版本是 Rust 1.81 依赖的版本,并不是最新版本)。

综合以上两点,Rust 标准库以 git submodule 的形式引入了 backtrace-rs,但是并没有执行 backtrace-rs 中的 build.rs 的构建逻辑,导致 dl_iterate_phdr 特性未能启用。那么标准库的 backtrace 就无法在 Android 上正常工作了。

破案了!

那么如何修复呢?

实际上,我们只需在标准库中启用 backtrace-rsdl_iterate_phdr 特性即可。但是从 #120593 开始,Rust 对 Android 的最小支持 API 版本从 19 提升至 21,并且 从 21 开始,Android 就支持了 dl_iterate_phdr,具体信息可以查看这里。所以我们可以在 backtrace-rs 库中直接默认开启 dl_iterate_phdr 特性,无需检测 Android 的 API 版本(Rust 1.82 也是这么修复的)。

相关 PR 链接

二进制体积优化实践

3.1 概述

在 Android 平台上开发基础服务时,二进制体积的重要性不容忽视。过大的二进制文件不仅会拖慢 OTA 更新的速度,还会降低软件热更新的效率,同时也会占用更多的存储空间。那么,我们该如何有效缩减 Rust 程序的体积呢?其实,GitHub 上的 Minimizing Rust Binary Size 项目已经详细总结了几乎所有有效的优化手段。感兴趣的读者可以前往该项目页面深入了解,这里不再赘述。

在这些优化手段中,有一项需要单独拎出来讲讲:去掉(strip)二进制中的符号表和调试信息(debug info)。这虽然能显著减小二进制体积,但也会带来一些问题。**例如,一旦程序 panic,堆栈信息将不可读,这无疑给调试工作带来了极大不便。**我们如何在缩减二进制体积的同时,又能在 panic 时获取有用的堆栈信息呢?

以运行在车机上的 GreptimeDB Edge 为例,提出一个方案:构建两个可执行文件,其中,一个通过去除符号表和调试信息实现了体积的最小化,专门用于在车机的生产环境中运行;而另一个则完整保留了符号表和调试信息,并备份到云端。当车机系统出现异常(如 panic 或 crash)时,我们能够从云端获取对应版本的二进制文件,并利用其中的符号表和调试信息来还原出人类可读的堆栈信息,从而为故障诊断和问题解决提供有力支持。

(图 2:以 GreptimeDB Edge 为例的二进制体积优化)
(图 2:以 GreptimeDB Edge 为例的二进制体积优化)

3.2 堆栈恢复

那么,堆栈恢复的具体过程又是怎样的呢?接下来,我们通过一个示例程序来具体说明。

下面是一个简化的 demo 程序。在运行过程中,该程序会触发 panic,并且设置了自定义的 panic hook。在 panic hook 中,会打印出各个 object 加载到内存中的位置,以及堆栈信息。

main.rs

rust
use std::panic;

use backtrace::Backtrace;
use tracing::error;

fn main() {
    tracing_subscriber::fmt::init();
    set_panic_hook();
    a();
}

#[inline(never)]
fn a() {
    b();
}

#[inline(never)]
fn b() {
    // panic here!!!
    panic!("Oh no!");
}

pub fn set_panic_hook() {
    log_base_addr();

    #[cfg(windows)]
    const LINE_ENDING: &str = "\r\n";

    #[cfg(not(windows))]
    const LINE_ENDING: &str = "\n";

    panic::set_hook(Box::new(move |panic| {
        let backtrace = Backtrace::new();

        let Some(l) = panic.location() else {
            error!(
                "Panic: {:?}, backtrace: {}{:#?}",
                panic, LINE_ENDING, backtrace
            );
            return;
        };

        error!(
            "Panic: {:?}, file: {}, line: {}, col: {}, backtrace: {}{:#?}",
            panic,
            l.file(),
            l.line(),
            l.column(),
            LINE_ENDING,
            backtrace,
        );
    }));
}

fn log_base_addr() {
    for o in phdrs::objects() {
        error!("Object name: {:?}, base addr: {:#x?}", o.name(), o.addr());
    }
}

Cargo.toml

rust
[package]
name = "panic_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
backtrace = "0.3"
phdrs = { git = "https://github.com/softdevteam/phdrs.git", rev = "86992b1e8e896a495387d931072c6088086eeabd" }
tracing = "0.1"
tracing-subscriber = "0.3"

[profile.release]
debug = true
opt-level = "z"
lto = true
codegen-units = 1

首先,在项目的根目录下执行以下命令,生成带有符号表和调试信息的二进制文件:

yaml
cargo build --release

注意,我们在 Cargo.toml 中覆盖了默认的编译选项 release。

编译完成后,进入 target/release 目录,将生成的 panic_demo 二进制文件复制一份,并命名为 panic_demo_with_strip ,然后使用 strip 工具去除 panic_demo_with_strip 中的符号表和调试信息。

yaml
cd target/release/
cp panic_demo panic_demo_with_strip
strip -s panic_demo_with_strip

通过上述步骤,我们最终在 target/release 目录下得到了两个二进制文件:panic_demo(带有完整的符号表和调试信息)和 panic_demo_with_strip(去除了符号表和调试信息)。

直接运行 panic_demo,结果,堆栈信息可读。因为 panic_demo 中携带了符号表与 debug info

rust
./panic_demo
rust
./panic_demo
2025-03-10T03:05:27.843007Z ERROR panic_demo: Object name: "", base addr: 0x5621ce28b000
2025-03-10T03:05:27.843075Z ERROR panic_demo: Object name: "linux-vdso.so.1", base addr: 0x738d45df3000
2025-03-10T03:05:27.843094Z ERROR panic_demo: Object name: "/usr/lib/libc.so.6", base addr: 0x738d45bd8000
2025-03-10T03:05:27.843107Z ERROR panic_demo: Object name: "/usr/lib/libgcc_s.so.1", base addr: 0x738d45baa000
2025-03-10T03:05:27.843120Z ERROR panic_demo: Object name: "/lib64/ld-linux-x86-64.so.2", base addr: 0x738d45df5000
2025-03-10T03:05:27.850986Z ERROR panic_demo: Panic: PanicHookInfo { payload: Any { .. }, location: Location { file: "src/main.rs", line: 20, col: 5 }, can_unwind: true, force_no_backtrace: false }, file: src/main.rs, line: 20, col: 5, backtrace:
   0:     0x5621ce2b4ba5 - panic_demo::set_panic_hook::{{closure}}::hfc3c6c380e6b0be5
                               at /home/fys/rust_target/projects/panic_demo/src/main.rs:33:25
   1:     0x5621ce2f0312 - <alloc::boxed::Box<F,A> as core::ops::function::Fn<Args>>::call::ha3fed88e6e913722
                               at /rustc/a4cb3c831823d9baa56c3d90514b75b2660116fa/library/alloc/src/boxed.rs:1984:9
                           std::panicking::rust_panic_with_hook::h1df75c095a4f3488
                               at /rustc/a4cb3c831823d9baa56c3d90514b75b2660116fa/library/std/src/panicking.rs:825:13
   2:     0x5621ce31b5a5 - std::panicking::begin_panic_handler::{{closure}}::hf3afa20cd541c11f
                               at /rustc/a4cb3c831823d9baa56c3d90514b75b2660116fa/library/std/src/panicking.rs:683:13
   3:     0x5621ce31b539 - std::sys::backtrace::__rust_end_short_backtrace::h3ced788cfddd85e3
                               at /rustc/a4cb3c831823d9baa56c3d90514b75b2660116fa/library/std/src/sys/backtrace.rs:168:18
   4:     0x5621ce31bebc - rust_begin_unwind
                               at /rustc/a4cb3c831823d9baa56c3d90514b75b2660116fa/library/std/src/panicking.rs:681:5
   5:     0x5621ce2d41ef - core::panicking::panic_fmt::ha07a50819406191f
                               at /rustc/a4cb3c831823d9baa56c3d90514b75b2660116fa/library/core/src/panicking.rs:75:14
   6:     0x5621ce2b4b81 - panic_demo::b::h9ebbf8c80464f859
                               at /home/fys/rust_target/projects/panic_demo/src/main.rs:20:5
   7:     0x5621ce2b4b4b - panic_demo::a::he4eee93f5289646a
                               at /home/fys/rust_target/projects/panic_demo/src/main.rs:14:5
   8:     0x5621ce2b47dd - panic_demo::main::he3522539407f83f5
                               at /home/fys/rust_target/projects/panic_demo/src/main.rs:9:5
   9:     0x5621ce2b2d4d - core::ops::function::FnOnce::call_once::h3e6e811b791b14aa
                               at /home/fys/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:250:5
                           std::sys::backtrace::__rust_begin_short_backtrace::h00639e0e41301441
                               at /home/fys/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/sys/backtrace.rs:152:18
  10:     0x5621ce2b5286 - main
  11:     0x738d45bff488 - <unknown>
  12:     0x738d45bff54c - __libc_start_main
  13:     0x5621ce2b2c55 - _start
  14:                0x0 - <unknown>

运行 panic_demo_with_strip,由于没有携带符号表和 Debug info,panic 后的堆栈信息不可读:

rust
./panic_demo_with_strip
yaml
❯ ./panic_demo_with_strip
2025-03-10T03:05:49.818147Z ERROR panic_demo: Object name: "", base addr: 0x59bba9fd8000
2025-03-10T03:05:49.818207Z ERROR panic_demo: Object name: "linux-vdso.so.1", base addr: 0x77bf1596d000
2025-03-10T03:05:49.818216Z ERROR panic_demo: Object name: "/usr/lib/libc.so.6", base addr: 0x77bf15752000
2025-03-10T03:05:49.818221Z ERROR panic_demo: Object name: "/usr/lib/libgcc_s.so.1", base addr: 0x77bf15724000
2025-03-10T03:05:49.818226Z ERROR panic_demo: Object name: "/lib64/ld-linux-x86-64.so.2", base addr: 0x77bf1596f000
2025-03-10T03:05:49.818808Z ERROR panic_demo: Panic: PanicHookInfo { payload: Any { .. }, location: Location { file: "src/main.rs", line: 20, col: 5 }, can_unwind: true, force_no_backtrace: false }, file: src/main.rs, line: 20, col: 5, backtrace:
   0:     0x59bbaa001ba5 - <unknown>
   1:     0x59bbaa03d312 - <unknown>
   2:     0x59bbaa0685a5 - <unknown>
   3:     0x59bbaa068539 - <unknown>
   4:     0x59bbaa068ebc - <unknown>
   5:     0x59bbaa0211ef - <unknown>
   6:     0x59bbaa001b81 - <unknown>
   7:     0x59bbaa001b4b - <unknown>
   8:     0x59bbaa0017dd - <unknown>
   9:     0x59bba9fffd4d - <unknown>
  10:     0x59bbaa002286 - <unknown>
  11:     0x77bf15779488 - <unknown>
  12:     0x77bf1577954c - __libc_start_main
  13:     0x59bba9fffc55 - <unknown>
  14:                0x0 - <unknown>

如何堆栈恢复呢?以地址 0x59bbaa001ba5 为例展示堆栈恢复的过程。

首先,在 panic hook 中,我们打印了各个 object 加载到内存中的位置。通过这些信息,可以判断出 0x59bbaa001ba5 是发生于 panic_demo_with_strip object 所处的内存地址中,因此我们可以计算出 0x59bbaa001ba5 基于 base address 的偏移量为 0x59bbaa001ba5 - 0x59bba9fd8000 - 1 = 0x29ba4

然后,使用 addr2line 工具,将地址转换为源代码中的文件名和行号。

⚠️ 如果在编译选项中设置了 lto = true,那么 GNU addr2line 可能不会正常工作。这个时候,需要使用 gimli-rs/addr2line

rust
addr2line -e panic_demo 0x29ba4
rust
➜ addr2line -e panic_demo 0x29ba4
/home/fys/rust_target/projects/panic_demo/src/main.rs:33

按照上面的方法,我们也可以将其他的地址转化为源代码中的文件名和行号。这样我们就恢复出了人类可读的堆栈信息!

总结

交叉编译一直是非常棘手的,可能会碰到各种各样的问题,并没有什么固定的解决方案,我们总是要针对特定的问题进行处理。幸运的是,Cargo NDK 和 Android NDK 提供了一套便捷的解决方案,帮助我们有效地应对了大部分的编译问题。

**通过本文的探讨,我们认识到交叉编译在 Android 环境中的重要性,以及 Rust 编译机制的优势。**虽然理想的编译过程在实践中会遇到诸多挑战,但希望我们的经验能为后续的开发提供一些实用的参考和启发。


关于 Greptime

Greptime 格睿科技专注于为可观测、物联网及车联网等领域提供实时、高效的数据存储和分析服务,帮助客户挖掘数据的深层价值。目前基于云原生的时序数据库 GreptimeDB 已经衍生出多款适合不同用户的解决方案,更多信息或 demo 展示请联系下方小助手(微信号:greptime)。

欢迎对开源感兴趣的朋友们参与贡献和讨论,从带有 good first issue 标签的 issue 开始你的开源之旅吧~期待在开源社群里遇见你!添加小助手微信即可加入“技术交流群”与志同道合的朋友们面对面交流哦~

Star us on GitHub Now: https://github.com/GreptimeTeam/greptimedb

官网:https://greptime.cn/

文档:https://docs.greptime.cn/

Twitter: https://twitter.com/Greptime

Slack: https://greptime.com/slack

LinkedIn: https://www.linkedin.com/company/greptime/

加入我们的社区

获取 Greptime 最新更新,并与其他用户讨论。