openGauss数据库源码解析(一)| openGauss简介(2)
1.3 系统架构
openGauss主要包含了openGauss服务器、客户端驱动、OM(operations manager,运维管理模块)等模块,它的架构如图1-1所示,模块说明如表1-1所示。
图1-1 openGauss软件架构
表1-1 openGauss模块说明
名称 描述
OM 运维管理模块,提供openGauss日常运维、配置管理的管理接口、工具
客户端驱动 客户端驱动(client driver),负责接收来自应用的访问请求,并向应用返回执行结果;负责与openGauss实例的通信,下发SQL在openGauss实例上执行,并接收命令执行结果
openGauss主(备)设备 openGauss主(备)设备,负责存储业务数据(支持行存储、列存储、内存表存储)、执行数据查询任务以及向客户端驱动返回执行结果
storage 服务器的本地存储资源,持久化存储数据
1.4 代码结构
本节从数据库系统通信管理、SQL引擎和存储引擎3个方面对openGauss的代码结构进行介绍。
1.4.1 通信管理
openGauss查询响应使用简单的“单一用户对应一个服务器线程”的客户端/服务器模型实现。由于无法提前知道需要建立多少个连接,因此必须使用主进程(gaussmaster)。主进程在指定的TCP/IP(transmission control protocol/internet protocol,传输控制协议/互联网协议)端口上侦听传入的连接,只要检测到连接请求,主进程就会生成一个新的服务器线程。服务器线程之间使用信号量和共享内存相互通信,以确保整个并发数据访问期间的数据完整性。
客户端进程可以被理解为满足openGauss协议的任何程序。许多客户端都基于C语言库libpq进行通信,但是该协议有几种独立的实现,例如Java JDBC驱动程序。
建立连接后,客户端进程可以将查询发送到后端服务器。查询使用纯文本传输,即在前端(客户端)中没有进行解析。服务器解析查询语句、创建执行计划、执行并通过在已建立连接上传输检索到的结果集,将其返回给客户端。
openGauss数据库中处理客户端连接请求的模块叫作作postmaster。前端程序发送启动信息给postmaster,postmaster根据信息内容建立后端响应线程。postmaster也管理系统级的操作,比如调用启动和关闭程序。postmaster在启动时创建共享内存和信号量池,但它自身不管理内存、信号量和锁操作。
当客户端发来一个请求信息,postmaster立刻启动一个新会话,新会话对请求进行验证,验证成功后为它匹配后端工作线程。这种模式架构上处理简单,但是高并发下由于线程过多,切换和轻量级锁区域的冲突过大导致性能急剧下降。因此openGauss通过线程资源池化复用的技术来解决该问题。线程池技术的整体设计思想是线程资源池化,并且在不同连接直接复用。
-
postmaster源码组织
postmaster源码目录为:/src/gausskernel/process/postmaster。postmaster源码文件如表1-2所示。
表1-2 postmaster源码文件
模块 源码文件 功能
postmaster postmaster.cpp 用户响应主程序
aiocompleter.cpp 完成预取(prefetch)和后端写(backWrite)I/O操作
alarmchecker.cpp 闹钟检查线程
lwlockmonitor.cpp 轻量锁的死锁检测
pagewriter.cpp 写页面
pgarch.cpp 日志存档
pgaudit.cpp 审计线程
pgstat.cpp 统计信息收集
startup.cpp 服务初始化和恢复
syslogger.cpp 捕捉并写所有错误日志
autovacuum.cpp 垃圾清理线程
bgworker.cpp 后台工作线程(服务共享内存)
bgwriter.cpp 后台写线程(写共享缓存)
cbmwriter.cpp 修改数据块跟踪记录线程
remoteservice.cpp 远程服务线程,用于双机损坏页修复时的远程服务
checkpointer.cpp 检查点处理
fencedudf.cpp 保护模式下运行用户定义函数
gaussdb_version.cpp 版本特性控制
twophasecleaner.cpp 清理两阶段事务线程
walwriter.cpp 预写式日志写入 -
postmaster主流程
postmaster主流程代码如下:
/* postmaster.cpp /
…
int PostmasterMain(int argc, char argv[])
{
InitializePostmasterGUC(); /* 初始化postmaster模块配置参数*/
…
pgaudit_agent_init(); /* 初始化审计模块*/
…
for (i = 0; i < MAXLISTEN; i++) /* 建立输入socket监听*/
t_thrd.postmaster_cxt.ListenSocket[i] = PGINVALID_SOCKET;
…
/* 建立共享内存和信号池*/
reset_shared(g_instance.attr.attr_network.PostPortNumber);
…
/* 初始化postmaster信号管理*/
gs_signal_slots_init(GLOBAL_ALL_PROCS + EXTERN_SLOTS_NUM);
…
InitPostmasterDeathWatchHandle(); /* 初始化宕机监听*/
…
pgstat_init(); /* 初始化统计数据收集子系统*/
InitializeWorkloadManager(); /* 初始化工作负载管理器*/
…
InitUniqueSQL(); /* 初始化unique SQL资源*/
…
autovac_init(); /* 初始化垃圾清理线程子系统*/
…
status = ServerLoop(); /* 启动postmaster主业务循环*/
…
}
1.4.2 SQL引擎
数据库的SQL引擎是数据库重要的子系统之一,它对上负责承接应用程序发送过来的SQL语句,对下则负责指挥执行器运行执行计划。其中优化器作为SQL引擎中最重要、最复杂的模块,被称为数据库的“大脑”,优化器产生的执行计划的优劣直接决定数据库的性能。
本节从SQL语句发送到数据库服务器开始,对SQL引擎的各个模块进行全面的介绍与源码解析,以实现对SQL语句执行的逻辑与源码更深入的理解。其响应流程如图1-2所示。
图1-2 openGauss数据库SQL查询响应流程
- 查询解析——parser
SQL解析对输入的SQL语句进行词法分析、语法分析、语义分析,获得查询解析树或者逻辑计划。SQL查询语句解析的解析器(parser)阶段包括如下。
(1) 词法分析:从查询语句中识别出系统支持的关键字、标识符、操作符、终结符等,每个词确定自己固有的词性。
(2) 语法分析:根据SQL语言的标准定义语法规则,使用词法分析中产生的词去匹配语法规则,如果一个SQL语句能够匹配一个语法规则,则生成对应的语法树(abstract synatax tree,AST)。
(3) 语义分析:对语法树进行检查与分析,检查语法树中对应的表、列、函数、表达式是否有对应的元数据(指数据库中定义有关数据特征的数据,用来检索数据库信息)描述,基于分析结果对语法树进行扩充,输出查询树(Query)。主要检查的内容包括:
检查关系的使用:FROM子句中出现的关系必须是该查询对应模式中的关系或视图。
检查与解析属性的使用:在SELECT语句中或者WHERE子句中出现的各个属性必须是FROM子句中某个关系或视图的属性。
检查数据类型:所有属性的数据类型必须是匹配的。
词法和语法分析代码基于gram.y和scan.l中定义的规则,使用UNIX工具bison和flex构建产生。其中,词法分析器在文件scan.l中定义,它负责识别标识符、SQL关键字等。对于找到的每个关键字或标识符,都会生成一个标记并将其传递给解析器。语法解析器在文件gram.y中定义,由一组语法规则和每当触发规则时执行的动作组成,基于这些动作代码架构并输出语法树。在解析过程中,如果语法正确,则进入语义分析阶段并建立查询树返回,否则将返回错误,终止解析过程。
解析器在词法和语法分析阶段仅使用有关SQL语法结构的固定规则来创建语法树。它不会在系统目录中进行任何查找,因此无法理解所请求操作的详细语义。
语法解析完成后,语义分析过程将解析器返回的语法树作为输入,并进行语义分析以了解查询所引用的表、函数和运算符。用来表示此信息的数据结构称为查询树。解析器解析过程分为原始解析与语义分析,分开的原因是,系统目录查找只能在事务内完成,并且不希望在收到查询字符串后立即启动事务。原始解析阶段足以识别事务控制命令(BEGIN,ROLLBACK等),然后可以正确执行这些命令而无须任何进一步分析。一旦知道正在处理的实际查询(例如SELECT或UPDATE),就可以开始事务,这时才调用语义分析过程。
1) parser源码组织
parser源码目录为:/src/common/backend/parser。parser源码文件如表1-3所示。
表1-3 parser源码文件
模块 源码文件 功能
parser parser.cpp 解析主程序
scan.l 词法分析,分解查询成token(令牌)
scansup.cpp 处理查询语句转义符
kwlookup.cpp 将关键词转换为具体的token
keywords.cpp 标准关键词列表
analyze.cpp 语义分析
gram.y 语法分析,解析查询token并产生原始解析树
parse_agg.cpp 处理聚集操作,比如SUM(col1)、AVG(col2)
parse_clause.cpp 处理子句,比如WHERE、ORDER BY
parse_compatibility.cpp 处理数据库兼容语法和特性支持
parse_coerce.cpp 处理表达式数据类型强制转换
parse_collate.cpp 对完成表达式添加校对信息
parse_cte.cpp 处理公共表格表达式(WITH 子句)
parse_expr.cpp 处理表达式,比如col、col+3、x=3
parse_func.cpp 处理函数,table.column和列标识符
parse_node.cpp 对各种结构创建解析节点
parse_oper.cpp 处理表达式中的操作符
parse_param.cpp 处理参数
parse_relation.cpp 支持表和列的关系处理程序
parse_target.cpp 处理查询解析的结果列表
parse_type.cpp 处理数据类型
parse_utilcmd.cpp 处理实用命令的解析分析
2) parser主流程
parser主流程代码如下:
/* parser.cpp /
…
/ 原始解析器,输入查询字符串,做词法和语法分析,返回原始语法解析树列表*/
List* raw_parser(const char* str, List** query_string_locationlist)
{
…
/* 初始化 flex scanner /
yyscanner = scanner_init(str, &yyextra.core_yy_extra, ScanKeywords, NumScanKeywords);
…
/ 初始化 bison parser */
parser_init(&yyextra);
/* 解析! */
yyresult = base_yyparse(yyscanner);
/* 清理释放内存*/
scanner_finish(yyscanner);