第四届总结博客
https://forum.gitlink.org.cn/forums/7186/detail
https://forum.gitlink.org.cn/forums/7215/detail
https://forum.gitlink.org.cn/forums/7185/detail
博客总结:
# 博客整体介绍
# 参与队伍
毅阳队:洪毅、王路阳、黄俊源
# 整体主题
整体主题包括但不限于基本操作和部署、密态等值查询技术、安全管理、慢 SQL 原因剖析、事务机制、公共组件等内容。专注于从对源代码的解读中、openGauss 的实际安装和使用中总结出模块的作用机制和原理。
博客力求跳脱出单个源代码文件的限制,以全局的视角审视各个源代码文件、数据结构、执行函数在 openGauss 的某个特定工作的运作流程中发挥的作用,并将我们的理解、执行流程图等以及相关的代码写于博客中,以带给阅读者对 openGauss 运转机制更为深刻的认识。
# 工作介绍
# 黄俊源
# 王路阳
# 洪毅
# 前言
在认真阅读比赛要求的时候,我们发现第四届的参赛选手已经撰写了关于 openGauss 的很多博客,与此同时,我们还发现在 openGauss 社区也有非常丰富的相关资料。我们相信,站在巨人的肩膀上能够让我们走得更远、认识的更深刻。但为了保证我们作品的原创性、发挥自主创造力和想象力,我们在一开始并不选择参考别人写得资料,而是仅仅参考 openGauss 的总体介绍,并详细梳理阅读项目源码,在理解之后逐步写出我们的初步作品。
思想碰撞才会产生灵感的火花,在完成初步作品之后,我们开始阅读其他选手撰写的博客,遇到不相同的主题,我们也会结合源码力求理解,遇到相同的主题则进行两者的对比,并思考谁的理解更加深刻或正确等,在觉得别人的思考更好的时候,我们会附上引用链接,并更为详细说明问题,发表见解。以此来修改深化我们的作品。
完成作品后,也想着向 openGauss 开源社区贡献我们的力量,因此我们在 csdn 等平台上也发布了我们的博客。
# 总结
# 密态等值查询
# 简介
全新的数据全生命周期保护方案:全密态数据库机制。在这种机制下数据在客户端就被加密,从客户端传输到数据库内核,到在内核中完成查询运算,到返回结果给客户端,数据始终处于加密状态,而数据加解密所需的密钥则由用户持有
密态等值查询主要提供以下能力。
(1) 数据加密:openGauss 通过客户端驱动加密敏感数据,保证敏感数据明文不在除客户端驱动外的地方存在。密钥分为数据加密密钥和密钥加密密钥,客户端驱动仅需要妥善保管密钥加密密钥即可保证只有自己才拥有解密数据密文的能力。
(2) 数据检索:openGauss 支持在用户无感知的情况下,为其提供对数据库密文进行等值检索的能力。在数据加密阶段,openGauss 会将与加密相关的元数据存储在系统表中,当处理敏感数据时,客户端会自动检索加密相关元数据并对数据进行加解密。
openGauss 新增数据加解密表语法,通过采用驱动层过滤技术,在客户端的加密驱动中集成了 SQL 语法解析、密钥管理和敏感数据加解密等模块来处理相关语法
上面这句话尚未理解
语法解析器解析设计密态的语法、替换数据参数并存储,重构 SQL 后发送。
接受到加密数据时,解密并刷新缓存
列级加密
用户在创建加密表的时候需要指定加密列的列加密密钥(Column Encryption Key,CEK)和加密类型,以确定该数据列以何种方式进行加密。同时,在创建表前,应该先创建客户端主密钥(client master key,CMK)
加密步骤和语法可简化为如下 3 个阶段:创建客户端密钥 CMK、创建列加密密钥 CEK 和创建加密表。
CMK 被用于加密 CEK,可指定加密 CEK 的算法,即指定 CMK 的密钥类型。
客户端主密钥创建语法本质上是将 CMK 的元信息解析并保存在 CreateClientLogicGlobal 结构体中
服务端解析为 CreateClientLogicGlobal 结构体并检查用户 namespace 等权限
CMK 元信息保存在系统表中。
用 List 来存储参数是因为创建的秘钥有多个么?
有加密 CEK 的 CMK,那么两者是不是多对多的关系
CEK 被用于加密用户数据
列加密密钥的明文,默认随机生成,也可由用户指定
在创建列加密密钥时的检验参数应该是指创建命令中的参数
ENCRYPTED_VALUE 是被加密的列的值
在对 CEK 参数进行解析后,使用 CMK 对 ENCRYPTED_VALUE 参数进行加密,加密完成后使用加密后的 ENCRYPTED_VALUE 参数和其他参数对创建 CEK 的语法进行重构。
将输入的查询语句转换成加密查询语句。
数据加密驱动程序能够实现在数据发送到数据库之前透明地加密数据,数据在整个语句的处理过程中以密文形式存在,在返回结果时,解密返回的数据集,从而保证整个过程对用户是透明、无感知的。
openGauss 密态数据库在进行等值查询的时候,整个查询过程对用户是无感知的,虽然存储在数据库中的数据是密文形式,但在展示数据给用户的时候会将密文数据进行解密处理。以从加密表中进行等值查询语句为例,整个语句处理过程如图 9-42 所示。客户端解析 SELECT 查询语句中的列属性信息,如果缓存已有则从缓存中提取列属性信息;如果缓存中找不到,需要从服务端查询该信息,并缓存。列加密密钥 CEK 是以密文形式存储在服务端,客户端需要解密 CEK。然后用其加密 SELECT 查询语句中条件参数。加密后的 SELECT 查询语句发送给数据库服务端执行完成后,返回加密的查询结果集。客户端用解密后的列加密密钥 CEK 解密 SELECT 查询结果集,并返回解密后的明文结果集给应用端。
# 公共组件
openGauss 的公共组件包括系统表、数据库初始化、多线程架构、线程池、内存管理、多维监控和模拟信号机制等。每个组件实现了一个独立的功能。
# 系统表
系统表又称为数据字典或者元数据,存储管理数据库对象的定义信息,如表、索引、触发器等。用户可通过系统表查询用户定义的具体对象信息,如表的每个字段类型。因为 openGauss 支持一个实例管理多个数据库,所以系统表分为实例级别的系统表和数据库级别的系统表。实例级别的系统表在一个实例管理的多个数据库之间共享,整个实例只有一份;数据库级别的系统表,每个数据库各有一份。
pg_class 系统表中保存的是表的定义信息)
数据库在运行过程中对系统表的访问。openGauss 对系统表的访问主要是通过 syscache 机制。syscache 机制是一个通用的机制,主要对系统表的数据进行缓存,提升系统表数据的访问速度
有一个 oid(object identifier),对象标识符,在访问数据库的时候会查看该值已确定是否能访问。
而表和索引也会有 oid,
# 数据库初始化
数据库正常启动时需要指定数据目录,数据目录中包括了系统表的初始化数据。数据库初始化的过程会生成这些初始系统表数据文件,该过程由 initdb 和 openGauss 进程配合生成。initdb 控制执行过程,创建目录和基本的配置文件;openGauss 进程负责系统表的初始化。initdb 通过 PG_CMD_OPEN 宏启动 openGauss 进程,同时打开一个管道流,然后通过解析系统表文件中的 SQL 命令,并把命令通过 PG_CMD_PUTS 宏的管道流发给 openGauss 进程,最后通过 PG_CMD_CLOSE 宏关闭管道流。PG_CMD_OPEN 宏是系统函数 popen 的封装宏,PG_CMD_PUTS 宏是系统函数 fputs 的封装宏,PG_CMD_CLOSE 宏是系统函数 pclose 的封装宏。
# 多线程架构
openGauss 在启动后只有一个进程,后台任务都是以一个进程中的线程来运行。对于客户端的新连接,在非线程池模式下也是以启动一个业务线程来处理。在多线程架构下更容易实现多个线程资源的共享,如并行查询、线程池等。
# 主要线程
openGauss 的后台线程是不对等的,其中 Postmaster 是主线程,其他线程都是它创建出来的。openGauss 后台线程的功能介绍如表 1 所示。
# 线程间通信
openGauss 多线程通信使用了原来的 PostgreSQL 的多进程通信方式。
# 线程池技术
线程池机制实现了会话和处理线程分离,在大并发连接的情况下仍然能够保证系统有很好的 SLA 响应。另外不同的线程组可绑到不同的 NUMA(non-uniform memory access,非一致性内存访问)核上,天然匹配 NUMA 化的 CPU 架构,从而提升 openGauss 的整体性能。
# 内存管理
数据库在运行过程中涉及许多对象,这些对象具有不同的生命周期,有些处理需要频繁分配内存。如一个 SQL 语句,在解析时需要对词法单元和语法单元分配内存,在执行过程中需要对执行状态分配内存。在事务结束时,如果不是 prepare 语句,那么 SQL 语句的执行计划内存和执行过程的状态内存都需要释放。如果是 prepare 语句,那么执行计划需要保存到缓冲池中,执行过程的状态内存释放即可。为了保证内存分配的高效和避免内存泄漏。
openGauss 在内存管理上采用了上下文的概念,即具有同样生命周期或者属于同一个上下文语义的内存放到一个 MemoryContext 管理,MemoryContext 的结构代码如下(
# 模拟信号机制
信号是 Linux 进程 / 线程之间的一种通信机制
信号是一种有限的资源,OS 提供的信号有 SIGINT、SIGQUIT、SIGTERM、SIGALRM、SIGPIPE、SIGFPE、SIGUSR1、SIGUSR2、SIGCHLD、SIGTTIN、SIGTTOU、SIGXFSZ 等。这些信号一般都是系统专用的,每个信号都有专门的用途,比如 SIGALRM 是系统定时器的通知信号。留给应用的信号主要是 SIGUSR1、SIGUSR2。
信号模拟的基本原理是每个线程注册管理自己的信号处理函数,信号枚举值仍然使用系统的信号值,线程使用自己的变量记录信号和回调函数对应关系。线程之间发送信号时,先设置变量为具体的信号值,然后使用系统调用 pthread_kill 发送信号,线程收到通知后再根据额外的变量表示的具体信号值,回调对应的信号处理函数。
信号处理几个的主要流程为初始化模拟信号机制、注册信号处理函数、发送信号和处理信号。具体的处理逻辑如下。
# 事务机制
事务是数据库操作的执行单位,需要满足最基本的 ACID(原子性、一致性、隔离性、持久性)属性。