ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

openGauss源码解析开篇:从架构到代码地图

2026/9/19 0:46:20 拓冰建站 浏览量
openGauss源码解析开篇:从架构到代码地图 在数据库这个圈子里提到国产数据库openGauss是一个绕不开的名字。我最初接触它的时候第一反应是这不就是PostgreSQL换了个壳吗但随着逐步深入源码才发现远远不是这么回事。openGauss在PG的基础上做了大量企业级增强从线程模型到存储引擎从内存管理到复制机制都有很多值得深挖的设计。写这个源码解析系列目的很简单把openGauss从代码层面拆开揉碎讲清楚它到底是怎么工作的。这篇开篇我先不急着贴代码而是先把openGauss的全貌、架构、源码目录结构讲透。因为读源码最怕的就是一头扎进某个函数里出不来缺一张全局地图。如果你是想基于openGauss做数据库课程设计的学生、需要评估选型的DBA或者是想研究数据库实现原理的开发者这篇文章都能帮你快速建立一个整体认知框架。有了这个基础后面逐模块拆解源码时才不会迷路。1. openGauss是什么先把这个项目放进坐标系里1.1 从PostgreSQL到openGauss的演进脉络openGauss是2020年6月正式开源的关系型数据库管理系统采用木兰宽松许可证v2发行。它的代码源头可以追溯到PostgreSQL 9.2版本但如果你拿着PG 9.2的源码去对比今天的openGauss会发现两者差异大到几乎可以当作两个独立的数据库产品来看待。为什么这么说拿几个关键点来说明。openGauss在PG的基础上引入了线程池架构把原来的进程模型改为线程模型新增了两个存储引擎一个行存引擎适合OLTP场景一个列存引擎后来演化为列存和内存引擎面向OLAP场景重构了内存管理机制引入了全局内存上下文和共享缓冲区管理在复制层面实现了基于quorum的同步复制还自研了故障恢复和备机build机制。这些改动不是简单的修修补补而是动了数据库核心架构的大手术。所以我一直认为与其说openGauss是PostgreSQL的分支不如说它是基于PG的设计思想、结合企业级场景需求重新打造的一款数据库。这也是为什么我会专门写一个源码解析系列因为它的代码里藏着的设计决策远比表面功能更能说明问题。1.2 为什么企业级市场需要它openGauss定位很清晰面向企业级核心业务场景的关系型数据库。这个企业级三个字意味着什么意味着高可用、高性能、高安全、易运维这些词每个背后都对应着具体的代码实现。拿高可用来讲openGauss实现了双机、一主多备、级联备等多种部署形态支持同步、异步、潜在等多种复制模式还提供了第三方复制工具来对接异构数据库。这些能力在源代码里对应着replication目录下的一大套逻辑包括日志发送、日志接收、日志回放、冲突检测等模块。拿高性能来讲除了线程池openGauss还做了SQL引擎的优化比如向量化执行引擎一次处理一批数据而不是一行在分析型查询上性能提升非常明显。代码里对应的是src/include/vecexecutor和src/gausskernel/runtime/vecexecutor这两个目录核心数据结构是VectorBatch。对DBA和运维人员来说openGauss还提供了全量的运行监控能力通过dbe_perf视图可以查看丰富的性能数据对安全要求高的场景它支持全密态等值查询数据在数据库端以密文存储和计算。这些特性在源码里都是有明确模块对应的后面系列文章会一个一个拆解。2. 整体架构先看懂骨架再谈源码细节2.1 逻辑架构与线程模型的对应关系读openGauss源码之前必须先理解它的整体逻辑架构。openGauss的架构可以简单概括为一个内核多个周边工具。内核部分自上而下分为三层。最上层是接口层负责客户端通信、SQL解析和查询优化中间是执行层负责查询执行、表达式计算、存储访问底层是存储层负责数据持久化、日志管理、事务处理。听起来和大多数数据库差不多但openGauss在执行层和存储层之间还插了一个acceleration加速层用来承载智能化和向量化加速能力。线程模型值得单独说一说这是openGauss和PG最大的架构差异之一。PG用的是进程模型一个连接对应一个进程进程之间通过共享内存通信。openGauss在主线程模型上改成了线程池模型一组工作线程共享监听线程创建的连接。从源码上看线程池的核心实现在src/gausskernel/process/threadpool目录下。关键数据结构是ThreadPool它管理着ThreadPoolGroup、ThreadPoolWorker、ThreadPoolListener等组件。每个worker线程可以处理多个连接上的任务任务通过ThreadPoolSession和ThreadPoolTask来抽象。这套机制让openGauss在数千连接的情况下线程数量依然可控不会像进程模型那样吃光系统资源。源码解析有一个好处就是可以通过代码验证文档里的描述。比如你去看线程池相关的头文件能看到MAX_BACKEND这些宏的定义也能看到g_threadPool全局对象是怎么初始化的这些细节在官方文档里往往不会展开。2.2 存储引擎与核心组件拆解openGauss支持多种存储引擎这一块在源码里对应多个不同的目录和接口。默认的行存引擎在src/gausskernel/storage/access下实现了堆表、索引、undo、redo等核心存储逻辑。列存引擎在src/gausskernel/storage/column目录下核心是基于列式存储的CUCompress Unit管理。内存引擎MOT在src/gausskernel/storage/mot目录下是一个完全基于内存的存储引擎数据不落盘通过WAL日志保证持久性。这些存储引擎通过统一的行列混合存储框架对外提供服务。什么叫混合存储就是一张表可以选择行存也可以选择列存甚至一张表内既有行存部分又有列存部分比如把历史数据放在列存分区热数据放在行存分区。这种设计在代码层面通过StoreType枚举和RelationData结构体中的存储类型字段来区分。除了存储引擎openGauss还有几个核心组件需要了解。SQL引擎负责把用户的SQL语句变成可执行的计划相关代码在src/gausskernel/optimizer和src/gausskernel/executor目录事务管理模块保证ACID特性相关代码在src/gausskernel/storage/access/transam目录安全模块负责认证、权限控制和加密在src/gausskernel/security目录。把这些组件串起来看openGauss就是一个标准的SQL引擎 存储引擎 事务处理三段式数据库架构。但每一段都做了很多企业级增强比如SQL引擎里有基于代价的优化器CBO存储层里有基于日志的恢复机制事务层里支持两阶段提交。理解了这个整体框架后面读源码就有了抓手。3. 源码目录源码解析的地图与入口3.1 顶层目录结构一览拿到openGauss源码包并解压之后第一件事不是找main函数而是先把顶层目录过一遍。openGauss的源码目录结构继承了PG的习惯用src作为所有源码的根目录下面分common、gausskernel、bin、include等几个子目录。这里我列出最核心的几个目录给初次接触源码的人一个导航src/common存放公共代码包括port平台适配、stringinfo字符串处理、fe_memutils内存分配等基础模块这些模块在内核和其他工具中都会被复用属于地基中的地基。src/gausskernel数据库内核的主战场几乎所有核心模块都在这里面。它下面又分了process进程/线程管理、storage存储、optimizer优化器、executor执行器、parser解析器、rewrite查询重写、utils工具函数、security安全、catalog系统表等子目录。src/bin存放客户端工具源码比如gsql交互式SQL工具、gs_ctl数据库启停工具、gs_dump逻辑备份工具、gs_basebackup物理备份工具等这些工具就是DBA日常运维时用到的命令。看代码的时候要有一个心理预期openGauss的内核代码量非常大仅src/gausskernel目录下的C/C源文件数量就超过两千个。所以千万不要想着一口气全部看完而是按照模块、按照功能点去定点爆破。3.2 核心模块的代码入口定位我把自己读源码时常用的几个入口文件整理出来方便大家按图索骥。所谓入口文件就是你想要了解某个模块时第一个应该打开的文件通常它定义了该模块最核心的数据结构或对外接口。线程池的入口src/gausskernel/process/threadpool/threadpool.cpp里面的ThreadPool::Initialize和ThreadPool::WorkerMain是线程池初始化和worker线程主循环的入口。内存上下文入口src/common/backend/utils/mmgr/mcxt.cpp里面定义了MemoryContext接口和MemoryContextCreate、MemoryContextAlloc等核心函数内存泄漏排查全靠对这块的理解。事务入口src/gausskernel/storage/access/transam/xact.cppStartTransaction、CommitTransaction、AbortTransaction三个函数是事务生命周期的骨架。查询执行入口src/gausskernel/executor/execMain.cppExecutorStart、ExecutorRun、ExecutorFinish、ExecutorEnd四个函数串起了执行器的主流程。WAL日志入口src/gausskernel/storage/access/transam/xlog.cppXLogInsert负责写日志RecoveryRestartPoint负责恢复时的重启点处理。找到入口之后怎么往下读我的经验是三步走。第一步读头文件把模块里的结构体定义、宏定义、全局变量声明都过一遍建立这个模块有哪些东西的认知第二步读入口函数理解函数内部第一步做了什么、调用了哪些子函数画出调用关系第三步跟调用链选一条最核心的调用链从头跟到尾比如一条SQL从客户端发出来到返回结果这条链路能走通之后对数据库的理解会上一个台阶。4. 解析路线规划这个系列打算怎么带大家读源码4.1 推荐的源码阅读顺序数据库源码阅读是一个系统工程没有合理的顺序很容易在代码海洋里迷失。我本人在读openGauss源码时走过不少弯路一开始直接去看执行器结果被各种状态机的跳转绕晕后来调整了顺序效率高了很多。给读者一个建议路线也是我写这个系列会遵循的顺序。第一阶段先读基础设施包括内存管理、线程池、日志系统这三块是理解后续所有模块的前提。第二阶段读SQL处理链路从解析器到优化器再到执行器形成一条SQL的一生的完整认知。第三阶段读存储引擎包括表结构管理、索引实现、缓冲区管理。第四阶段读事务和恢复理解WAL机制、MVCC实现和故障恢复流程。第五阶段再看高级特性比如列存、内存引擎、全密态、资源池化等。这个顺序背后的逻辑是先通后专基础设施和SQL链路是每个模块都要依赖的公共部分先掌握它们相当于给大脑装了一套操作系统存储和事务是数据库区别于其他系统的核心需要深入理解高级特性则是企业级场景的增量价值最后看有助于形成基础能力 差异化特性的完整认知。4.2 前期准备编译调试环境搭建读源码如果只是静态阅读很多细节是理解不到位的。跑起来、打断点、看变量值、改代码验证才能真正把知识内化。这一节说下我自己搭建openGauss编译调试环境的经验。openGauss支持在openEuler、CentOS、Ubuntu等系统上编译。我推荐用openEuler 20.03 LTS或者22.03 LTS因为官方在这两个系统上测试最充分遇到问题的概率最低。如果只是本机学习配一台4核8G内存的虚拟机就够了代码编译比较吃内存建议给虚拟机分配不低于4G内存。编译过程也不复杂大致步骤如下。第一步从openGauss官网或者Gitee获取源码建议用git clone而不是直接下载zip包方便后续拉取更新。第二步安装编译依赖包括gcc、g、make、cmake、flex、bison、ncurses-devel、readline-devel等在openEuler上可以直接用yum install安装。第三步创建编译用户openGauss不允许用root用户编译和安装需要先useradd omm并切换到该用户。第四步执行chmod x build.sh ./build.sh -3rd一键编译这个过程会花一到两个小时取决于机器性能。编译完成后还可以再配置一下调试环境。我的做法是用gdb直接附加到数据库进程或者用gsql在另一个终端执行查询在gdb中设置断点比如break ExecutorRun这样就能看到执行器的实时调用过程。读源码的过程中每理解一个模块就实际跑一下相关场景比如理解了MVCC之后去执行两个并发事务观察可见性行为这种代码 实验的验证方式效果是最好的。5. 常见问题与踩坑记录5.1 初次接触openGauss源码的典型困惑这一节把我在学习openGauss源码过程中遇到的高频问题整理出来给读者做一份避坑清单。这些问题在官方文档里不容易找到答案但实际动手时几乎每个人都会碰到。第一个问题是为什么openGauss里很多PG的函数找不到了。因为openGauss在内核对很多PG函数做了重命名或者移动位置比如PG里的heap_insert在openGauss里变成了heap_insert加了一层封装函数签名也有所不同。遇到这种情况别慌用grep在源码目录里搜函数名通常能找到它的定义位置或者被谁调用定位之后再顺着调用链看。第二个问题是头文件里的结构体定义和实际使用的地方对不上。原因在于openGauss大量使用了条件编译同一个结构体在不同编译选项下字段不同。比如RelationData结构体在USE_OPENGLOBAL等宏开启时会多出几个字段。读代码时要留意#ifdef和#ifndef区分哪些代码是当前编译配置下生效的。第三个问题是日志看不懂。openGauss运行时会输出大量日志格式是[timestamp] [level] [module] [pid] [threadId] [message]比如2025-01-01 12:00:00.123 CST 14001 [BACKEND] LOG: ...。调试源码时最有效的做法是在源码里临时加elog(LOG, xxx pid%d, pid)这样的打印语句重新编译后运行通过自己加的日志来确认代码走到了哪个分支这比看系统日志直观得多。5.2 学习资料与工具链推荐最后推荐一些我实际用下来觉得顺手的工具和资料。源码在线阅读推荐使用Gitee上的openGauss官方仓库浏览代码方便也可以直接下载到本地用IDE打开。IDE方面自己用VS Code或者CLion都可以CLion对CMake工程的支持更好但内存占用更高如果虚拟机配置一般就用VS Code加C/C插件再看一个cquery或clangd插件补全代码跳转。阅读代码的辅助工具方面推荐用ctags或者cscope生成代码索引这样在vim或者编辑器里可以直接跳转到函数定义。还有一个技巧是使用gdb配合cgdb这种文本界面调试体验比纯命令行好很多。对于源码中那些比较复杂的数据结构之间的关系不建议画大而全的UML图而是针对单个模块画调用关系图图上只保留关键函数和数据流转方向简单实用。学习资料方面openGauss官方文档包括《openGauss概述》《开发者指南》《运维指南》是必须通读的基础材料源代码里的doc目录也藏着不少宝藏有些模块的设计文档写得很详细社区上的技术博客和问答可以解决大部分具体问题。最关键的一点是练习源码阅读的能力要读写结合读一段源码就在自己的笔记里用自己的话总结这段代码做了什么、为什么这么做、有哪些局限性这样才能把源码变成自己的东西。我个人在实际操作中的体会是读数据库源码最忌着急。openGauss这套代码量级的东西每周能精读透一个模块就已经很快了。先把基础模块啃下来再往上层走后面会越来越顺。如果读代码时觉得卡住了不妨退回一步重新看看这个模块在整体架构中的位置很多当时看不懂的设计放回到架构里就自然说得通了。这个系列后续会一篇一篇地把核心模块带大家过一遍先从线程池和内存管理开始。