数据库的扩展开发框架
一般来说,数据库的扩展开发主要有的目的就是扩展数据库引擎的能力(不管是用pgrx还是其他的框架都一样):
例如PostgreSQL上最著名的扩展PostGIS,就是扩展了PG数据库的空间数据支持能力,目前PostGIS已经成了使用最广泛的开源空间数据库扩展。
Esri从10.7版本开始,逐步支持PostGIS,而到10.8之后,所有产品线已经默认全部支持PostGIS,所以Esri的ArcGIS产品线,系统默认支持SDE/PostGIS双线空间数据库引擎)。
如果要扩展数据库的能力,那么一般来说需要以下两种能力:
- 对于数据库引擎自身的全面操作能力,例如从最基础的SQL的能力,到函数定义、触发性、存储过程等;又例如更深度一些的数据库配置的控制能力,甚至与数据库的内核控制能力。只有达到这种能力,才能被称之为一个合格的数据库扩展开发框架。
例如我需要让数据库直接支持我们特别定义的一种文件格式,在数据库层面做到能够导入导出,就必须要进行扩展开发了。 - 就是让数据库系统能够跳出数据库引擎自身的局限,去操作和执行一些更强的能力。例如对于关键数据项进行触发性的监控,一旦特定条件被触发,则调用扩展语言能力,取发送警报信息,设置锁死数据库这种高级能力。
本例中的安全监控能力,通常的实现是依赖与数据库管理系统来做的,也就是第三方应用,对数据库进行监控,这样无论是性能、效率、稳定性还是安全性,都远远不如把这种能力放到数据库引擎上来做。
——题外话:有同学看到这里,可能会说:数据库本来就是一个系统里面压力最大的部分,在架构设计的时候都巴不得把所有的功能都从数据库上挪走,以减轻数据库的压力,你这样做,不是反其道而行之么?
常见高并发系统里面,数据库确实是最容易被攻破的环节,是因为相对于微服务架构下,天生就具备了容易横向扩展和负载均衡的应用系统而言,数据库的横向扩展的难度和成本都是指数级的,所以大部分高并发架构设计中,都使用了各种手段来保护数据库,例如Redis缓存、队列、读写分离等,希望把数据库的压力都分配出去。
而我们这里采用数据库扩展开发,让一部分功能直接在数据库引擎上执行,则有三个方面的考量:
- 对于一些并发量不大,但是对于计算效率要求比较高的系统,数据库压力并不是瓶颈,反而对于CPU密集型运算来说,跑在数据库引擎上这种在“最贴近数据的地方做运算”的优势更大。
- 一些功能,通常是利用第三方应用来监控数据库(例如安全监控,或者信息更新同步),会造成复杂的交互、以及更长的逻辑链,也增大了系统风险的概率。反而把这些功能放到数据库系统上,能够有效的减少系统的复杂性。
- 从IO而言,写在数据库扩展函数里面的功能,在数据库引擎上做计算,能够最大限度的减少数据库与应用系统之间的数据IO。
那么从今天开始,我们就进入pgrx的开发环节,我们来看看如何开发一个数据库扩展功能。
注意!!写在pgrx之前的话:
并不是所有场景都需要(或者适合)用rust来写的,绝大部分操作数据库的功能和计算,用SQL就已经足够了!
本系列中,所有的案例,仅用于说明pgrx的能力,而并非是说这样做比用SQL更合适。反之:对于操作数据库本身的部分,大部分能用SQL来实现的东西,都比做一个扩展开发要更加合适。
pgrx基础功能函数的开发
前言说明:
本系列开发,采用vscode远程连接windows 10 wsl2的ubuntu 的模式,具体安装方式,请自行查阅相关文档,基础环境如下:
系统环境:
- 操作系统版本 : Ubuntu 20.04.5 LTS
- gcc 版本: 9.4.0
- Clang版本:10.0.0
- rust/cargo版本:1.68.0
- pgrx版本:0.7.4
- postgresql版本:11- 15
- 默认在postgresql 13下进行测试。
以上内容安装请看上一篇文章
开发环境:
- vscode 版本: 1.77.3
- 插件:
- rust-analyzer v0.3.1481 (rust插件)
- Rust Syntax v0.6.1 (rust语法提示)
- WSL v0.77.0 (vscode 连接wsl插件)
- CodeLLDB v1.9.0 (C/C++运行即调试插件)
以上版本号截止到2023年4月,之后的话大家用更新的版本,也是可以的。
pgrx的hello world
我们可以从pgrx框架自动生成的脚手架来看看一个hello world程序的结构:
建立的是一个标准的rust cargo模式的工程,一般来说,要关注就这两个文件,lib.rs是rust功能模块主代码,一个rust工程一般只有一个lib文件,而Cargo.toml则是工程的配置文件,二者的具体的功能和作用,大家而已参考Rust相关基础教程。
pgrx的Cargo.toml内容如下:
重点关注几个设置就行:
- features中的default设置,pgrx脚手架给出的默认是pg13的版本,同时告诉你可以在11到15之间进行选择(如果你的开发环境和我一样,把这些都安装了的话)。
- dependencies中设置的pgrx版本,一般默认都会给你最新版本,没啥说的,不过这个设置是cargo最中主要的设置,以后我们要引入其他的包,也都要写在这个地方。
里面的这些内容,你不用pgrx来生成,也是可以的,自己手写一样能够有效果,同样的,你如果你不安装pgrx扩展,全部手写也可以做到,只是没有这些脚手架,各种编译的命令参数和环境设置,会让写到怀疑人生……
之后我们来看看lib.rs中的内容:
我们分成三部分来讲解:
第一部分:逻辑功能块:
use pgrx::prelude::*;
这句代码表示需要引用pgrx包里面的prelude模块,这个模块定义和声明了pgrx里面的各种通用函数、设置和宏,一般来说,有这句代码,pgrx里面常用的功能就到齐了。
pgrx::pg_module_magic!();
这句代码是调用pgrx定义的模块宏,如果不懂什么叫做宏,你就可以理解为,只要有这句话,整个lib的工程,就会被自动识别为postgresql的一个扩展模块,可以在postgresql被自动识别和加载就可以了。
#[pg_extern] fn hello_pgrxdemo() -> &'static str { "Hello, pgrxdemo" }
这个方法就是pgrx的hello world方法,可以看见代码量非常的简洁。
我们逐行进行解读
首先第一句
#[pg_extern]
#[]是一个是Rust中的注解属性,用于编译环境的注解,类似于Python里面的装饰器和Java里面annotation,
而pg_extern表示要用pg_extern这个扩展注释来封装下面Rust函数,使其接口符合 postgres extension 的 C ABI,以及处理 Rust 数据结构和 postgres 内部数据结构的转换。
可以如果你对这些概念都不熟悉的话,仅需要记住在Rust的函数上面,加载了这个注解属性,这个方法就可以被编译成PG兼容的函数了就行。
下面就是我们要写的函数了,这是一个标准的rust函数,结构如下:
fn 函数名(可选参数1: 参数类型1,……) ->返回值类型{ 函数体 }
其中,fn是函数定义的关键字,-> 表示本函数有返回值,这里要注意的是'static str这返回值的写法,单引号是rust里面生命周期声明方法,'static表示声明这个函数的返回值是一个全局(生命周期)静态的字符串。
有关生命周期的问题,请打架查阅Rust相关教程和材料。
题外话,rust语言别认为学习曲线最陡峭的语言之一,一般来说,要学会Rust会要过三大天坑:
一号天坑就是所有权与生命周期问题,这个坑起码能埋掉80%以上的初学者。
二号天坑就是特性(trait)与泛型编程,这个坑能把通过了一号天坑的人里面,再埋掉50%以上。
三号天坑就是异步编程,这个也被认为是rust里面的精华,但是也是最难通过一个坑,这个坑能把通过了前面两关的同学,最后再坑进去50%。
所以,100个进入Rust的初学者,能够通过这三大天坑的,就不到5个人了……
题外话到到此为止,不过大家要想好好学习一下Rust的话, 切记趟这三大天坑的时候要注意。
函数体里面就是一句问好:"hello pgrxdemo",看到这里又有同学问,我们返回的字符串,为什么没有用关键字return呢?
这也是Rust的一个特性,我们函数体里面那个hello pgrxdemo,不但没有return,而且没有;号结尾,这种没有分号的语句,在rust叫做表达式,rust任何表达式都表示(应该)有返回值,如果函数的最后一句是一个表达式的话,则这个表达式的返回值就是这个函数的返回值了。
这个函数可以直接返回一个静态字符串(把静态生命周期去掉也可以,静态字符串对系统性能更友好而已)。
然后在看看lib.rs的第而部分:
这里是pgrx给我们预先生成的一个用于测试的mod,并且给了两个注解属性:
- #[cfg(any(test, feature = "pg_test"))] cfg表示使用属性来进行条件编译,这里的条件本mod下面的所有方法,都用pg_test条件进行测试。
- #[pg_schema]属性表示测试方法会运行在一个postgresql的schema中,如果指定了自己的schema,则运行在自己制定的,如果没有指定,则运行在默认的schema中。
在rust代码里面,注释为test的mod,可以独立运行测试,但是不会在release中进行发布。
最后看看第三部分:
这部分也是pgrx自动生成的,用于初始化测试过程和安装打包,如果你有自定义的postgresql配置,也可以写在这里面,一般情况下,我们不去修改这一部分。
接下去,我们看看在运行的过程中,会发生什么事情?
首先输入debug运行模式的命令:
cargo pgrx run
编译器进行debug模式编译,然后自动启动postgresql的命令模式,主要干了以下几件事情:
- 首先会把工程里面的pgrxdemo.control这个文件拷贝到设置的pg环境下面的extension目录下面,这个文件主要的内容是进行一些常规声明如下:
comment = 'pgrxdemo: Created by pgrx' default_version = '@CARGO_VERSION@' module_pathname = '$libdir/pgrxdemo' relocatable = false superuser = true
一般我们不用去改他(除非你有一些特殊的环境设置)
- 会把你的工程编译好的so库文件,写到你的pg环境下面的lib文件下,文件名通常是你工程的名称,我这里是:
/usr/lib/postgresql/13/lib/pgrxdemo.so - 会在pgrxdemo.control这个文件相同的位置,生成一个sql文件,这个文件主要定以的就是在sql中调用你的扩展函数的声明了,如下所示:
我们去文件系统下面看看:
so文件:
control和sql文件:
注意,只要control和sql文件存在于extension文件里面,如果格式内容正确,postgresql的扩展就能够自动找到并且加载为pg的可用扩展,如下所示:
这里面的版本和说明信息,都是control文件中的内容。
而installed_version(目前已安装版本)为Null,说明系统中可用,但是还没有安装,接下去,我们只需要在数据库里面创建一下扩展,就可以用了。
创建扩展以及执行语句,如下:
至此,一个pgrx扩展的hello world,就完整写完了,里面每个步骤的代码和意义,也做了一个简单的解释。
比较简单……我觉得……是吧……
待续未完。