随着技术国产化的趋势不断加强,许多大型客户对于数据库的选择也开始倾向于国产产品,例如达梦,以响应国家的号召和政策法规。
在这种背景下,SAAS企业就需要及时将自己的产品系统与国产数据库作对接,以加强其在大客户领域的竞争力。
本文就以MySQL对接国产达梦数据库为例,讨论一种可能的适配方案。
信创数据库对接主要涉及到两类问题:
DML语句的适配也可以简单分为两类问题:
简单用字符串替换能解决的
语句结构改变需要手动调整SQL的,例如:
公司基于MySQL的业务服务有50~60个,手动修改所有服务的代码去适配另一个数据库,不太现实,后期维护也是一个问题,于是就想能不能找一个统一的地方来处理这个适配工作。
在尽量少侵入业务的前提下,有两种处理办法:
a. 在orm层作语法替换,让每个服务集成定制的orm库;
b. 写一个中间件,来代理发给mysql的所有请求,在中间件上完成适配工作。这个中间件需要承担如下角色:
由于中间件对整个系统的侵入最小,所以我们优先尝试走中间件的方案。
调研过多个开源项目,能符合我们需求的有go-mysql-server、 vitess、kingshard, 代码了解下来,vitess过于庞大,go-mysql-server代码不够直观且mysql协议部分依赖其它项目,最后我们选择了基于kingshard作二次开发。
中间件整体采用从上到下的分层依赖结构,几个核心包职责如下:
server部分是基于kingshard的server模块作的二次修改,整体保留了kingshard的函数结构和代码执行流程。
nodes :
-
# db alias name
name : uc_uniform
# db driver name
driver_name: dm
# default max conns for connection pool
max_conns_limit : 32
# master represents a real mysql master server
datasource : dm://uc_uniform/[email protected]:5236?socketTimeout=10000
【mysql协议请求的数据包格式示意】
类型 | 字段 | 含义 |
---|---|---|
int<1> | COMMAND | 0x03:COM_QUERY |
string | query | SQL语句 |
【mysql常用指令及对应的处理】
命令 | 含义 | 处理函数 | 是否需要与数据库交互 |
---|---|---|---|
COM_INIT_DB | 连接建立时指定DB | handleInitDB() | 中间件处理 |
COM_PING | 心跳包 | handlePing() | 中间件处理 |
COM_QUERY | DDL/DML语句 | handleQuery() | 后端数据库处理 |
COM_STMT_PREPARE | 预编译SQL | handleStmtPrepare() | 中间件处理 |
COM_STMT_EXECUTE | 发送参数执行预编译SQL | handleStmtExecute() | 后端数据库处理 |
COM_STMT_CLOSE | 关闭预编译SQL | handleStmtClose() | 中间件处理 |
COM_QUIT | 连接关闭 | handleQuit() | 中间件处理 |
基于database/sql的API定义了一套用于SQL处理的标准插件接口:
// db querier
type dbQuerier interface {
Prepare(query string) (*sql.Stmt, error)
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
// transaction beginner
type txer interface {
Begin() (*sql.Tx, error)
}
// transaction ending
type txEnder interface {
Commit() error
Rollback() error
}
type SQLPlugin interface {
dbQuerier
txer
txEnder
}
只要实现这套接口,就可以当作一个插件灵活插入到SQL的处理流程中执行,例如:
如果不需要了,也可以随时把它移除,对SQL整体处理没有影响。通过此插件设计实现了代码的分层和后续业务灵活扩展:
sqlparser是SQL语法解析的开源项目,我们的改动主要是加了一个SQL语法的转换功能,并定义了一个标准转换接口:
type SQLConverter interface {
Convert(sql string) (string, error)
}
目前只实现了Mysql-to-Oracle的语法转换,转换流程简单示意如下:
以开篇的SQL语句为例,我们示意下转换前和转换后的语法树结构。
# 转换前带on duplicate key update冲突语法的语句
`insert into webcal_live_info(cal_id,channelId,pullurl,password,extraInfo) values(634311,131722,'https://rlive1uat.rmeet.com.cn/activity/geeZWo3','','') on duplicate key update pullurl='https://rlive1uat.rmeet.com.cn/activity/geeZWo3', password='', extraInfo=''`
# 转换后的merge into语句
`merge into webcal_live_info as t using dual on t.cal_id = 634311 and t.channelId = 131722 when matched then update set t.pullurl = 'https://rlive1uat.rmeet.com.cn/activity/geeZWo3', t.password = '', t.extraInfo = '' when not matched then insert (cal_id, channelId, pullurl, password, extraInfo) values (634311, 131722, 'https://rlive1uat.rmeet.com.cn/activity/geeZWo3', '', '')`
转换语法要做的工作就是从原语法树中提取出table、column、data信息,重新组装成一棵符合oracle语法的AST树。
visit := func(node SQLNode) (kcontinue bool, err error) {
switch node.(type) {
case *ColName:
node.(*ColName).Qualifier = TableName{
Name: NewTableIdent("t"),
}
return true, nil
default:
return true, nil
}
}
Walk(visit, node)
mysql包是kingshard自带的mysql数据包读写协议封装,我们的改动主要是添加了对列数据封装成行数据的支持。之所以要做这个工作,主要是database/sql结果集与mysql结果集存在一定的差异。
差异点:
mysql包已经提供了对mysql.Result结构体的协议封装,我们需要做的就是把sql.Result或sql.Rows转换成mysql.Result。大概分为两步:
sql.RawBytes
来接收列数据。将列封装成行的协议规则:
func packetTextRowData(row []sql.RawBytes) (RowData, error) {
length := 0
for _, val := range row {
if val == nil {
length++
} else {
l := len(val)
length += LenEncIntSize(uint64(l)) + l
}
}
data := make([]byte, 0, length)
for _, val := range row {
if val == nil {
data = append(data, NullValue)
} else {
data = append(data, PutLengthEncodedString(val)...)
}
}
if len(data) != length {
return nil, fmt.Errorf("packet row: got %v bytes but expected %v", len(data), length)
}
return RowData(data), nil
}
关于MySQL数据传输协议,请参考文末附的MySQL协议文档链接。
本文从信创数据库对接给现有业务系统带来的冲击角度出发,介绍了一种基于异构DB中间件来简化适配工作量的方法,并给出了中间件的整体设计构思。
中间件首先基于开源kingshard的server包改造了一个MySQL服务端,然后基于sqlparser开源项目完成了SQL语法的解析和转换,最后基于一篇文章Mysql协议介绍来实现了最后的MySQL响应数据包协议封装,一个能够自动完成MySQL和达梦语法协议转换的中间件就构建起来了。
这个中间件虽然是以MySQL和达梦的语法转换为案例来构造,但它通过对语法转换模型作抽象接口提取,理论上可以自由扩展对其它SQL型数据库的支持,详情见3.3节Backend包
。
关于sqlproxy的设计,暂时就介绍这么多,如果有兴趣了解更多,可以查看项目源码,下面参考阅读里有附源码链接。