前置声明
由于 openGauss 数据库本身也开源不久,所以周边基础设施也正处于遍地开花的阶段,所以本文不保证长期的时效性,仅针对现阶段的问题,提出一种解决方案。
openGauss 介绍
按照官网的介绍,openGauss 是一款高性能,高安全,高可靠的开源关系型数据库管理系统,采用木兰宽松许可证v2发行。openGauss内核早期源自开源数据库PostgreSQL,融合了华为在数据库领域多年的内核经验,在架构、事务、存储引擎、优化器及ARM架构上进行了适配与优化。
openGauss 在2020年6月30日开放源代码,代码托管在 gitee 上。
目前我所在公司也主要是做数据库方面的事情,且也基于 openGauss 内核研发了一款商业版的数据库 MogDB,感兴趣的也可以去了解一下。
背景
针对 openGauss 的基础设施不完善,我之前基于 py-postgresql 1.3.0 版本,开发了 openGauss 的 python 驱动,并提交到了 openGauss 官方仓库:https://gitee.com/opengauss/openGauss-connector-python-pyog
该驱动适用于不使用 ORM,以SQL形式交互的 python 程序。
但是在对接客户的过程中,客户提出了这样的需求:
- 程序中用到了 SQLAlchemy 做为ORM来和数据库交互
- 连接数据库时,需要支持类似 JDBC 多 IP 的方式连接(例如:
user:password@host1:port1,host2:port2/db
这种形式),这种需求主要是用于后端数据库部署形态为主备架构,且没有固定的虚拟IP用于连接,所以需要将主备所有机器的IP都传递进去,但只能返回主库的连接。
赶紧去扫了一下 SQLAlchemy 的源码,SQLAlchemy 中目前支持的 pg 驱动有:
其实只要在驱动里面实现了 PEP-249 (DBAPI 2.0 接口规范) 就可以。但是我们都知道,在使用 SQLAlchemy 创建 engine 的时候,需要在 ://
前面使用固定的字符串进行驱动的选择。比如 postgresql+pypostgresql://...
,内部其实就是把加号 replace 成点,然后去加载对应的驱动模块文件。
但是 openGauss 的驱动可能是目前不支持的,所以我们需要解决的就是两个问题:
- 开发一个驱动,能够实现 DBAPI 2.0 接口规范,且支持多IP连接
- 增强 SQLAlchemy ,支持我们开发的 openGauss 驱动
解决了以上两个问题后,就可以和平时使用 SQLAlchemy 一样来操作 openGauss 了。
完整架构图
py-opengauss 库
第一个问题就是要开发一个 openGauss 的驱动,我们上面提到我之前基于 py-postgresql 1.3.0 开发了一个驱动,但是当时仅支持了 openGauss 的连接和SQL交互,现在需要基于目前的需求进行开发。
代码目前已经发布到 github 上,源码地址:https://github.com/vimiix/py-opengauss
包已经推送到 pypi 上,最新版本 1.3.6
首先是 DBAPI 2.0 接口,庆幸的是 py-postgresql 本身就已经支持了 DBAPI 2.0 接口规范,代码位于 postgresql.driver.dbapi20
。
所以我们需要做的是如何在接口上支持多IP。
为了减少对于现有代码的侵入,我修改了 dbapi20 模块中 connect
函数,上层需要通过 host
字段将IP部分(包含所有主机和端口号),传递进来进行处理。
例如: {'host': 'host1:5432,host2:6432'}
在 connect 函数中,会遍历去进行连接,直到找到主库的连接后停止遍历返回连接。
这样就解决了第一个问题。
这部分功能已经发布到 1.3.6 版本中,所以在 pip 安装的时候需指定版本:
pip install py-opengauss==1.3.6
SQLAlchemy 定制
接下来是第二个问题,需要增强 SQLAlchemy 支持 py-opengauss 驱动。
定制后的代码地址:https://github.com/vimiix/sqlalchemy
我 fork 了一份 SQLAlchemy 的代码,在现有的代码上,在 lib/sqlalchemy/dialects/postgresql
中新增 pyopengauss 的模块,在内部 dbapi 接口处返回我们的驱动即可:
@classmethod
def dbapi(cls):
from py_opengauss.driver import dbapi20
return dbapi20
这样我们就在可以 create_engine 的时候,使用 postgresql+pyopengauss://
这个前缀来进行连接了。
此时还存在另外一个问题,我们在业务层创建 engine 时传入的连接字符串假如是多IP形式的,SQLAlchemy 内部会通过一个固定的正则表达式来匹配 user
, password
, host
, port
,db
以及 query
。但是对于多IP的字符串,匹配出来以后仅会匹配到第一个 host
部分,剩余部分被匹配到了 port
部分,类似下面这样:
这样的话,对于 port 字段进行 int(port)
转换整型的时候,必然会报错。
所以我对于正则表达式进行了修改,让 host:port
部分先不拆开,一起匹配出来, 然后我们手动进行解析。如果是多IP的情况,就不进行拆分,直接放入 host
字段传递给驱动(上面提到过,驱动部分我们也是通过该字段解析的)。如果不是多IP形式,就直接 split 拆分,host
字段放 host, port
字段放 port ,和常规连接一样提交给驱动即可。
具体代码实现:
ipv4host = components.pop("ipv4host")
ipv6host = components.pop("ipv6host")
if ipv4host:
if ',' in ipv4host:
# multiple host
components["host"] = ipv4host
elif ':' in ipv4host:
host, port = ipv4host.split(':', 1)
components["host"] = host
components["port"] = int(port)
else:
components["host"] = ipv4host
else:
components["host"] = ipv6host
进行了多IP的适配以后,此时的 SQLAlchemy 就能够满足我们的需求了。
由于我们是基于 SQLAlchemy 的定制,且为了业务代码的少变更,所以安装方式只能是通过下载源码,然后手动通过 python setup.py install
来进行安装。安装后,业务层仅需要修改一个连接字符串即可。
使用方式
- 安装 py-opengauss
- 通过 pip 安装
pip install py-opengauss==1.3.6
- 通过源码安装
git clone https://github.com/vimiix/py-opengauss.git
cd py-opengauss
python3 setup.py install
- 安装定制后的 SQLAlchemy
git clone https://github.com/vimiix/sqlalchemy
cd sqlalchemy
python3 setup.py install
- 业务层修改连接串
from sqlalchemy import create_engine
engine = create_engine('postgresql+pyopengauss://user:password@host1:port1,host2:port2/db')