openGauss数据库源码解析 | openGauss简介(二)

1.2  应用场景

openGauss数据库有以下几个主要应用场景。

(1) 交易型应用。

大并发、大数据量、以联机事务处理为主的交易型应用,如电商、金融、O2O、电信CRM/计费等,可按需选择不同的主备部署模式。

(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通过线程资源池化复用的技术来解决该问题。线程池技术的整体设计思想是线程资源池化,并且在不同连接直接复用。

1. 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

预写式日志写入

2. 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查询响应流程

1. 查询解析——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);
...
    return yyextra.parsetree;
}
/* analyze.cpp */
...
/* 分析原始语法解析树,做语义分析并输出查询树 */
Query* parse_analyze(
    Node* parseTree, const char* sourceText, Oid* paramTypes, int numParams, bool isFirstNode, bool isCreateView)
{
    /* 初始化解析状态和查询树 */
    ParseState* pstate = make_parsestate(NULL);
    Query* query = NULL;
...
    /* 将解析树转换为查询树 */
    query = transformTopLevelStmt(pstate, parseTree, isFirstNode, isCreateView);
...
    /* 释放解析状态 */
    free_parsestate(pstate);
...
    return query;
}
2. SQL查询分流——traffic cop

traffic cop模块负责查询的分流,它负责区分简单和复杂的查询指令。事务控制命令(例如BEGIN和ROLLBACK)非常简单,因此不需要其他处理,而其他命令(例如SELECT和JOIN)则传递给重写器(参考第6章)。这种区分通过对简单命令执行最少的优化,并将更多的时间投入复杂的命令上,从而减少了处理时间。简单和复杂查询指令也对应如下两类解析。

(1) 软解析(简单,旧查询):当openGauss共享缓冲区中存在已提交SQL语句的已解析表示形式时,可以重复利用缓存内容执行语法和语义检查,避免查询优化相对昂贵的操作。
(2) 硬解析(复杂,新查询):如果无缓存语句可重用,或者第一次将SQL语句加载到openGauss共享缓冲区中,则会导致硬解析。同样,当一条语句在共享缓冲区中老化时,再次重新加载该语句时,还会导致另一次硬解析。因此,共享缓冲区的大小也会影响解析调用的数量。

可以查询gs_prepared_statements查看缓存了什么,以区分软/硬解析(它仅对当前会话可见)。此外,gs_buffercache模块提供了一种实时检查共享缓冲区高速缓存内容的方法,它甚至可以分辨出有多少数据块来自磁盘,有多少数据来自共享缓冲区。

1) traffic cop(tcop)源码组织

traffic cop(tcop)源码目录为:/src/common/backend/tcop。traffic cop(tcop)源码文件如表1-4所示。

表1-4  traffic cop(tcop)源码文件

模块

源码文件

功能

tcop

auditfuncs.cpp

记录数据库操作审计信息

autonomous.cpp

创建可被用来执行SQL查询的自动会话

dest.cpp

与查询结果被发往的终点通信

utility.cpp

数据库通用指令控制函数

fastpath.cpp

在事务期间缓存操作函数和类型等信息

postgres.cpp

后端服务器主程序

pquery.cpp

查询处理指令

stmt_retry.cpp

执行SQL语句失败时,分析返回的错误码,决定是否进行重试

2) traffic cop主流程

traffic cop主流程代码如下:

...
/*原始解析器,输入查询字符串,做词法和语法分析,返回原始解析树列表*/
int PostgresMain(int argc, char* argv[], const char* dbname, const char* username)
{
...
    /* 整体初始化*/
    t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(dbname, InvalidOid,     username);
    ...
    /* 事务的自动错误处理 */
    if (sigsetjmp(local_sigjmp_buf, 1) != 0) { ... }
...
/* 错误语句的重新尝试阶段 */
if (IsStmtRetryEnabled() && StmtRetryController->IsQueryRetrying()) 
{ ... }
    /* 无错误查询指令循环处理*/
    for (;;) {
...
/* 按命令类型执行处理流程*/
switch(firstchar){
...

case: 'Q': ... /* 简单查询 */
case: 'P': ... /* 解析 */
case: 'E': ... /* 执行 */
   }
...
    }
    ...
}

  • 22
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值