OpenTenBase 是腾讯开源的企业级分布式数据库,但官方支持列表里并没有 Ubuntu 22.04。
这篇教程记录了在 Ubuntu 22.04 上从零开始源码编译、部署并验收一套单机分布式集群
(1 个 GTM + 1 个 CN + 2 个 DN)的完整过程,重点是解决官方文档没有覆盖到的
依赖与兼容性问题。全部命令均在实测环境中验证通过。

本文为第 3 版(2026-09-25 修订):新增「写在前面」阅读须知与「日常使用」两章,
为全部命令补充了逐行注释,修正了依赖清单(uuid-dev 与 libossp-uuid-dev 须同时安装),
并全面扩充了排错表与实测踩坑记录。

目录

  1. 写在前面(阅读须知)
  2. 引言
  3. 环境准备
  4. 获取源码
  5. 编译前的两处源码修改(核心)
  6. 编译与安装
  7. 配置集群
  8. 部署与启动集群
  9. 数据库初始化与验收
  10. 日常使用:重启后启动集群与基础 SQL 操作
  11. 常见问题与排错
  12. 附录

写在前面(阅读须知)

本章说明本教程使用的记号约定与几个常用操作,后续章节不再重复。请按章节顺序依次执行;每一步均提供验证方法,验证通过后再进行下一步。

0.1 终端的基本操作

终端(Terminal)是执行命令行操作的窗口,本教程所有命令均在终端中执行。在 Ubuntu 桌面按 Ctrl+Alt+T 即可打开一个新终端。

两组容易混淆的快捷键:在终端中,Ctrl+Shift+C 为复制、Ctrl+Shift+V 为粘贴(并非 Windows 习惯的 Ctrl+C / Ctrl+V);单独按 Ctrl+C 的作用是终止当前正在运行的命令,后文排错时会用到。

0.2 提示符与输入约定

命令行最左侧会显示提示符,用于标识当前用户与环境。提示符本身不是命令的一部分,复制命令时不要将其带上。本教程会出现的提示符见表 0-1。

表 0-1 本教程中出现的提示符

看到的字符 含义
lzh@ubuntu:~$ 你自己的普通用户(本机装系统时的用户,本文以 lzh 为例)
root@ubuntu:~# root 超级用户提示符(本教程不直接使用 root)
opentenbase@ubuntu:~$ 第二章创建的 opentenbase 用户
pgxc_ctl> 第七章启动 pgxc_ctl 工具后的交互提示符
postgres=# 第八章进入 psql 数据库命令行后的提示符
\(行尾) 续行符,表示这条命令没写完,下一行接着写

输入约定:以 # 开头的行是注释,解释下一行或同一行命令的含义,不需要输入到终端;以 -- 开头的是 SQL 注释,同样不用输入。带续行符 \ 的命令请整块复制,包括反斜杠。

0.3 开始前的准备

确认系统版本。打开终端执行 lsb_release -a,Distributor 一行应显示 Ubuntu,Release 应为 22.04。其他版本不保证本文的补丁步骤适用。

给虚拟机拍快照。本教程涉及系统级配置修改,建议在 VMware 菜单中先执行「虚拟机 → 快照 → 拍摄快照」。后续任一步骤出现异常时,可通过恢复快照快速回到初始状态。

检查硬件。执行下面两条命令,对照第二章 2.1 节的最低要求。

# 查看内存:Mem 行的 total 应在 8G 左右,低于 4G 编译会非常吃力
free -h

# 查看磁盘:Filesystem 行里挂在 / 的分区 Avail 一列应预留 5G 以上
df -h

0.4 常用应急操作

  • 终止当前命令:命令长时间未结束时,按 Ctrl+C 终止并回到提示符,不会造成任何破坏。
  • 退出 vim:编辑过程中按键混乱时,先按 Esc 键,再依次输入 :q! 回车,不保存退出,文件保持原样。第六章编辑配置文件时会给出 vim 的具体按键步骤。
  • 退出交互界面:普通命令行输入 exit;pgxc_ctl 管理界面输入 exit;psql 数据库交互界面输入 \q。后文用到时会再次说明。

一、引言

1.1 OpenTenBase 是什么

OpenTenBase 是腾讯开源的企业级分布式数据库管理系统,由 OpenAtom 开放原子基金会孵化,前身是 TDSQL 社区版与 Postgres-XL 项目。它采用无共享(share-nothing)架构,集群由三类节点组成:

  • GTM(全局事务管理器):负责分布式事务号与全局一致性;
  • CN(协调节点 Coordinator):只存元数据,接收 SQL 后拆分下发;
  • DN(数据节点 DataNode):存储分片数据。

用户统一连接 CN,对上层应用透明。内核基于 PostgreSQL 10,兼容大部分 PG 语法与生态工具。本教程在 Ubuntu 22.04 上从零开始源码编译、部署并验收一套单机分布式集群(1 个 GTM + 1 个 CN + 2 个 DN),重点是解决官方文档没有覆盖到的依赖与兼容性问题。全部命令均在下述实测环境中验证通过。

1.2 实测环境

表 1-1 实测环境

项目 配置
操作系统 Ubuntu 22.04.5 LTS(jammy)
内核 6.8.0-138-generic
虚拟化平台 VMware Workstation(Windows 宿主机)
CPU / 内存 4 核 / 8 GB(可用约 7.7 GB)
磁盘 数据盘挂载在 /data,建议预留 20 GB 以上
软件版本 OpenTenBase master 分支(内核号 V5.21,安装目录名 opentenbase_bin_v5.0)
集群拓扑 单机:GTM :20001 + CN cn001 :30004 + DN dn001 :40004 + DN dn002 :40007

1.3 为什么需要这篇教程

官方 README 列出的支持系统是 TencentOS 2/3、OpenCloudOS 8.x、CentOS 7/8 和 Ubuntu 18.04,Ubuntu 22.04 并不在列。直接照官方步骤在 Ubuntu 22.04 上编译,会先后碰到 configure 硬编码路径、C 语言 bool 类型冲突等多个报错,本教程逐一给出原因分析与解决方案。

二、环境准备

2.1 硬件要求

官方要求最低 8 GB 内存。实测 make -sj3 编译时峰值占用约 4 GB,8 GB 内存的虚拟机足够;若只有 4 GB,请把编译并发降到 -sj2 并适当加大 swap。磁盘方面,源码约 500 MB、编译中间产物约 2 GB、安装产物约 200 MB、四个数据实例初始化后约 1 GB,共预留 5 GB 以上,生产环境建议 20 GB 以上。用 0.3 节的 free -h 和 df -h 自查。

2.2 安装系统依赖

在 Ubuntu 桌面的终端里,以你自己的用户(如 lzh)执行以下命令。sudo 会要求输入当前用户(lzh)的密码;输入过程中屏幕不显示任何字符,属正常安全机制,输入完成后直接回车。

# 更新 APT 软件包索引,确保能获取到最新软件包信息
sudo apt update

# 安装编译工具、常用开发库、SSH 工具和中文语言包
# -y:自动确认安装,不再逐个询问
# \  :Shell 续行符,表示下一行仍是同一条命令,整块复制即可
sudo apt install -y git gcc g++ make cmake bison flex \
    libreadline-dev zlib1g-dev libssl-dev libxml2-dev \
    libossp-uuid-dev uuid-dev libzstd-dev liblz4-dev libicu-dev \
    libcurl4-openssl-dev libssh2-1-dev sshpass \
    openssh-server language-pack-zh-hans

上面前半部分与官方 README 一致,但有几个包是官方清单没写、实际必需的,见表 2-1。其中 uuid-dev 与 libossp-uuid-dev 都要装:前者提供 uuid 库本身,后者提供 configure 检测的 OSSP UUID 接口,缺任一个都会在 configure 阶段报库缺失。如果还需要在虚拟机里截图做验收记录,可以额外安装 scrot。

表 2-1 关键依赖说明(★ 为官方清单未列出但实际必需)

软件包 用途 备注
libzstd-dev ★ 压缩库,configure 强制检测 官方清单未列出,缺少则 configure 报错
liblz4-dev ★ 压缩库,configure 强制检测 同上
g++ ★ C++ 编译器 编译 contrib 阶段需要,缺少则后半段 make 报错
libicu-dev ★ Unicode 支持 部分模块依赖
libcurl4-openssl-dev ★ HTTP 客户端库 部分模块依赖
libossp-uuid-dev ★ UUID 支持(OSSP 接口) 配合 –with-ossp-uuid
uuid-dev ★ UUID 库开发文件 与 libossp-uuid-dev 同时安装,缺一则 configure 报库缺失
libreadline-dev psql 交互命令行 官方清单已含
libssl-dev OpenSSL 加密支持 Ubuntu 22.04 为 OpenSSL 3.0,引出第四章的 bool 补丁
openssh-server SSH 服务 pgxc_ctl 高度依赖 SSH

2.3 创建 opentenbase 用户

OpenTenBase 不允许用 root 运行,需要专用的运行用户。本教程约定:家目录与所有数据都放在 /data/opentenbase。

# 创建 /data 目录
# -p:递归创建父目录;如果目录已存在也不会报错
sudo mkdir -p /data

# 创建 opentenbase 用户
# -d /data/opentenbase:指定该用户的家目录为 /data/opentenbase
# -s /bin/bash:指定该用户登录后使用的 Shell 为 bash
# -m:如果家目录不存在,则自动创建
sudo useradd -d /data/opentenbase -s /bin/bash -m opentenbase

# 为 opentenbase 用户设置密码
# 会交互式提示输入两次新密码;同样输入时不回显
# 本教程以 opentenbase 为例,请记牢这个密码
sudo passwd opentenbase

# 将 opentenbase 用户追加到 sudo 组
# -aG:追加到附加组,不会覆盖用户原有的其他附加组
sudo usermod -aG sudo opentenbase

# 递归修改 /data/opentenbase 的属主、属组为 opentenbase
# -R:递归处理子目录和文件
sudo chown -R opentenbase:opentenbase /data/opentenbase

说明:后文多处出现 su - opentenbase:此时输入的是 opentenbase 的密码,而非 lzh 的。若报「认证失败」,多为密码输入有误;忘记密码时可用 sudo passwd opentenbase 重设。

2.4 配置 SSH 免密登录

pgxc_ctl 部署工具把每个节点都当作远程主机,即使全部节点都在本机 127.0.0.1,它也会通过 SSH 下发命令。因此 opentenbase 用户必须能免密 SSH 到自己,否则 deploy all 会反复要求输入密码甚至卡死。

# 切换到 opentenbase 用户(输入 2.3 节设置的密码)
# - 表示以登录方式切换,会加载该用户的环境变量和家目录配置
su - opentenbase

# 生成 SSH 密钥对
# -t rsa:指定密钥类型为 RSA
# -N "":设置私钥密码为空,便于实现免密登录
# -f ~/.ssh/id_rsa:指定私钥文件路径,公钥为 ~/.ssh/id_rsa.pub
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa

# 将刚生成的公钥追加到授权密钥文件中
# >> 表示追加写入,避免覆盖已有公钥
cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

# 修改 authorized_keys 文件权限为 600
# SSH 对权限要求严格,权限过大可能导致免密登录失效
chmod 600 ~/.ssh/authorized_keys

# 测试本机 SSH 免密登录:登录后执行 date 并返回结果
# 首次连接会问 Are you sure you want to continue connecting (yes/no)?
# 手动输入 yes 回车(不能只按回车)
ssh 127.0.0.1 date

# 退出 opentenbase 用户,回到原来的用户会话
exit

验证标准:执行 ssh 127.0.0.1 date 后不再询问密码、直接打印出当前日期时间,即为成功。若仍要求输入密码,检查 chmod 600 这一步是否执行过。

注意(实测踩坑):若跳过 ssh-keygen 直接执行 cat 追加公钥,会报 -bash: /data/opentenbase/.ssh/authorized_keys: No such file or directory——报错出自 bash 而非 cat,说明 ~/.ssh 目录本身尚不存在(该目录由 ssh-keygen 自动创建),authorized_keys 自然无从创建,ssh 127.0.0.1 date 也仍会要求密码。解决:补执行 ssh-keygen 生成密钥对,再依次重做 cat 追加与 chmod 600,直至验证通过。

三、获取源码

3.1 方式一:git clone(首选)

# 切换到 opentenbase 用户
su - opentenbase

# 进入 opentenbase 用户的家目录,源码将下载到这里
cd /data/opentenbase

# 从 GitHub 克隆 OpenTenBase 源码仓库
# 克隆完成后会在当前目录下生成 OpenTenBase 文件夹
git clone https://github.com/OpenTenBase/OpenTenBase.git

3.2 方式二:网络不顺时的替代方案

GitHub 直连不稳定时,git clone 常见两类报错:GnuTLS recv error (-110) 和 Connection refused。以下两个方案均实测可用,优先用方案 A。

# ============================================================
# 方案 A:通过 codeload 直接下载源码压缩包(约 36 MB,不走 git 协议)
# ============================================================

# 下载 master 分支源码压缩包
# -L:跟随 HTTP 重定向(codeload 地址通常会重定向)
# -o:将下载内容保存为指定文件名
curl -L -o OpenTenBase-master.tar.gz \
    https://codeload.github.com/OpenTenBase/OpenTenBase/tar.gz/refs/heads/master

# 解压下载的 tar.gz 压缩包
# z:使用 gzip 解压;x:解压归档;f:指定要解压的文件
tar zxf OpenTenBase-master.tar.gz

# 解压生成的目录名是 OpenTenBase-master,必须改名为 OpenTenBase,
# 否则第四章的 cd /data/opentenbase/OpenTenBase 会失败:
mv OpenTenBase-master OpenTenBase

# ============================================================
# 方案 B:从 gitcode 平台镜像仓库克隆(国内网络更快)
# ============================================================

# git clone 会在当前目录下直接创建 OpenTenBase 目录,无需改名
git clone https://gitcode.com/OpenTenBase/OpenTenBase.git

目录名必须统一:两种方式获取的代码完全一致,但目录名必须统一为 /data/opentenbase/OpenTenBase(方案 A 下载的目录名为 OpenTenBase-master,必须执行 mv 改名)。无论采用哪种方式,均应执行以下命令验证,能列出 src、contrib 等目录方为通过:

# 验证源码目录就位:应输出 contrib  src  configure 等内容
ls /data/opentenbase/OpenTenBase

四、编译前的两处源码修改(核心)

这一章是整个安装过程中唯一需要动脑子的地方,也是本教程与官方文档的主要差异。两处修改都源于同一个背景:腾讯内部的编译环境(BINARYLIBS 机制)与我们本地的 Ubuntu 22.04 不同,但开源出的配置脚本没有完全适配。

4.1 问题一:configure 硬编码了 libzstd / liblz4 的绝对路径

现象:即使已经安装了 libzstd-dev,configure 仍然报错:

checking for zstd ... configure: error: zstd library not found

原因:用 grep 检查 configure 脚本可以发现,它没有使用系统动态库,而是硬编码了腾讯内部 BINARYLIBS 环境的静态库绝对路径(共 4 处,行号在 12802、12837、12852、12887 附近):

# 在 configure 脚本中搜索硬编码的 libzstd.a 和 liblz4.a 路径
# -n:显示匹配内容所在的行号
# "libzstd.a\|liblz4.a":基本正则表达式,\| 表示「或」
grep -n "libzstd.a\|liblz4.a" configure

# 输出说明(Ubuntu 上这两个文件通常并不存在):
# 12802:  LIBS="/usr/local/lib/libzstd.a $LIBS"
# 12837:  LIBS="/usr/local/lib/liblz4.a $LIBS"

解决:把硬编码路径改成链接器参数,让它去链接 apt 安装的系统库(依赖 2.2 节安装的 libzstd-dev 和 liblz4-dev):

# 进入 OpenTenBase 源码目录
cd /data/opentenbase/OpenTenBase

# 使用 sed 原地修改 configure 脚本:
# -i:直接修改文件内容,不输出到终端
# s|旧内容|新内容|g:替换语法,用 | 作分隔符避免与路径中的 / 冲突;g 表示全局替换
# 将 /usr/local/lib/libzstd.a 替换为 -lzstd(链接系统 libzstd 库)
# 将 /usr/local/lib/liblz4.a  替换为 -llz4 (链接系统 liblz4 库)
sed -i "s|/usr/local/lib/libzstd.a|-lzstd|g; s|/usr/local/lib/liblz4.a|-llz4|g" configure

# 验证替换结果:应看到 -lzstd / -llz4 出现在原来硬编码路径的位置
# head:只显示前 10 行,避免输出过多
grep -n "lzstd\|llz4" configure | head

4.2 问题二:char bool 与 OpenSSL 3.0 的 stdbool.h 冲突

现象:configure 顺利通过后,make 在编译后端源文件时大量报错:

In file included from src/backend/access/common/heaptuple.c:26:
src/include/access/htup_details.h:706:15:
error: conflicting types for 'getmissingattr';
have 'Datum(const TupleDescData *, int, bool *)'
{aka 'long unsigned int(const struct TupleDescData *, int, _Bool *)'}
note: previous declaration of 'getmissingattr' was here (tuptable.h, bool * -> char *)

原因:内核头文件 src/include/c.h 在 C 语言下用 typedef char bool; 自定义了 bool 类型(PostgreSQL 10 时代的写法)。而 Ubuntu 22.04 的 OpenSSL 是 3.0 版本,开启 –with-openssl 编译时,它的头文件会间接把标准头文件 <stdbool.h> 拉进同一个编译单元,其中 _Bool 是编译器内建类型,与 char 定义冲突,同一函数就出现了两个不同的原型。

解决:在 c.h 的 bool 定义之前提前引入标准 <stdbool.h>,让整个工程统一使用标准 _Bool。为避免手改出错,直接用下面的命令自动修改(先备份、后验证):

# 进入 OpenTenBase 源码目录
cd /data/opentenbase/OpenTenBase

# 备份原始 c.h,便于出错时恢复
cp src/include/c.h src/include/c.h.bak

# 用 Python 自动修改 src/include/c.h
# <<'EOF' 是 heredoc 写法:把两个 EOF 之间的内容原样传给 python3 执行
# 引号中的单引号表示不做 Shell 变量替换
python3 - <<'EOF'
import io

# 目标文件路径
p = "src/include/c.h"

# 读取整个文件
s = io.open(p, encoding="utf-8").read()

# 原始代码片段:bool 定义前没有引入 stdbool.h
old = "#ifndef bool\ntypedef char bool;"

# 替换后的片段:先引入标准 stdbool.h,再保留原有逻辑
new = "#include <stdbool.h>\n\n#ifndef bool\ntypedef char bool;"

# 断言原片段存在,避免匹配不到时静默写入无变化的内容
assert old in s, "pattern not found"

# 只替换第一处(count=1)并写回
io.open(p, "w", encoding="utf-8").write(s.replace(old, new, 1))

# 输出成功提示
print("c.h patched")
EOF

# 验证:应输出包含 #include <stdbool.h> 的那一行及其行号
grep -n "stdbool" src/include/c.h

# 再看一眼上下文,确认插入位置在 #ifndef bool 之前
grep -n -A 6 -B 2 "stdbool" src/include/c.h

重要:如果中途重新解压了源码包,两处修改都会被覆盖,必须重新执行本章的两个补丁步骤再编译。

4.3 其他常见报错速查

表 4-1 依赖类报错速查表

报错信息 原因 解决
g++: No such file or directory(编译 contrib 阶段) 未安装 C++ 编译器 sudo apt install g++
configure: error: ICU library not found 缺 libicu-dev sudo apt install libicu-dev
configure: error: libraries … curl 或 uuid 相关 缺对应 dev 包 sudo apt install libcurl4-openssl-dev uuid-dev
Could not get lock /var/lib/dpkg/lock-frontend 后台 unattended-upgrades 正在占用 apt 锁 等待其结束(ps -p <PID> 确认进程退出)后再装;不要强删锁文件

五、编译与安装

5.1 configure 参数说明

表 5-1 configure 参数说明

参数 作用
–prefix=…/opentenbase_bin_v5.0 安装目录。目录名必须叫 opentenbase_bin_v5.0,集群工具按此约定寻址
–enable-user-switch 允许以普通用户(非 root)启动实例
–with-libxml 启用 XML 类型与函数
–disable-license 关闭腾讯内部 license 校验,社区版必选
–with-openssl 启用加密连接(会触发 4.2 节的补丁需求)
–with-ossp-uuid 启用 UUID 生成函数
CFLAGS=”-g” 保留调试符号,便于出错时定位

5.2 一次性干净构建

重要经验:务必从全新解压的源码一次性完成 configure 和 make。多次反复 configure 会把不同时刻生成的二进制混在一起,埋下 5.4 节的版本不一致问题(笔者实测两个二进制的编译时间戳相差了 12 分钟,导致 initdb 拒绝启动 postgres)。如果之前编译失败过,先删掉源码目录和安装目录,重新解压、重打第四章两个补丁,再执行:

# 切换到 opentenbase 用户
su - opentenbase

# 设置源码目录环境变量,指向 OpenTenBase 源码根目录
export SOURCECODE_PATH=/data/opentenbase/OpenTenBase

# 设置安装目录环境变量,后续编译安装都放在这个目录下
export INSTALL_PATH=/data/opentenbase/install

# 确保安装目录存在,避免后续安装因目录缺失而失败
mkdir -p $INSTALL_PATH

# 进入源码目录
cd $SOURCECODE_PATH

# 删除旧的安装目录,确保本次是干净安装
# -r:递归删除;-f:强制删除不提示
rm -rf $INSTALL_PATH/opentenbase_bin_v5.0

# 赋予 configure 脚本可执行权限
chmod +x configure

# 运行 configure 进行编译配置
# --prefix:安装到 $INSTALL_PATH/opentenbase_bin_v5.0
# 其余参数含义见表 5-1
./configure --prefix=$INSTALL_PATH/opentenbase_bin_v5.0 \
    --enable-user-switch --with-libxml --disable-license \
    --with-openssl --with-ossp-uuid CFLAGS="-g"

# 编译源码(约 20~40 分钟)
# -s:静默模式;-j3:并行 3 个任务,8 GB 内存建议 3,充足可到 4
make -sj3

# 将编译好的文件安装到 --prefix 指定的目录
make install

环境变量只在当前终端有效:export 设置的 SOURCECODE_PATH 和 INSTALL_PATH 只在当前这个终端窗口里有效,新开一个终端或断开重连后就没了,需要重新执行上面两行 export。如果后面哪一步报「变量不存在」或路径为空,先检查是不是换了终端。

5.3 编译 contrib 工具(pgxc_ctl 在这里)

主体 make install 不会安装集群管理工具 pgxc_ctl,它在 contrib 目录里,需要单独编译(这一步会用到 g++):

# 回到源码根目录($SOURCECODE_PATH 是 5.2 节设置的环境变量)
cd $SOURCECODE_PATH

# 赋予 contrib/pgxc_ctl/make_signature 脚本可执行权限
# 不赋权可能导致后续步骤失败
chmod +x contrib/pgxc_ctl/make_signature

# 进入 contrib 目录(包含附加模块和扩展工具)
cd contrib

# 编译 contrib 下所有模块
make -sj3

# 安装到 configure 时指定的 --prefix 目录下
make install

5.4 版本一致性验证(必做)

安装完成后验证 initdb 与 postgres 的版本字符串必须完全一致,尤其是末尾的编译时间戳:

# 查看 initdb 的版本字符串,重点看末尾的编译时间戳
$INSTALL_PATH/opentenbase_bin_v5.0/bin/initdb --version

# 查看 postgres 的版本字符串,必须与上面完全一致
$INSTALL_PATH/opentenbase_bin_v5.0/bin/postgres --version

# 两行输出应类似(时间戳一致):
# initdb (PostgreSQL) 10.0 @ OpenTenBase_v5.0 OpenTenBase V5.21 2026-09-23 21:46:29
# postgres (PostgreSQL) 10.0 @ OpenTenBase_v5.0 OpenTenBase V5.21 2026-09-23 21:46:29

如果两行的时间戳不同(例如一个 21:46:29、一个 21:34:xx),说明安装目录里混入了不同批次的产物,无需逐个文件排查,直接回到「一次性干净构建」,从全新解压的源码重新构建一遍即可。后续 initdb 报 postgres was not the same version as initdb 就是这个病因。

六、配置集群

6.1 环境变量:必须写在 .bashrc 最顶部

先解释为什么:Ubuntu 的 ~/.bashrc 开头自带一段「交互检查」,非交互场景直接 return 退出,写在它之后的变量对非交互场景无效:

# Ubuntu ~/.bashrc 开头自带:非交互 shell 直接 return
case $- in
    *i*) ;;
      *) return;;
esac

而 pgxc_ctl 部署时正是通过 ssh 127.0.0.1 initdb ... 这样的非交互方式执行命令。如果把环境变量写在交互检查之后,远程会话看不到 PATH,就会满屏报错:

initdb: command not found
pg_ctl: command not found

这是源码安装(非 RPM 包)特有的坑:官方镜像里 PATH 是预配好的。

因此要把下面四行加在 opentenbase 用户 ~/.bashrc(即 /data/opentenbase/.bashrc)的最顶部。vim 编辑步骤如下:

# 确认当前用户是 opentenbase(应输出 opentenbase)
whoami

# 若不是,先切换:su - opentenbase

# 用 vim 编辑家目录下的 .bashrc 配置文件
vim ~/.bashrc

注意(实测踩坑):执行 vim ~/.bashrc 时可能提示 Command 'vim' not found——Ubuntu 22.04 最小化安装并不预装 vim。解决:先执行 sudo apt install vim 安装(opentenbase 已在 2.3 节加入 sudo 组,输入 opentenbase 自己的密码),再重新执行 vim ~/.bashrc;或改用 nano 编辑(按键方式见下文)。

vim 打开后依次执行以下五步:①连按两次小写 g,光标跳至文件第一行;②按大写 I 进入插入模式,左下角显示 – INSERT –;③使用终端粘贴快捷键 Ctrl+Shift+V 粘贴下方四行;④按 Esc 退出插入模式;⑤输入 :wq 并回车,保存并退出。也可改用 nano ~/.bashrc:粘贴后按 Ctrl+O 回车保存,Ctrl+X 退出。需要粘贴的四行如下:

# OpenTenBase 安装根目录(后面第七、八章的命令都依赖它)
export PG_HOME=/data/opentenbase/install/opentenbase_bin_v5.0

# 把 OpenTenBase 的 bin 目录加入 PATH,
# 使 initdb、postgres、pg_ctl、pgxc_ctl 等命令可以直接调用
export PATH=$PG_HOME/bin:$PATH

# 把 lib 目录加入动态库搜索路径
# ${LD_LIBRARY_PATH:-} 表示原变量未设置时取空值,避免报错
export LD_LIBRARY_PATH=$PG_HOME/lib:${LD_LIBRARY_PATH:-}

# 统一语言环境,避免 locale 差异导致初始化或运行异常
export LC_ALL=C

保存后需让配置生效:修改 .bashrc 后,已打开的终端不会自动重新加载,须手动执行一次 source:

# 让配置在当前终端立即生效(每改一次 .bashrc 都要执行一次)
source ~/.bashrc

# 验证 1:应输出 /data/opentenbase/install/opentenbase_bin_v5.0
echo $PG_HOME

# 验证 2:应输出 /data/opentenbase/install/opentenbase_bin_v5.0/bin/pgxc_ctl
which pgxc_ctl

注意:以后每修改一次 .bashrc,都需执行 source ~/.bashrc 使其对当前终端生效;新开终端会自动加载,无需再次 source。第七、八章如遇 command not found,应首先确认是否执行过 source。

6.2 集群规划

表 6-1 单机四节点端口规划

节点 角色 主端口 数据目录
gtm 全局事务管理器 20001 /data/opentenbase/data/gtm
cn001 协调节点(对外提供 SQL) 30004 /data/opentenbase/data/cn001
dn001 数据节点 1 40004 /data/opentenbase/data/dn001
dn002 数据节点 2 40007 /data/opentenbase/data/dn002

每个节点还有一个转发端口和一个连接池端口(例如 cn001 另占 30005、30006),按 6.3 节配置文件中的写法保持递增即可。完整端口清单见第十一章表 11-1。

6.3 pgxc_ctl.conf 完整配置

创建配置目录,并在该目录下新建 pgxc_ctl.conf 文件(注意当前用户必须是 opentenbase,文件属主错了第七章会读不到配置):

# 创建 pgxc_ctl 专用配置目录并进入
mkdir -p /data/opentenbase/pgxc_ctl
cd /data/opentenbase/pgxc_ctl

# 用 vim 新建并编辑配置文件(文件不存在时 vim 会自动创建)
vim pgxc_ctl.conf

vim 按键流程:打开后按 I 进入插入模式 → Ctrl+Shift+V 粘贴下方全部内容 → 按 Esc → 输入 :wq 回车保存。若中途编辑出错,按 Esc 后输入 :q! 回车不保存退出,重新打开文件再编辑。粘贴的内容如下(这是实测可用的完整文件,从 #!/bin/bash 到最后一个 (none) 全部复制):

#!/bin/bash
pgxcInstallDir=/data/opentenbase/install/opentenbase_bin_v5.0
pgxcOwner=opentenbase
defaultDatabase=postgres
pgxcUser=$pgxcOwner
tmpDir=/tmp
localTmpDir=$tmpDir
configBackup=n
configBackupHost=pgxc-linker
configBackupDir=$HOME/pgxc
configBackupFile=pgxc_ctl.bak
myip=127.0.0.1
rundir=/data/opentenbase/data
clusterMaxMachine=1
clusterMaxParallelQuery=10
#---- GTM ----------
gtmName=gtm
gtmMasterServer=$myip
gtmMasterPort=20001
gtmMasterDir=$rundir/gtm
gtmExtraConfig=none
gtmMasterSpecificExtraConfig=none
gtmSlave=n
gtmSlaveServer=$myip
gtmSlavePort=20002
gtmSlaveDir=$rundir/gtm_slave
gtmSlaveSpecificExtraConfig=none
gtmProxyDir=$rundir/gtm_pxy
gtmProxy=n
gtmPxyExtraConfig=none
gtmPxySpecificExtraConfig=(none)
gtmProxyNames=(gtm_pxy1)
gtmProxyPorts=(20003)
gtmProxyServers=($myip)
gtmProxyDirs=($gtmProxyDir)
gtmHostType=(primary)
gtmHostIdx=(0)
#---- Coordinators -------
coordNames=(cn001)
coordPorts=(30004)
coordForwardPorts=(30005)
poolerPorts=(30006)
coordPgHbaEntries=(127.0.0.0/8)
coordMasterServers=($myip)
coordMasterDirs=($rundir/cn001)
coordMaxWALSenders=(4)
coordSlave=n
coordSlaveSync=n
coordSlaveServers=($myip)
coordSlavePorts=(30007)
coordSlavePoolerPorts=(30008)
coordSlaveDirs=($rundir/cn001_slave)
coordArchLogDirs=($rundir/coord_archlog)
coordExtraConfig=coordExtraConfig
cat > $coordExtraConfig <<EOF
listen_addresses = '*'
wal_level = hot_standby
max_connections = 100
shared_buffers = 256MB
max_pool_size = 200
log_destination = 'csvlog'
logging_collector = on
log_directory = 'pg_log'
log_truncate_on_rotation = on
log_filename = 'postgresql-%A-%H.log'
log_rotation_age = 4h
log_rotation_size = 0
EOF
coordSpecificExtraConfig=(none)
coordExtraPgHba=none
coordSpecificExtraPgHba=(none)
#---- Datanodes ---------------------
primaryDatanode=dn001
datanodeNames=(dn001 dn002)
datanodePorts=(40004 40007)
datanodeForwardPorts=(40005 40008)
datanodePoolerPorts=(40006 40009)
datanodePgHbaEntries=(127.0.0.0/8)
datanodeMasterServers=($myip $myip)
datanodeMasterDirs=($rundir/dn001 $rundir/dn002)
datanodeMaxWALSenders=(4 4)
datanodeSlave=n
datanodeSlaveServers=($myip $myip)
datanodeSlavePorts=(40010 40011)
datanodeSlavePoolerPorts=(40012 40013)
datanodeSlaveSync=n
datanodeSlaveDirs=($rundir/dn001_slave $rundir/dn002_slave)
datanodeArchLogDirs=($rundir/dn001_archlog $rundir/dn002_archlog)
datanodeExtraConfig=datanodeExtraConfig
cat > $datanodeExtraConfig <<EOF
listen_addresses = '*'
wal_level = hot_standby
max_connections = 100
shared_buffers = 512MB
max_pool_size = 200
log_destination = 'csvlog'
logging_collector = on
log_directory = 'pg_log'
log_truncate_on_rotation = on
log_filename = 'postgresql-%A-%H.log'
log_rotation_age = 4h
log_rotation_size = 0
EOF
datanodeSpecificExtraConfig=(none)
datanodeExtraPgHba=none
datanodeSpecificExtraPgHba=(none)
# 保存后检查:应能打印出文件的前 3 行(#!/bin/bash 等)
ls -l pgxc_ctl.conf
head -3 pgxc_ctl.conf

有两个细节容易踩坑:第一,所有数组型变量的元素个数必须与节点数一一对应,例如两个 DN 就必须写 datanodeMaxWALSenders=(4 4),只写 (4) 会在启动时报 Number of elements in datanode master definitions are different;第二,coordPgHbaEntries=(127.0.0.0/8) 控制客户端白名单,单机环境保持环回地址段即可,若需要从其它机器连接,把对应网段加进去。

七、部署与启动集群

配置完成后开始部署。确保当前为 opentenbase 用户:

# 切换到 opentenbase 用户
su - opentenbase

# 进入 pgxc_ctl 工作目录
# pgxc_ctl 启动时会自动读取当前目录下的 pgxc_ctl.conf,所以必须先 cd 过来
cd /data/opentenbase/pgxc_ctl

# 启动 pgxc_ctl 交互式命令行
pgxc_ctl

提示符变为 pgxc_ctl> 即表示已进入管理界面。依次执行以下三条子命令,过程中若弹出类似 Continue? 的确认提示,输入 y 回车:

# 1) 将二进制分发到各节点(本机即本机),并生成 pgxc_ctl_bash
#    会把 $PG_HOME 打包,按配置分发到各节点
> deploy all

# 2) 初始化 GTM / CN / DN 四个实例并拉起
#    GTM:全局事务管理器;CN:协调节点,对外提供 SQL;DN:数据节点
> init all

# 3) 查看集群状态
> monitor all

monitor all 全部正常时应输出四行 Running:

Running: gtm master
Running: coordinator master cn001
Running: datanode master dn001
Running: datanode master dn002

日常启停:重启虚拟机后集群不会自动拉起,登录 opentenbase 用户执行 pgxc_ctl 后输入 start all 即可(不需要再 deploy/init);关闭前先 stop all(完整步骤见第九章)。另外,刚才的 deploy all 过程中 pgxc_ctl 会在 /data/opentenbase/install 下打包二进制 tar 包,若磁盘紧张可以删掉生成的临时包。

退出方式:退出 pgxc_ctl 管理界面:在 pgxc_ctl> 后输入 exit 回车(或按 Ctrl+D),即回到普通命令行。

八、数据库初始化与验收

8.1 连接协调节点

# 使用 psql 连接协调节点(CN)
# -h 127.0.0.1:连接本机
# -p 30004:协调节点端口,与 pgxc_ctl.conf 中的 coordPorts 一致
# -d postgres:连接默认的 postgres 数据库
# -U opentenbase:使用 opentenbase 用户登录
psql -h 127.0.0.1 -p 30004 -d postgres -U opentenbase

连接成功后提示符变为 postgres=#,表示已进入 psql 交互界面。此界面中的 SQL 语句须以英文分号结尾才会执行;psql 自身的元命令以反斜杠开头(如 \c、\l);退出 psql 使用 \q 命令。

8.2 初始化节点组与分片组(源码安装必做)

这是源码安装与官方镜像的又一关键差异:pgxc_ctl 的 init all 不会创建分片映射,直接 CREATE TABLE 会报错 ERROR: default group not defined (42704),补上节点组后再建表会报 please initialize group ... sharding map first。在 psql 里执行以下两条 SQL 即可一劳永逸:

-- 创建默认节点组 default_group,包含两个数据节点 dn001、dn002
-- default_group 是节点组名称;WITH (dn001, dn002) 指定组内的 DN 列表
CREATE DEFAULT NODE GROUP default_group WITH (dn001, dn002);

-- 将默认节点组设置为分片组,初始化全局分片映射
-- 为后续建表的分片规则建立基础映射关系
CREATE SHARDING GROUP TO GROUP default_group;

第一条把两个 DN 组成默认节点组,第二条初始化全局分片映射。如果后续想建显式指定分片的表,语法为 DISTRIBUTE BY SHARD(id) TO GROUP default_group:

-- 建表时通过 DISTRIBUTE BY SHARD(id) 指定按 id 分片
-- TO GROUP default_group 指定分片到 default_group 节点组
CREATE TABLE ... DISTRIBUTE BY SHARD(id) TO GROUP default_group;

8.3 建库建表验收

-- 创建验收用数据库
CREATE DATABASE simple_db;

-- 切换到 simple_db 数据库(提示符会变成 simple_db=#)
\c simple_db

-- 创建测试表 test01,含两个字段:id 和 name
CREATE TABLE test01 (id INT, name TEXT);

-- 插入两行测试数据
INSERT INTO test01 VALUES (1, 'hello'), (2, 'world');

-- 查询 test01 全部数据
-- 能查到 2 行,说明协调节点 → 数据节点 → 存储 全链路打通
SELECT * FROM test01;

8.4 验收结果

三项验收结果如下,均为实测真实运行截图。

验收项一:monitor all 四个节点全部 Running

验收项一:monitor all 四个节点全部 Running

验收项二:psql 连接成功,SELECT version() 显示 OpenTenBase V5.21

验收项二:psql 连接成功,SELECT version() 显示 OpenTenBase V5.21

验收项三:simple_db.test01 建表插入查询到 2 行

验收项三:simple_db.test01 建表插入查询到 2 行

九、日常使用:重启后启动集群与基础 SQL 操作

安装与验收全部完成后,集群进入日常使用阶段。本章给出两个最常用的流程:一是重启虚拟机后把集群重新拉起来,二是在 psql 里完成建库、建表、查询、修改、删除等基础操作。所有命令均假设第八章的验收已经通过。

9.1 重启虚拟机后的集群启动

虚拟机重启后,GTM、CN、DN 四个进程不会自动拉起(本教程未配置开机自启,如有需要可自行编写 systemd 服务单元,此处不展开),需要手动启动。此前做过的配置都保存在磁盘上,重启后依然有效,无需重做:2.4 节的 SSH 免密、6.1 节写在 .bashrc 最顶部的环境变量如此,第八章创建的数据库、表以及 8.2 节的节点组与分片映射也是如此。同样不需要再执行第七章的 deploy all 与 init all,用 start all 拉起即可。

# 打开终端(Ctrl+Alt+T),切换到 opentenbase 用户(输入它的密码)
su - opentenbase

# 进入 pgxc_ctl 工作目录
# pgxc_ctl 启动时自动读取当前目录下的 pgxc_ctl.conf,必须先 cd 过来
cd /data/opentenbase/pgxc_ctl

# 启动 pgxc_ctl 交互式命令行,提示符变为 pgxc_ctl> 即成功
pgxc_ctl
# 查看集群当前状态:刚开机时应显示四个节点均为 Not running
> monitor all

# 一键拉起全部节点(GTM / CN / DN),无需再 deploy / init
> start all

# 再次确认状态:应输出四行 Running(gtm / cn001 / dn001 / dn002)
> monitor all

# 退出 pgxc_ctl 管理界面,回到普通命令行
> exit

验证标准:monitor all 输出四行 Running 即启动成功,随后即可按 9.3 节连接数据库。若 start all 中途报错或状态长期为 Not running,先按第十章排错表排查——最常见的原因是上次关机前没有 stop all 就直接关闭了虚拟机。

9.2 关闭虚拟机前:干净地停止集群

日常关机前建议先停止集群,让四个节点把内存中的数据正常落盘,避免留下脏数据;停止后再次开机就按 9.1 节的流程启动。若只是暂时不用数据库而不打算关机,同样先 stop all,需要时再 start all。

# 切换到 opentenbase 用户
su - opentenbase

# 进入 pgxc_ctl 工作目录
cd /data/opentenbase/pgxc_ctl

# 启动 pgxc_ctl 管理界面
pgxc_ctl
# 停止全部节点(GTM / CN / DN 依次关闭)
> stop all

# 确认已全部停止:应显示四个 Not running
> monitor all

# 退出管理界面,之后即可正常关闭虚拟机
> exit

说明:先 stop all 再关机可避开绝大多数启动异常。若曾强制关机导致 start all 失败,按第十章排错表处理;注意其中「清空 /data/opentenbase/data/* 重新 init」的做法会删除全部数据,仅适用于库里没有重要数据的场合。

9.3 基础 SQL 操作:建库、建表、增删改查

以下操作在集群启动后进行,先按 8.1 节连接协调节点,再依次执行。SQL 语句以英文分号 ; 结尾才会执行;以 -- 开头的是注释,不用输入。示例使用新的练习数据库 demo_db,与 8.3 节的验收数据互不影响;建表依赖 8.2 节创建的 default_group,若当时跳过了那两条 SQL,先回 8.2 节补做。

# 连接协调节点(CN),成功后提示符变为 postgres=#
# -h/-p:本机 30004 端口;-d:默认数据库;-U:登录用户
psql -h 127.0.0.1 -p 30004 -d postgres -U opentenbase
-- 创建练习用数据库
CREATE DATABASE demo_db;

-- 切换到 demo_db(提示符变为 demo_db=#)
\c demo_db

-- 建表:三个字段;DISTRIBUTE BY SHARD(id) 表示按 id 字段分片
-- TO GROUP default_group:分片落到 8.2 节创建的默认节点组(dn001、dn002)
CREATE TABLE student (id INT, name TEXT, score INT) DISTRIBUTE BY SHARD(id) TO GROUP default_group;

-- 插入三行数据
INSERT INTO student VALUES (1, '张三', 90), (2, '李四', 85), (3, '王五', 78);

-- 查询全部数据(应返回 3 行)
SELECT * FROM student;

-- 条件查询 + 排序:80 分及以上,按分数从高到低
SELECT name, score FROM student WHERE score >= 80 ORDER BY score DESC;

-- 修改:把 id 为 2 的分数改为 95
UPDATE student SET score = 95 WHERE id = 2;

-- 删除行:删掉 id 为 3 的记录
DELETE FROM student WHERE id = 3;

-- 查看表结构(字段名与类型)
\d student

-- 删除表
DROP TABLE student;

-- 删除数据库:不能删除当前正在连接的库,先切回 postgres
\c postgres

-- 删除 demo_db
DROP DATABASE demo_db;

-- 退出 psql,回到命令行
\q

说明:以上是最基础的入门操作。OpenTenBase 兼容大部分 PostgreSQL 语法,普通建表、查询的写法与 PostgreSQL 一致;分布式相关特性(分片键的选择、复制表等)建议先查阅官方文档(见 11.3 节)再用于实际数据。

十、常见问题与排错

表 10-1 全流程排错表

问题 原因 解决
git clone 报 GnuTLS recv error / Connection refused GitHub 连接不稳定 改用第三章的 codeload 直链或 gitcode 镜像
configure: error: zstd library not found configure 硬编码 BINARYLIBS 路径 第四章问题一 sed 补丁 + 安装 libzstd-dev liblz4-dev
conflicting types for ‘getmissingattr’ char bool 与 OpenSSL 3.0 的 _Bool 冲突 第四章问题二给 c.h 加 #include <stdbool.h>
postgres was not the same version as initdb 多次 configure 混装了不同时间戳的产物 按「一次性干净构建」从全新解压一次性重建
initdb/pg_ctl: command not found(init all 时) 非交互 SSH 看不到 PATH 环境变量写到 .bashrc 最顶部(6.1 节)并确认 source 过
Number of elements in datanode master definitions are different 数组元素个数与节点数不符 datanodeMaxWALSenders 等改成每节点一个元素(6.3 节)
ERROR: default group not defined pgxc_ctl 不会自动创建分片映射 执行 8.2 节的两条 CREATE … GROUP SQL
monitor all 显示 Not running 上次初始化失败留下脏数据 停掉进程后清空 /data/opentenbase/data/*,重新 deploy all + init all
su - opentenbase 报「认证失败」 密码输错(输入时不回显) 重新输入;忘记密码用 sudo passwd opentenbase 重设
cat ~/.ssh/id_rsa.pub >> authorized_keys 报 No such file or directory,ssh 仍要密码 跳过了 ssh-keygen,~/.ssh 目录与密钥对都不存在 补执行 ssh-keygen 生成密钥对,再重做 cat 追加与 chmod 600(2.4 节)
刚改完 .bashrc,命令仍 command not found 忘了 source,或变量写在交互检查之后 source ~/.bashrc;确认四行 export 在文件最顶部(6.1 节)
vim 里按错键、画面混乱 插入/普通模式混淆 先按 Esc,再输入 :q! 回车不保存退出,重新打开重来
Command ‘vim’ not found(编辑 .bashrc / pgxc_ctl.conf 时) Ubuntu 22.04 未预装 vim sudo apt install vim;或改用 nano(Ctrl+O 回车保存,Ctrl+X 退出)
在 psql 中无法退出 psql 有独立的元命令体系 输入 \q 回车退出;pgxc_ctl 中则输入 exit
pgxc_ctl 找不到配置 / 配置不生效 未在 pgxc_ctl.conf 所在目录启动 先 cd /data/opentenbase/pgxc_ctl 再运行 pgxc_ctl(第七章)
Could not get lock /var/lib/dpkg/lock-frontend unattended-upgrades 后台升级占用 apt 等待其自然结束(ps -p <PID> 确认),不要强删锁

一个小技巧:如果需要在脚本里得到带 postgres=# 提示符的交互式输出(比如截图存档),用 printf '...' | ssh -tt opentenbase@127.0.0.1 psql ... 强制分配终端;直接管道进 psql 的非交互输出是没有提示符的。

十一、附录

11.1 完整端口清单

表 11-1 端口占用清单

端口 用途
20001 GTM 主节点
20003 GTM Proxy(本方案未启用)
30004 CN cn001 对外提供 SQL(psql 连这里)
30005 CN 转发端口
30006 CN 连接池端口
40004 / 40005 / 40006 DN dn001 主 / 转发 / 连接池
40007 / 40008 / 40009 DN dn002 主 / 转发 / 连接池

11.2 目录结构

全部安装完成后,/data/opentenbase 的目录结构如下:

/data/opentenbase/
├── OpenTenBase/               # 源码(已打两处补丁)
├── install/
│   └── opentenbase_bin_v5.0/  # 编译安装产物(bin lib include share)
├── pgxc_ctl/
│   └── pgxc_ctl.conf          # 集群配置(第六章)
└── data/
    ├── gtm/                   # GTM 实例
    ├── cn001/                 # 协调节点
    ├── dn001/                 # 数据节点 1
    └── dn002/                 # 数据节点 2

11.3 参考链接

11.4 结语

至此,Ubuntu 22.04 环境下的源码编译与单机分布式集群部署全部完成。安装过程中的两项关键经验——修改前备份、修改后立即验证——同样适用于后续的版本升级与问题排查。遇到本教程未覆盖的报错时,建议优先完整阅读报错原文,多数问题的线索位于前两行。