dqlite 分布式 SQLite 教程 / 第 2 章:安装与编译
第 2 章:安装与编译
本章介绍如何在 Linux 上从源码编译 dqlite、使用包管理器安装、Docker 快速部署,以及 Go 语言绑定的配置方法。
2.1 环境准备
dqlite 仅支持 Linux 系统,以下是最低环境要求:
| 项目 | 最低版本 | 说明 |
|---|---|---|
| 操作系统 | Linux(内核 3.10+) | 依赖 epoll,不支持 macOS/Windows |
| GCC | 9.0+ | 需要 C11 支持 |
| CMake | 3.16+ | 或 autotools(autoconf/automake) |
| autoconf | 2.69+ | 源码编译需要 |
| automake | 1.14+ | 源码编译需要 |
| libtool | 2.4+ | 源码编译需要 |
| pkg-config | 0.29+ | 库查找 |
| zlib | 1.2+ | 压缩支持 |
2.1.1 Ubuntu / Debian 安装依赖
sudo apt update
sudo apt install -y \
build-essential \
autoconf automake libtool \
pkg-config \
libuv1-dev \
zlib1g-dev \
liblz4-dev \
libsqlite3-dev
2.1.2 CentOS / RHEL / Fedora 安装依赖
# Fedora
sudo dnf install -y \
gcc make autoconf automake libtool \
pkg-config \
libuv-devel \
zlib-devel \
lz4-devel \
sqlite-devel
# CentOS / RHEL (需要 EPEL)
sudo yum install -y epel-release
sudo yum install -y \
gcc make autoconf automake libtool \
pkg-config \
libuv-devel \
zlib-devel \
lz4-devel \
sqlite-devel
2.1.3 Alpine Linux 安装依赖
apk add \
build-base \
autoconf automake libtool \
pkgconfig \
libuv-dev \
zlib-dev \
lz4-dev \
sqlite-dev
2.2 从源码编译
2.2.1 编译 libuv
dqlite 依赖 libuv 作为异步 I/O 库。如果你的系统没有提供 libuv-dev 包,需要手动编译:
# 下载 libuv
wget https://dist.libuv.org/dist/v1.48.0/libuv-v1.48.0.tar.gz
tar xzf libuv-v1.48.0.tar.gz
cd libuv-v1.48.0
# 编译安装
sh autogen.sh
./configure --prefix=/usr/local
make -j$(nproc)
sudo make install
sudo ldconfig
2.2.2 编译 dqlite
# 克隆源码
git clone https://github.com/canonical/dqlite.git
cd dqlite
# 检出最新稳定版
git checkout v1.16.6
# 编译
autoreconf -i
./configure
make -j$(nproc)
# 安装
sudo make install
sudo ldconfig
2.2.3 验证安装
# 检查库文件
ls -la /usr/local/lib/libdqlite*
# �期输出:
# /usr/local/lib/libdqlite.so -> libdqlite.so.0.0.1
# /usr/local/lib/libdqlite.so.0 -> libdqlite.so.0.0.1
# /usr/local/lib/libdqlite.so.0.0.1
# 检查头文件
ls /usr/local/include/dqlite.h
# 检查 pkg-config
pkg-config --modversion dqlite
# 预期输出:1.16.6(或类似版本号)
2.2.4 编译选项说明
| 选项 | 默认值 | 说明 |
|---|---|---|
--enable-debug | off | 启用调试模式(-g -O0) |
--enable-static | off | 编译静态库 |
--enable-build-raft | on | 内嵌编译 Raft 库 |
--with-lz4 | auto | 启用 LZ4 压缩 |
--prefix | /usr/local | 安装路径 |
启用调试模式编译:
./configure --enable-debug
make -j$(nproc)
sudo make install
2.3 使用系统包管理器
某些 Linux 发行版已经提供了 dqlite 包:
2.3.1 Ubuntu
# Ubuntu 22.04+ 提供了 dqlite 库包
sudo apt install libdqlite-dev
# 验证
dpkg -l | grep dqlite
注意: 发行版仓库中的版本可能较旧。如需最新版本,建议从源码编译。
2.3.2 Snap
# Canonical 的 LXD 项目打包了 dqlite
sudo snap install lxd
2.3.3 版本对比
| 安装方式 | 版本 | 更新频率 | 适用场景 |
|---|---|---|---|
| 源码编译 | 最新 | 手动 | 生产环境、开发 |
| Ubuntu APT | 较旧 | 跟随发行版 | 快速体验 |
| Snap | 较新 | 自动 | 与 LXD 配合 |
2.4 Docker 快速部署
使用 Docker 是快速体验 dqlite 的最佳方式。
2.4.1 单节点演示
# 使用官方 dqlite-demo 镜像
docker run -d --name dqlite-demo \
-p 9001:9001 \
-v dqlite-data:/data \
-e DQLITE_NODE_ID=1 \
-e DQLITE_BIND_ADDRESS=0.0.0.0:9001 \
-e DQLITE_DATA_DIR=/data \
dqlite/dqlite-demo:latest
注意: 截至本文编写时,dqlite 官方未提供统一的演示 Docker 镜像。以下方式是构建自定义镜像。
2.4.2 自定义 Dockerfile
# Dockerfile.dqlite
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y \
build-essential \
autoconf automake libtool \
pkg-config \
libuv1-dev \
zlib1g-dev \
liblz4-dev \
libsqlite3-dev \
wget \
&& rm -rf /var/lib/apt/lists/*
# 编译 dqlite
WORKDIR /build
RUN wget -q https://github.com/canonical/dqlite/archive/refs/tags/v1.16.6.tar.gz \
&& tar xzf v1.16.6.tar.gz \
&& cd dqlite-1.16.6 \
&& autoreconf -i \
&& ./configure --prefix=/usr \
&& make -j$(nproc) \
&& make install \
&& ldconfig
# 编译 demo 程序
COPY demo.c /build/demo.c
RUN gcc -o /usr/local/bin/dqlite-demo /build/demo.c -ldqlite -lpthread
# 数据目录
RUN mkdir -p /data
VOLUME /data
EXPOSE 9001
CMD ["dqlite-demo"]
2.4.3 构建并运行
# 构建镜像
docker build -t my-dqlite:latest -f Dockerfile.dqlite .
# 运行
docker run -d --name dqlite-node1 \
-p 9001:9001 \
-v dqlite-node1-data:/data \
my-dqlite:latest
2.5 C 语言库集成
2.5.1 头文件和库文件
编译安装 dqlite 后,系统中会有以下文件:
| 文件 | 路径 | 说明 |
|---|---|---|
| 头文件 | /usr/local/include/dqlite.h | C API 声明 |
| 共享库 | /usr/local/lib/libdqlite.so | 动态链接库 |
| pkg-config | /usr/local/lib/pkgconfig/dqlite.pc | 编译配置 |
2.5.2 编译链接
使用 pkg-config:
gcc -o myapp myapp.c $(pkg-config --cflags --libs dqlite) -lpthread
手动指定路径:
gcc -o myapp myapp.c \
-I/usr/local/include \
-L/usr/local/lib \
-ldqlite -lpthread \
-Wl,-rpath,/usr/local/lib
2.5.3 最小程序示例
/* minimal_dqlite.c */
#include <dqlite.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
dqlite_node *node;
int rc;
/* 1. 创建节点 */
rc = dqlite_node_create(
1, /* 节点 ID */
"/tmp/dqlite-minimal-data", /* 数据目录 */
"127.0.0.1:9001", /* 绑定地址 */
&node /* 输出节点指针 */
);
if (rc != 0) {
fprintf(stderr, "Create failed: %d\n", rc);
return EXIT_FAILURE;
}
/* 2. 启动节点 */
rc = dqlite_node_start(node);
if (rc != 0) {
fprintf(stderr, "Start failed: %s\n", dqlite_node_errmsg(node));
dqlite_node_destroy(node);
return EXIT_FAILURE;
}
printf("dqlite node started on 127.0.0.1:9001\n");
printf("Data dir: /tmp/dqlite-minimal-data\n");
/* 3. 等待(实际应用中在此处理业务逻辑) */
printf("Press Enter to stop...\n");
getchar();
/* 4. 停止并清理 */
dqlite_node_stop(node);
dqlite_node_destroy(node);
return EXIT_SUCCESS;
}
编译和运行:
gcc -Wall -o minimal_dqlite minimal_dqlite.c $(pkg-config --cflags --libs dqlite) -lpthread
./minimal_dqlite
2.5.4 CMake 集成
在你的 CMake 项目中使用 dqlite:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(my_dqlite_app C)
set(CMAKE_C_STANDARD 11)
# 查找 dqlite
find_package(PkgConfig REQUIRED)
pkg_check_modules(DQLITE REQUIRED dqlite)
add_executable(myapp src/main.c)
target_include_directories(myapp PRIVATE ${DQLITE_INCLUDE_DIRS})
target_link_libraries(myapp ${DQLITE_LIBRARIES} pthread)
2.5.5 Makefile 示例
CC = gcc
CFLAGS = -Wall -Wextra -O2 $(shell pkg-config --cflags dqlite)
LDFLAGS = $(shell pkg-config --libs dqlite) -lpthread
SRCS = $(wildcard src/*.c)
OBJS = $(SRCS:.c=.o)
TARGET = myapp
all: $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
%.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f $(OBJS) $(TARGET)
.PHONY: all clean
2.6 Go 语言绑定
go-dqlite 是 Canonical 维护的 Go 语言绑定,LXD 和 MicroK8s 都使用它。
2.6.1 安装 go-dqlite
# 前提:已安装 Go 1.21+ 和编译好的 libdqlite
go get github.com/canonical/go-dqlite/v2
2.6.2 Go 绑定工作原理
go-dqlite 提供了 database/sql 驱动,可以直接使用标准 Go SQL 接口:
┌─────────────────────────────────┐
│ Go Application │
│ ┌───────────────────────────┐ │
│ │ database/sql 接口 │ │
│ └─────────┬─────────────────┘ │
│ │ │
│ ┌─────────▼─────────────────┐ │
│ │ go-dqlite driver │ │
│ │ ┌─────────────────────┐ │ │
│ │ │ CGO → libdqlite.so │ │ │
│ │ └─────────────────────┘ │ │
│ └───────────────────────────┘ │
└─────────────────────────────────┘
2.6.3 Go 示例程序
package main
import (
"context"
"database/sql"
"fmt"
"log"
"os"
"time"
dqlite "github.com/canonical/go-dqlite/v2"
"github.com/canonical/go-dqlite/v2/driver"
)
func main() {
dir, err := os.MkdirTemp("", "dqlite-example")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
// 创建节点
node, err := dqlite.New(1, "127.0.0.1:9001", dir, nil)
if err != nil {
log.Fatal(err)
}
defer node.Close()
// 创建数据库驱动
drv, err := driver.New(map[uint64]string{1: "127.0.0.1:9001"},
driver.WithLogFunc(func(level dqlite.LogLevel, msg string, args ...interface{}) {
fmt.Printf("[%s] %s\n", level, fmt.Sprintf(msg, args...))
}),
)
if err != nil {
log.Fatal(err)
}
defer drv.Close()
// 打开数据库
db := sql.OpenDB(drv)
defer db.Close()
// 等待连接就绪
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
log.Fatal(err)
}
// 创建表
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
)`)
if err != nil {
log.Fatal(err)
}
// 插入数据
result, err := db.Exec("INSERT INTO users (name, email) VALUES (?, ?)",
"张三", "zhangsan@example.com")
if err != nil {
log.Fatal(err)
}
id, _ := result.LastInsertId()
fmt.Printf("Inserted user id: %d\n", id)
// 查询
var name, email string
err = db.QueryRow("SELECT name, email FROM users WHERE id = ?", id).Scan(&name, &email)
if err != nil {
log.Fatal(err)
}
fmt.Printf("User: %s <%s>\n", name, email)
}
2.6.4 Go 项目结构建议
my-dqlite-app/
├── go.mod
├── go.sum
├── main.go
├── node/
│ ├── node.go # dqlite 节点管理
│ └── config.go # 配置加载
├── store/
│ ├── db.go # 数据库操作封装
│ └── migrate.go # Schema 迁移
└── Makefile
2.6.5 CGO 依赖注意事项
go-dqlite 使用 CGO 调用 libdqlite.so,需要注意以下事项:
| 事项 | 说明 |
|---|---|
| CGO_ENABLED=1 | Go 编译时必须启用 CGO |
| libdqlite.so 在链接路径中 | 通过 LD_LIBRARY_PATH 或 ldconfig |
| 编译机器需要 libdqlite-dev | 交叉编译需要对应架构的库 |
| 静态编译 | 需要 libdqlite 的 .a 文件 |
静态编译示例:
CGO_ENABLED=1 go build \
-ldflags '-linkmode external -extldflags "-static -ldqlite -lraft -lsqlite3 -luv -llz4 -lpthread"' \
-o myapp .
2.7 常见问题
2.7.1 编译错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
dqlite.h: No such file | 头文件未找到 | 安装 libdqlite-dev 或指定 -I 路径 |
libdqlite.so: cannot open | 运行时找不到库 | 执行 sudo ldconfig 或设置 LD_LIBRARY_PATH |
libuv not found | libuv 未安装 | sudo apt install libuv1-dev |
undefined reference to sqlite3_* | SQLite 库未链接 | 添加 -lsqlite3 |
configure: error: liblz4 not found | LZ4 库缺失 | sudo apt install liblz4-dev |
2.7.2 运行时错误
| 错误 | 原因 | 解决方案 |
|---|---|---|
bind: address already in use | 端口被占用 | 更换端口或关闭占用进程 |
permission denied | 数据目录权限不足 | 检查目录权限 |
no such file or directory | 数据目录不存在 | 先创建数据目录 |
2.7.3 数据目录准备
# 创建数据目录
mkdir -p /var/lib/dqlite/node1
# 设置权限(建议使用专用用户)
sudo useradd -r -s /bin/false dqlite
sudo chown -R dqlite:dqlite /var/lib/dqlite
2.8 开发环境配置
2.8.1 VS Code 配置
创建 .vscode/c_cpp_properties.json:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/usr/local/include"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"intelliSenseMode": "linux-gcc-x64",
"browse": {
"path": ["/usr/local/include"],
"limitSymbolsToIncludedHeaders": true
}
}
],
"version": 4
}
2.8.2 使用 Docker 作为开发环境
# docker-compose.dev.yml
version: '3.8'
services:
dev:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/workspace
- dqlite-dev-data:/data
working_dir: /workspace
stdin_open: true
tty: true
cap_add:
- SYS_PTRACE
security_opt:
- seccomp:unconfined
volumes:
dqlite-dev-data:
# Dockerfile.dev
FROM ubuntu:24.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
build-essential \
autoconf automake libtool \
pkg-config \
libuv1-dev \
zlib1g-dev \
liblz4-dev \
libsqlite3-dev \
gdb valgrind \
vim git curl wget \
golang-go \
&& rm -rf /var/lib/apt/lists/*
# 编译安装 dqlite
WORKDIR /tmp
RUN git clone --depth 1 --branch v1.16.6 https://github.com/canonical/dqlite.git \
&& cd dqlite \
&& autoreconf -i \
&& ./configure --enable-debug --prefix=/usr \
&& make -j$(nproc) \
&& make install \
&& ldconfig \
&& rm -rf /tmp/dqlite
WORKDIR /workspace
CMD ["/bin/bash"]
本章小结
| 要点 | 说明 |
|---|---|
| 系统要求 | Linux only,需要 GCC、libuv、SQLite |
| 源码编译 | autoreconf -i && ./configure && make |
| Docker | 最快的上手方式 |
| C 集成 | 链接 libdqlite.so,使用 pkg-config |
| Go 绑定 | go-dqlite 通过 CGO 调用 libdqlite |
| 数据目录 | 需要预先创建并设置正确权限 |
下一章
→ 第 3 章:架构深度解析 — 深入了解 dqlite 的内部架构,包括 Raft 共识、日志复制、快照机制和成员变更。