37 Star 89 Fork 36

calvinwilliams / mysqlda

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.zh-CN.md 40.38 KB
一键复制 编辑 原始数据 按行查看 历史
calvinwilliams 提交于 2017-09-03 20:55 . modify README.zh-CN.md

mysqlda - MySQL数据库中间件

1. 概述

1.1. 数据分布式切分方式

分布式架构中最难解决的是数据分布式问题,大部分数据库中间件都以分库分表作为切分方式,好处是通用,但也存在以下问题:

  1. 扩容过程需要以切片为单位在库间移动数据。扩容规模受到切片数量限制,如果业务发展增长规模大大超出初期预估会导致切片数量不够用,陷入数据硬迁移的困境。
  2. 同一业务对象的数据分散在不同库中,无法做聚合、连接等复杂处理。
  3. 跨库意味着分布式事务,虽然现在有两阶段提交等解决方案,但理论上并不总是那么可靠,尤其是在金融行业苛求数据强一致性时。

以核心业务对象切分方式则以产品线入口业务对象作为切分目标(比如互联网业务系统中的客户对象),开户交易途径数据库中间件,以手机号或其它入口字段作为核心字段做附带权重的客群切分,归属到数据库集群中的某个库中,并保存分配结果,以后该客户的所有交易都会被发往其归属库处理。当需要库存储扩容时,只需简单的增加MySQL归属库到数据库集群中,在数据库中间件系统中增加新归属库配置信息,并调大新库被分配权重,新客户分配归属到新库的概率变大,当新库存储增长到一定程度时调平分配权重,新客户分配归属到所有库的概率均等,直到下一次扩容。

以核心业务对象切分方式的好处是:

  1. 无需预估切片数量,其扩容过程无需移动任何切片或数据。
  2. 由于同一业务对象的数据集中在其归属库中,所以可以进行任意聚合、连接等复杂处理。
  3. 每个库都是全业务库,同一业务对象的所有模块处理都在一个库中完成,不存在跨库分布式事务,数据强一致性丢还给数据库单库来保证。

但也存在以下硬伤:

  1. 产品线设计初期慎重挑选核心业务对象作为切分依据,后期很难变更。
  2. 有些业务系统存在多个核心业务对象,不适合使用这种切分方式,如银行线上线下整合核心。

以分库分表切分以核心业务对象切分是两种主流的数据分布式设计范式,各有优缺点,应在不同场景挑选合适的方式。

1.2. mysqlda

mysqlda是一款基于核心业务对象切分的Proxy模式的分布式MySQL数据库中间件。

mysqlda优势:

  • 以核心业务对象切分方式的所有好处。
  • 支持以核心业务对象定位MySQL归属库(如开户用身份证号或手机号或邮箱),也支持核心业务对象的关联对象(如开户后的用户ID、用户名、账号)定位MySQL归属库。
  • 归属库加权一致性哈希算法的分配权重自动调整,扩容后新库与老库的分配权重也自动调整,无需人工介入,使得所有归属库的数据量尽量自动均衡增长。
  • 已包含数据库网关高可用机制,当一个归属库当前MySQL主服务器不可用时自动切换到备服务器,支持多个备服务器。
  • 与MySQL服务器之间的连接池机制实现了连接复用和闲置清理,提高连接和切换性能。
  • 通过在线重载配置文件,扩容新增MySQL归属库、调整MySQL服务器优先列表等完全无感。

2. 架构与原理

2.1. 体系架构

images/architecture.png

mysqlda数据库中间件完全遵循MySQL通讯协议桥接应用服务器集群和MySQL数据库集群。

mysqlda内部进程结构为“父-单子进程”。

2.2. 工作原理

全量数据以核心业务对象切分到多个归属库中,每个归属库包含全业务表。一个归属库由一个MySQL服务器列表(需部署为向下游同步数据)组成,当当前MySQL服务器不可用时自动切换到下一个。

MySQL数据库集群预创建相同的连接用户名、密码,相同的数据库名和应用表结构,mysqlda预创建相同的连接用户名、密码。

启动mysqlda,从配置文件(etc/mysqlda.conf)中装载连接用户名、密码,从保存文件(etc/mysqlda.save、etc/mysqlda.关联对象类.save)中装载已存在的核心业务对象、关联对象 与 MySQL数据库集群库 归属库关系信息。

应用服务器调用标准MySQL连接函数/方法连接mysqlda,mysqlda会遵循MySQL通讯协议处理MySQL用户登录和密码校验。

登录成功后,所有DSL、DML操作前,应用服务器发送mysqlda扩展SQL选择核心业务对象("select library (核心业务对象)")或关联对象类关联对象("select library_by_correl_object (关联对象类) (关联对象)")以连接MySQL归属库,mysqlda会查询其已分配的MySQL库核心业务对象关联对象类关联对象(如果没有分配过则根据加权一致性哈希算法分配一个归属库并持久化到保存文件中),从该MySQL归属库对应数据库服务器有序列表中选择第一个有效MySQL服务器及其连接池中选取空闲连接(如没有缓存连接则新建一条连接),然后桥接对外和对内连接结对,开始处理后续所有DSL、DML操作。

后续操作中可以也发送mysqlda扩展SQL再选择核心业务对象关联对象类、关联对象以调整MySQL归属库服务器连接。

MySQL归属库对应一个数据库服务器列表,如由MySQL数据库1A(MASTER)、1B(SLAVE)、1C(SLAVE)、1D(SLAVE)组成,1A同步复制数据给1B、1C和1D,如果1A出现故障不能被mysqlda连接,mysqlda会依次尝试连接1B、1C和1D,实现系统可用性。

应用服务器发送mysqlda扩展SQL绑定关联对象类关联对象核心业务对象("set correl_object 关联对象类 关联对象 核心业务对象"),mysqlda会保存该关系并持久化到保存文件中,供以后直接用关联对象类关联对象定位MySQL归属库。

2.3. 简易案例

部署了三个MySQL归属库,每个库有主备两台MySQL服务器组成。

A用户用手机号(核心业务对象)开户,应用服务器发送手机号13812345678给mysqlda请求定位归属库("select library 13812341234"),mysqlda通过加权一致性哈希算法计算出该手机号(分配客户)归属库N并持久化到保存文件中,从归属库N连接池中取出一个连接,把该连接与应用服务器连接桥接,交换后面的所有SQL和处理结果。

开户业务逻辑中创建了账户331234567890,,应用服务器发送mysqlda扩展SQL给mysqla("set correl_object account_no 331234567890 13812345678"),mysqlda绑定两者关系并持久化到保存文件中。

A用户后续处理请求,可以送手机号("select library 13812341234")或账号("select library_by_correl_object account_no 331234567890")给mysqlda定位、连接用户归属库,该用户的所有业务数据和业务处理都在该归属库中完成。

2.4. 内部数据实体和关系

images/data_entity_and_relation.png

一个MySQL归属库(forward_instance)对应一个MySQL数据库服务器有序列表(forward_servers list)。

一个MySQL数据库服务器有序列表(forward_servers list)下辖一个空闲连接池(unused_forward_session list)和一个工作连接池(forward_session list)。

一个核心业务对象可以绑定一个或多个关联对象类(forward_correl_object_class)、关联对象(forward_correl_object)。

一个核心业务对象或一个关联对象类、关联对象MySQL归属库 建立一个归属关系(forward_library)。

accepted_session是应用服务器与mysqlda之间的通讯会话,forward_session是mysqlda与MySQL数据库服务器之间的通讯会话,一旦一条连接上的MySQL归属库被选定或切换,这两个会话会被桥接起来。

3. 安装部署

mysqlda只依赖于mysql或mariadb,必须先安装好开发包*-devel-*。

3.1. 解开mysqlda源码包 或 直接从源码托管地址克隆最新版

$ git clone http://git.oschina.net/calvinwilliams/mysqlda
Cloning into 'mysqlda'...
remote: Counting objects: 355, done.
remote: Compressing objects: 100% (345/345), done.
remote: Total 355 (delta 221), reused 0 (delta 0)
Receiving objects: 100% (355/355), 586.04 KiB | 0 bytes/s, done.
Resolving deltas: 100% (221/221), done.

3.2. 进入src目录,清理中间文件

$ cd mysqlda
$ make -f makefile.Linux clean
make[1]: Entering directory `/home/calvin/src/mysqlda/src'
make[2]: Entering directory `/home/calvin/src/mysqlda/src/mysqlda'
rm -f lk_list.o
rm -f rbtree.o
rm -f LOGC.o
rm -f fasterjson.o
rm -f util.o
rm -f rbtree_ins.o
rm -f IDL_mysqlda_conf.dsc.o
rm -f main.o
rm -f config.o
rm -f monitor.o
rm -f worker.o
rm -f comm.o
rm -f app.o
rm -f mysqlda
make[2]: Leaving directory `/home/calvin/src/mysqlda/src/mysqlda'
make[1]: Leaving directory `/home/calvin/src/mysqlda/src'
make[1]: Entering directory `/home/calvin/src/mysqlda/shbin'
make[1]: Leaving directory `/home/calvin/src/mysqlda/shbin'
make[1]: Entering directory `/home/calvin/src/mysqlda/test'
rm -f mysqlda_test_connect.o
rm -f mysqlda_test_select_library.o
rm -f mysqlda_test_set_correl_object.o
rm -f mysqlda_test_select_library_by_correl_object.o
rm -f mysqlda_test_insert.o
rm -f mysqlda_test_update.o
rm -f mysqlda_test_delete.o
rm -f mysqlda_test_connect
rm -f mysqlda_test_select_library
rm -f mysqlda_test_set_correl_object
rm -f mysqlda_test_select_library_by_correl_object
rm -f mysqlda_test_insert
rm -f mysqlda_test_update
rm -f mysqlda_test_delete
make[1]: Leaving directory `/home/calvin/src/mysqlda/test'

3.3. 修改安装目标目录

可执行文件mysqlda默认编译链接出来后安装到$HOME/bin,如需调整目标目录可编辑src/mysqlda/makeinstall

_BINBASE        =       $(HOME)/bin

管理脚本mysqlda.sh默认编译链接出来后安装到$HOME/shbin,如需调整目标目录可编辑shbin/makeinstall

NOCLEAN_OBJINST =       $(HOME)/shbin

没特殊需求可以不修改

3.4. 编译、安装

$ make -f makefile.Linux install
make[1]: Entering directory `/home/calvin/src/mysqlda/src'
make[2]: Entering directory `/home/calvin/src/mysqlda/src/mysqlda'
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c lk_list.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c rbtree.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c LOGC.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c fasterjson.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c util.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c rbtree_ins.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c IDL_mysqlda_conf.dsc.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c main.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c config.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c monitor.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c worker.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c comm.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include/mysqlda_api -std=gnu99 -I/usr/include/mysql  -c app.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda lk_list.o rbtree.o LOGC.o fasterjson.o util.o rbtree_ins.o IDL_mysqlda_conf.dsc.o main.o config.o monitor.o worker.o comm.o app.o -L. -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient -lcrypto 
cp -rf mysqlda /home/calvin/bin/
make[2]: Leaving directory `/home/calvin/src/mysqlda/src/mysqlda'
make[1]: Leaving directory `/home/calvin/src/mysqlda/src'
make[1]: Entering directory `/home/calvin/src/mysqlda/shbin'
cp -rf mysqlda.sh /home/calvin/shbin/
make[1]: Leaving directory `/home/calvin/src/mysqlda/shbin'
make[1]: Entering directory `/home/calvin/src/mysqlda/test'
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -std=gnu99 -I/usr/include/mysql  -c mysqlda_test_connect.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda_test_connect mysqlda_test_connect.o -L. -L/home/calvin/lib -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient 
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -std=gnu99 -I/usr/include/mysql  -c mysqlda_test_select_library.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda_test_select_library mysqlda_test_select_library.o -L. -L/home/calvin/lib -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient 
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -std=gnu99 -I/usr/include/mysql  -c mysqlda_test_set_correl_object.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda_test_set_correl_object mysqlda_test_set_correl_object.o -L. -L/home/calvin/lib -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient 
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -std=gnu99 -I/usr/include/mysql  -c mysqlda_test_select_library_by_correl_object.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda_test_select_library_by_correl_object mysqlda_test_select_library_by_correl_object.o -L. -L/home/calvin/lib -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient 
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -std=gnu99 -I/usr/include/mysql  -c mysqlda_test_insert.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda_test_insert mysqlda_test_insert.o -L. -L/home/calvin/lib -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient 
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -std=gnu99 -I/usr/include/mysql  -c mysqlda_test_update.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda_test_update mysqlda_test_update.o -L. -L/home/calvin/lib -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient 
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -I. -I/home/calvin/include -std=gnu99 -I/usr/include/mysql  -c mysqlda_test_delete.c
gcc -g -fPIC -O2 -Wall -Werror -fno-strict-aliasing -o mysqlda_test_delete mysqlda_test_delete.o -L. -L/home/calvin/lib -L/home/calvin/lib -L/usr/lib64/mysql -lmysqlclient 
make[1]: Leaving directory `/home/calvin/src/mysqlda/test'

3.5. 查询版本号,也确认可执行文件OK

$ mysqlda -v
mysqlda v0.0.6.0

4. 配置使用

4.1. 自动生成缺省配置文件

$ mysqlda -a init
$ cat ~/etc/mysqlda.conf
{
        "server" : 
        {
                "listen_ip" : "127.0.0.1" ,
                "listen_port" : 13306
        } ,
        "auth" : 
        {
                "user" : "calvin" ,
                "pass" : "calvin" ,
                "db" : "calvindb"
        } ,
        "session_pool" : 
        {
                "unused_forward_session_timeout" : 60
        } ,
        "forwards" : 
        [
        {
                "instance" : "mysqlda1" ,
                "forward" : 
                [
                {
                        "ip" : "127.0.0.1" ,
                        "port" : 3306
                }
                ]
        }
        ]
}

各配置项说明如下:

server.listen_ip : mysqlda面向应用服务器的侦听IP
server.listen_port : mysqlda面向应用服务器的侦听PORT
auth.user : mysqlda面向应用服务器的登录认证用户名,也是mysqlda面向MySQL数据库服务器列表集群的登录认证用户名
auth.pass : mysqlda面向应用服务器的登录认证用户密码,也是mysqlda面向MySQL数据库服务器列表集群的登录认证用户密码
auth.db : mysqlda面向应用服务器的登录数据库,也是mysqlda面向MySQL数据库服务器列表集群的登录数据库
session_pool.unused_forward_session_timeout : mysqlda面向MySQL数据库服务器列表集群的空闲连接池超时清理时间
forwards[].instance : MySQL归属库ID
forwards[].forward[].ip : MySQL归属库服务器IP
forwards[].forward[].port : MySQL归属库服务器PORT

4.2. 启动mysqlda

不带命令行参数直接执行mysqlda会得到完整参数列表,根据实际环境修改完配置文件后启动

$ mysqlda
USAGE : mysqlda -f (config_filename) --no-daemon -a [ init | start ]
                                     [ --loglevel-debug | --loglevel-info | --loglevel-notice
                                     | --loglevel-warn | --loglevel-error | --loglevel-fatal ]
                -v
$ mysqlda -a start

注意:默认日志等级为NOTICE,如果想以调试等级启动,加上命令行参数--loglevel-debug。

也可以使用管理脚本启动

$ mysqlda.sh
USAGE : mysqlda.sh [ status | start | stop | kill | restart | reload ] *
$ mysqlda.sh start

注意:mysqlda.sh第二个参数开始的参数列表会传递给mysqlda -a start后面。

查询启动日志(以我的环境参数配置)

$ view ~/log/mysqlda.log
2017-09-03 14:17:17.306390 | NOTICE | 18582:monitor.c:75 | [18582]fork[18583] ok
2017-09-03 14:17:17.306460 | NOTICE | 18582:monitor.c:69 | [18582]fork[18583] ok
2017-09-03 14:17:17.307423 | NOTICE | 18583:config.c:259 | Load forward_library /home/calvin/etc/mysqlda.save ok , count[2000]
2017-09-03 14:17:17.307452 | NOTICE | 18583:config.c:358 | Load all forward_correl_object_class ok , count[0]
2017-09-03 14:17:17.307456 | NOTICE | 18583:config.c:369 | instance[0x225a180][mysql_data_1] serial_range_begin[0] power[989]
2017-09-03 14:17:17.307459 | NOTICE | 18583:config.c:373 |      ip[192.168.6.22] port[3306]
2017-09-03 14:17:17.307462 | NOTICE | 18583:config.c:369 | instance[0x225a260][mysql_data_2] serial_range_begin[989] power[1013]
2017-09-03 14:17:17.307464 | NOTICE | 18583:config.c:373 |      ip[192.168.6.23] port[3306]
2017-09-03 14:17:17.307467 | NOTICE | 18583:config.c:377 | total_power[2002]
2017-09-03 14:17:17.307475 | NOTICE | 18583:worker.c:204 | epoll_create ok , #1#
2017-09-03 14:17:17.307479 | NOTICE | 18583:worker.c:219 | epoll_ctl #1# add alive_pipe_session #0# ok
2017-09-03 14:17:17.307488 | NOTICE | 18583:worker.c:231 | socket ok , #2#
2017-09-03 14:17:17.307499 | NOTICE | 18583:worker.c:248 | bind[192.168.6.21:13306] #2# ok
2017-09-03 14:17:17.307506 | NOTICE | 18583:worker.c:260 | listen[192.168.6.21:13306] #2# ok
2017-09-03 14:17:17.307509 | NOTICE | 18583:worker.c:275 | epoll_ctl #1# add listen_session #2# ok
2017-09-03 14:17:17.308110 | NOTICE | 18583:worker.c:293 | [mysql_data_1]mysql_real_connect[192.168.6.22][3306][calvin][calvin][calvindb] connecting ...
2017-09-03 14:17:17.309869 | NOTICE | 18583:worker.c:302 | [mysql_data_1]mysql_real_connect[192.168.6.22][3306][calvin][calvin][calvindb] connecting ok
2017-09-03 14:17:17.310148 | NOTICE | 18583:worker.c:313 | [mysql_data_1]mysql_close[192.168.6.22][3306] ok
2017-09-03 14:17:17.310219 | NOTICE | 18583:worker.c:293 | [mysql_data_2]mysql_real_connect[192.168.6.23][3306][calvin][calvin][calvindb] connecting ...
2017-09-03 14:17:17.311103 | NOTICE | 18583:worker.c:302 | [mysql_data_2]mysql_real_connect[192.168.6.23][3306][calvin][calvin][calvindb] connecting ok
2017-09-03 14:17:17.311119 | NOTICE | 18583:worker.c:313 | [mysql_data_2]mysql_close[192.168.6.23][3306] ok
2017-09-03 14:17:18.313143 | NOTICE | 18583:worker.c:345 | epoll_wait #1# return[0]events
2017-09-03 14:17:19.315163 | NOTICE | 18583:worker.c:345 | epoll_wait #1# return[0]events
2017-09-03 14:17:20.316408 | NOTICE | 18583:worker.c:345 | epoll_wait #1# return[0]events

则表示启动成功

如果mysqlda连接MySQL数据库集群有问题,启动初始化阶段会侦测出来

2017-09-03 14:17:17.306390 | NOTICE | 18582:monitor.c:75 | [18582]fork[18583] ok
2017-09-03 14:17:17.306460 | NOTICE | 18582:monitor.c:69 | [18582]fork[18583] ok
2017-09-03 14:17:17.307423 | NOTICE | 18583:config.c:259 | Load forward_library /home/calvin/etc/mysqlda.save ok , count[2000]
2017-09-03 14:17:17.307452 | NOTICE | 18583:config.c:358 | Load all forward_correl_object_class ok , count[0]
2017-09-03 14:17:17.307456 | NOTICE | 18583:config.c:369 | instance[0x225a180][mysql_data_1] serial_range_begin[0] power[989]
2017-09-03 14:17:17.307459 | NOTICE | 18583:config.c:373 |      ip[192.168.6.22] port[3306]
2017-09-03 14:17:17.307462 | NOTICE | 18583:config.c:369 | instance[0x225a260][mysql_data_2] serial_range_begin[989] power[1013]
2017-09-03 14:17:17.307464 | NOTICE | 18583:config.c:373 |      ip[192.168.6.23] port[3306]
2017-09-03 14:17:17.307467 | NOTICE | 18583:config.c:377 | total_power[2002]
2017-09-03 14:17:17.307475 | NOTICE | 18583:worker.c:204 | epoll_create ok , #1#
2017-09-03 14:17:17.307479 | NOTICE | 18583:worker.c:219 | epoll_ctl #1# add alive_pipe_session #0# ok
2017-09-03 14:17:17.307488 | NOTICE | 18583:worker.c:231 | socket ok , #2#
2017-09-03 14:17:17.307499 | NOTICE | 18583:worker.c:248 | bind[192.168.6.21:13306] #2# ok
2017-09-03 14:17:17.307506 | NOTICE | 18583:worker.c:260 | listen[192.168.6.21:13306] #2# ok
2017-09-03 14:17:17.307509 | NOTICE | 18583:worker.c:275 | epoll_ctl #1# add listen_session #2# ok
2017-09-03 14:17:17.308110 | NOTICE | 18583:worker.c:293 | [mysql_data_1]mysql_real_connect[192.168.6.22][3306][calvin][calvin][calvindb] connecting ...
2017-08-28 14:17:17.308110 | ERROR | 53070:worker.c:130 | [mysql_data_1]mysql_real_connect[192.168.6.22][13306][calvin][calvin][calvindb] failed , mysql_errno[2003][Can't connect to MySQL server on '192.168.6.22' (113)]
2017-08-28 14:17:17.308110 | INFO  | 53070:worker.c:482 | worker exit ...

4.3. 停止mysqlda

直接ps出来发送TERM信号即可,如果停不掉就发送KILL信号

$ ps -ef | grep mysqlda
calvin   53069     1  0 00:12 pts/1    00:00:00 mysqlda -a start
calvin   53097 53072  0 00:12 pts/2    00:00:00 view mysqlda.log
calvin   53111 52899  0 00:12 pts/1    00:00:00 grep --color=auto mysqlda
$ kill 53069

也可以使用管理脚本停止

$ mysqlda.sh stop

4.4. 扩容MySQL数据库集群

4.4.1. 增加MySQL归属库

在配置文件mysqlda.conf中新增一个forwards[],至少一个MySQL服务器forward,如以下新增了MySQL归属库mysql_data_3:

        "forwards" : 
        [
                {
                        "instance" : "mysql_data_1" ,
                        "forward" : 
                        [
                        { "ip" : "192.168.6.11" , "port" : 13306 } ,
                        { "ip" : "192.168.6.12" , "port" : 13306 } ,
                        { "ip" : "192.168.6.13" , "port" : 13306 }
                        ]
                } ,
                {
                        "instance" : "mysql_data_2" ,
                        "forward" : 
                        [
                        { "ip" : "192.168.6.21" , "port" : 13306 } ,
                        { "ip" : "192.168.6.22" , "port" : 13306 } ,
                        { "ip" : "192.168.6.23" , "port" : 13306 }
                        ]
                } ,
                {
                        "instance" : "mysql_data_3" ,
                        "forward" : 
                        [
                        { "ip" : "192.168.6.31" , "port" : 13306 } ,
                        { "ip" : "192.168.6.32" , "port" : 13306 } ,
                        { "ip" : "192.168.6.33" , "port" : 13306 }
                        ]
                }
        ]

发送USR1信号到mysqlda父进程。

注意:产生存量保存信息后一般不会修改或删除MySQL归属库。

4.4.2. 调整MySQL归属库服务器列表

在配置文件mysqlda.conf中新增一个forwards[],至少一个MySQL服务器forward,如以下每个MySQL归属库都增加了一台MySQL服务器:

        "forwards" : 
        [
                {
                        "instance" : "mysql_data_1" ,
                        "forward" : 
                        [
                        { "ip" : "192.168.6.11" , "port" : 13306 } ,
                        { "ip" : "192.168.6.12" , "port" : 13306 } ,
                        { "ip" : "192.168.6.13" , "port" : 13306 } ,
                        { "ip" : "192.168.6.14" , "port" : 13306 }
                        ]
                } ,
                {
                        "instance" : "mysql_data_2" ,
                        "forward" : 
                        [
                        { "ip" : "192.168.6.21" , "port" : 13306 } ,
                        { "ip" : "192.168.6.22" , "port" : 13306 } ,
                        { "ip" : "192.168.6.23" , "port" : 13306 } ,
                        { "ip" : "192.168.6.24" , "port" : 13306 }
                        ]
                }
        ]

发送USR1信号到mysqlda父进程以重载配置,也可以使用管理脚本。

$ mysqlda.sh reload

注意:理论上支持在一个MySQL归属库中修改和删除一台MySQL服务器,但会立即断开这台MySQL服务器的所有连接。

5. 保存文件输出格式

5.1. 核心业务对象-MySQL归属库 保存文件格式

每个核心业务对象建立归属关系后都会输出其关系到保存文件

文件名规则如下:

$HOME/etc/mysqlda.save

文件格式如下:

建立的日期 建立的时间 核心业务对象 MySQL归属库

示例如下:

2017-08-24 22:17:11 1 mysql_data_2
2017-08-26 16:34:31 2 mysql_data_1
2017-08-26 16:34:31 3 mysql_data_1
2017-08-26 16:34:31 4 mysql_data_1
2017-08-26 16:34:31 5 mysql_data_2
2017-08-26 16:34:31 6 mysql_data_1
2017-08-26 16:34:31 7 mysql_data_1
2017-08-26 16:34:31 8 mysql_data_1
2017-08-26 16:34:31 9 mysql_data_1
2017-08-26 16:34:31 10 mysql_data_1

5.2. 关联对象类、关联对象-核心业务对象 保存文件格式

每个关联对象类、关联对象和核心业务对象建立关系后都会输出其关系到保存文件

文件名规则如下:

$HOME/etc/mysqlda.(关联对象类).save

文件格式如下:

建立的日期 建立的时间 关联对象 核心业务对象

示例如下:

2017-08-26 18:11:42 330001 2
2017-08-26 18:12:41 330002 3

6. 开发示例

6.1. 说明

新增了三条mysqlda扩展SQL用于定位MySQL归属库、绑定关联对象等。

6.1.1. 用核心业务对象定位MySQL归属库

开发流程中,只需在连接函数/方法后,SQL执行前,插入mysqlda扩展SQL通过核心业务对象定位归属库:

select library (核心业务对象)

mysqlda会根据核心业务对象查询与MySQL归属库集合中一个归属库的映射关系,没有则用加权一致性哈希算法新建并持久化到保存文件中。

6.1.2. 用关联对象类、关联对象绑定到核心业务对象

关联对象类、关联对象 与 核心业务对象 绑定关系由mysqlda扩展SQL设置,并持久化到保存文件中:

set correl_object (关联对象类) (关联对象) (核心业务对象)

6.1.3. 用关联对象类、关联对象定位MySQL归属库

也可以通过mysqlda扩展SQL用关联对象类关联对象定位归属库

select library_by_correl_object (关联对象类) (关联对象)

mysqlda会根据关联对象类关联对象查询绑定的核心业务对象,再查询其归属库

6.2. C语言示例

项目主目录的test目录里是测试程序源码。

6.2.1. 用核心业务对象定位MySQL归属库

该测试程序用于给定一个序号区间,批量的建立和定位MySQL归属库。

注意:也可以直接用客户端mysql连接服务器执行SQL"select library (核心业务对象);"得到等价效果。

测试程序示例如下:

$ cat test/mysqlda_test_select_library.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "my_global.h"
#include "mysql.h"

/*
./mysqlda_test_connect "192.168.6.21" 13306 calvin calvin calvindb
*/

static void usage()
{
	printf( "USAGE : mysqlda_test_connect (ip) (port) (user) (pass) (database)\n" );
	return;
}

int main( int argc , char *argv[] )
{
	MYSQL		*conn = NULL ;
	char		*ip = NULL ;
	unsigned int	port ;
	char		*user = NULL ;
	char		*pass = NULL ;
	char		*database = NULL ;
	
	if( argc != 1 + 5 )
	{
		usage();
		exit(7);
	}
	
	printf( "mysql_get_client_info[%s]\n" , mysql_get_client_info() );
	
	conn = mysql_init(NULL) ;
	if( conn == NULL )
	{
		printf( "mysql_init failed\n" );
		return 1;
	}
	
	ip = argv[1] ;
	port = (unsigned int)atoi(argv[2]) ;
	user = argv[3] ;
	pass = argv[4] ;
	database = argv[5] ;
	if( mysql_real_connect( conn , ip , user , pass , database , port , NULL , 0 ) == NULL )
	{
		printf( "mysql_real_connect failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		return 1;
	}
	else
	{
		printf( "mysql_real_connect ok\n" );
	}
	
	mysql_close( conn );
	printf( "mysql_close\n" );
	
	return 0;
}

6.2.2. 用关联对象类、关联对象绑定到核心业务对象

该测试程序用于给定关联对象类、关联对象、核心业务对象,建立其关系

注意:也可以直接用客户端mysql连接服务器执行SQL"set correl_object 关联对象类 关联对象 核心业务对象;"得到等价效果。

测试程序示例如下:

$ cat test/mysqlda_test_set_correl_object.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "my_global.h"
#include "mysql.h"

/*
./mysqlda_test_set_correl_object "192.168.6.21" 13306 calvin calvin calvindb card_no 330001 1
*/

static void usage()
{
	printf( "USAGE : mysqlda_test_set_correl_object (ip) (port) (user) (pass) (database) (correl_object_class) (correl_object) (library)\n" );
	return;
}

int main( int argc , char *argv[] )
{
	MYSQL		*conn = NULL ;
	char		*ip = NULL ;
	unsigned int	port ;
	char		*user = NULL ;
	char		*pass = NULL ;
	char		*database = NULL ;
	
	char		*correl_object_class = NULL ;
	char		*correl_object = NULL ;
	char		*library = NULL ;
	char		sql[ 4096 + 1 ] ;
	
	int		nret = 0 ;
	
	if( argc != 1 + 8 )
	{
		usage();
		exit(7);
	}
	
	printf( "mysql_get_client_info[%s]\n" , mysql_get_client_info() );
	
	conn = mysql_init(NULL) ;
	if( conn == NULL )
	{
		printf( "mysql_init failed\n" );
		return 1;
	}
	
	ip = argv[1] ;
	port = (unsigned int)atoi(argv[2]) ;
	user = argv[3] ;
	pass = argv[4] ;
	database = argv[5] ;
	if( mysql_real_connect( conn , ip , user , pass , database , port , NULL , 0 ) == NULL )
	{
		printf( "mysql_real_connect failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		return 1;
	}
	else
	{
		printf( "mysql_real_connect ok\n" );
	}
	
	correl_object_class = argv[6] ;
	correl_object = argv[7] ;
	library = argv[8] ;
	
	memset( sql , 0x00 , sizeof(sql) );
	snprintf( sql , sizeof(sql) , "set correl_object %s %s %s" , correl_object_class , correl_object , library );
	nret = mysql_query( conn , sql ) ;
	if( nret )
	{
		printf( "mysql_query failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		mysql_close( conn );
		return 1;
	}
	else
	{
		printf( "mysql_query ok\n" );
	}
	
	mysql_close( conn );
	printf( "mysql_close\n" );
	
	return 0;
}

6.2.3. 用关联对象类、关联对象定位MySQL归属库

该测试程序用于给定一个序号区间,批量的建立和定位MySQL归属库

注意:也可以直接用客户端mysql连接服务器执行SQL"select library_by_correl_object 关联对象类 关联对象;"得到等价效果。

测试程序示例如下:

$ cat test/mysqlda_test_select_library_by_correl_object.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "my_global.h"
#include "mysql.h"

/*
./mysqlda_test_select_library_by_correl_object "192.168.6.21" 13306 calvin calvin calvindb card_no 330001
*/

static void usage()
{
	printf( "USAGE : mysqlda_test_select_library_by_correl_object (ip) (port) (user) (pass) (database) (correl_object_class) (correl_object)\n" );
	return;
}

int main( int argc , char *argv[] )
{
	MYSQL		*conn = NULL ;
	char		*ip = NULL ;
	unsigned int	port ;
	char		*user = NULL ;
	char		*pass = NULL ;
	char		*database = NULL ;
	
	char		*correl_object_class = NULL ;
	char		*correl_object = NULL ;
	char		sql[ 4096 + 1 ] ;
	
	int		nret = 0 ;
	
	if( argc != 1 + 7 )
	{
		usage();
		exit(7);
	}
	
	printf( "mysql_get_client_info[%s]\n" , mysql_get_client_info() );
	
	conn = mysql_init(NULL) ;
	if( conn == NULL )
	{
		printf( "mysql_init failed\n" );
		return 1;
	}
	
	ip = argv[1] ;
	port = (unsigned int)atoi(argv[2]) ;
	user = argv[3] ;
	pass = argv[4] ;
	database = argv[5] ;
	if( mysql_real_connect( conn , ip , user , pass , database , port , NULL , 0 ) == NULL )
	{
		printf( "mysql_real_connect failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		return 1;
	}
	else
	{
		printf( "mysql_real_connect ok\n" );
	}
	
	correl_object_class = argv[6] ;
	correl_object = argv[7] ;
	
	memset( sql , 0x00 , sizeof(sql) );
	snprintf( sql , sizeof(sql) , "select library_by_correl_object %s %s" , correl_object_class , correl_object );
	nret = mysql_query( conn , sql ) ;
	if( nret )
	{
		printf( "mysql_query failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		mysql_close( conn );
		return 1;
	}
	else
	{
		printf( "mysql_query ok\n" );
	}
	
	mysql_close( conn );
	printf( "mysql_close\n" );
	
	return 0;
}

6.2.4. 用核心业务对象定位MySQL归属库、然后批量执行INSERT语句

该测试程序用于给定一个序号区间,批量的建立、定位MySQL归属库、执行INSERT语句

测试程序示例如下:

$ cat test/mysqlda_test_insert.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "my_global.h"
#include "mysql.h"

/*
./mysqlda_test_insert "192.168.6.21" 13306 calvin calvin calvindb 1 1000
*/

static void usage()
{
	printf( "USAGE : mysqlda_test_insert (ip) (port) (user) (pass) (database) (begin_seqno) (end_seqno)\n" );
	return;
}

int main( int argc , char *argv[] )
{
	MYSQL		*conn = NULL ;
	char		*ip = NULL ;
	unsigned int	port ;
	char		*user = NULL ;
	char		*pass = NULL ;
	char		*database = NULL ;
	
	int		begin_seqno ;
	int		end_seqno ;
	int		seqno ;
	char		seqno_buffer[ 20 + 1 ] ;
	char		sql[ 4096 + 1 ] ;
	
	int		nret = 0 ;
	
	if( argc != 1 + 7 )
	{
		usage();
		exit(7);
	}
	
	printf( "mysql_get_client_info[%s]\n" , mysql_get_client_info() );
	
	conn = mysql_init(NULL) ;
	if( conn == NULL )
	{
		printf( "mysql_init failed\n" );
		return 1;
	}
	
	ip = argv[1] ;
	port = (unsigned int)atoi(argv[2]) ;
	user = argv[3] ;
	pass = argv[4] ;
	database = argv[5] ;
	if( mysql_real_connect( conn , ip , user , pass , database , port , NULL , 0 ) == NULL )
	{
		printf( "mysql_real_connect failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		return 1;
	}
	else
	{
		printf( "mysql_real_connect ok\n" );
	}
	
	memset( seqno_buffer , 0x00 , sizeof(seqno_buffer) );
	begin_seqno = atoi(argv[6]) ;
	end_seqno = atoi(argv[7]) ;
	for( seqno = begin_seqno ; seqno <= end_seqno ; seqno++ )
	{
		memset( sql , 0x00 , sizeof(sql) );
		snprintf( sql , sizeof(sql) , "select library %d" , seqno );
		nret = mysql_query( conn , sql ) ;
		if( nret )
		{
			printf( "mysql_query[%s] failed , mysql_errno[%d][%s]\n" , sql , mysql_errno(conn) , mysql_error(conn) );
			mysql_close( conn );
			return 1;
		}
		else
		{
			printf( "mysql_query[%s] ok\n" , sql );
		}
		
		memset( sql , 0x00 , sizeof(sql) );
		snprintf( sql , sizeof(sql) , "insert into test_table value( '%d' , '%d' )" , seqno , seqno );
		nret = mysql_query( conn , sql ) ;
		if( nret )
		{
			printf( "mysql_query[%s] failed , mysql_errno[%d][%s]\n" , sql , mysql_errno(conn) , mysql_error(conn) );
			mysql_close( conn );
			return 1;
		}
		else
		{
			printf( "mysql_query[%s] ok\n" , sql );
		}
	}
	
	mysql_close( conn );
	printf( "mysql_close\n" );
	
	return 0;
}

6.2.5. 用核心业务对象定位MySQL归属库、然后批量执行UPDATE语句

该测试程序用于给定一个序号区间,批量建立、定位MySQL归属库、执行UPDATE语句

测试程序示例如下:

$ cat test/mysqlda_test_update.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "my_global.h"
#include "mysql.h"

/*
./mysqlda_test_update "192.168.6.21" 13306 calvin calvin calvindb 1 1000
*/

static void usage()
{
	printf( "USAGE : mysqlda_test_update (ip) (port) (user) (pass) (database) (begin_seqno) (end_seqno)\n" );
	return;
}

int main( int argc , char *argv[] )
{
	MYSQL		*conn = NULL ;
	char		*ip = NULL ;
	unsigned int	port ;
	char		*user = NULL ;
	char		*pass = NULL ;
	char		*database = NULL ;
	
	int		begin_seqno ;
	int		end_seqno ;
	int		seqno ;
	char		seqno_buffer[ 20 + 1 ] ;
	char		sql[ 4096 + 1 ] ;
	
	int		nret = 0 ;
	
	if( argc != 1 + 7 )
	{
		usage();
		exit(7);
	}
	
	printf( "mysql_get_client_info[%s]\n" , mysql_get_client_info() );
	
	conn = mysql_init(NULL) ;
	if( conn == NULL )
	{
		printf( "mysql_init failed\n" );
		return 1;
	}
	
	ip = argv[1] ;
	port = (unsigned int)atoi(argv[2]) ;
	user = argv[3] ;
	pass = argv[4] ;
	database = argv[5] ;
	if( mysql_real_connect( conn , ip , user , pass , database , port , NULL , 0 ) == NULL )
	{
		printf( "mysql_real_connect failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		return 1;
	}
	else
	{
		printf( "mysql_real_connect ok\n" );
	}
	
	memset( seqno_buffer , 0x00 , sizeof(seqno_buffer) );
	begin_seqno = atoi(argv[6]) ;
	end_seqno = atoi(argv[7]) ;
	for( seqno = begin_seqno ; seqno <= end_seqno ; seqno++ )
	{
		memset( sql , 0x00 , sizeof(sql) );
		snprintf( sql , sizeof(sql) , "select library %d" , seqno );
		nret = mysql_query( conn , sql ) ;
		if( nret )
		{
			printf( "mysql_query[%s] failed , mysql_errno[%d][%s]\n" , sql , mysql_errno(conn) , mysql_error(conn) );
			mysql_close( conn );
			return 1;
		}
		else
		{
			printf( "mysql_query[%s] ok\n" , sql );
		}
		
		memset( sql , 0x00 , sizeof(sql) );
		snprintf( sql , sizeof(sql) , "update test_table set value='%d' where name='%d'" , seqno , seqno );
		nret = mysql_query( conn , sql ) ;
		if( nret )
		{
			printf( "mysql_query[%s] failed , mysql_errno[%d][%s]\n" , sql , mysql_errno(conn) , mysql_error(conn) );
			mysql_close( conn );
			return 1;
		}
		else
		{
			printf( "mysql_query[%s] ok\n" , sql );
		}
	}
	
	mysql_close( conn );
	printf( "mysql_close\n" );
	
	return 0;
}

6.2.6. 用核心业务对象定位MySQL归属库、然后批量执行DELETE语句

该测试程序用于给定一个序号区间,批量的建立、定位MySQL归属库、执行DELETE语句

测试程序示例如下:

$ cat test/mysqlda_test_delete.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "my_global.h"
#include "mysql.h"

/*
./mysqlda_test_delete "192.168.6.21" 13306 calvin calvin calvindb 1 1000
*/

static void usage()
{
	printf( "USAGE : mysqlda_test_delete (ip) (port) (user) (pass) (database) (begin_seqno) (end_seqno)\n" );
	return;
}

int main( int argc , char *argv[] )
{
	MYSQL		*conn = NULL ;
	char		*ip = NULL ;
	unsigned int	port ;
	char		*user = NULL ;
	char		*pass = NULL ;
	char		*database = NULL ;
	
	int		begin_seqno ;
	int		end_seqno ;
	int		seqno ;
	char		seqno_buffer[ 20 + 1 ] ;
	char		sql[ 4096 + 1 ] ;
	
	int		nret = 0 ;
	
	if( argc != 1 + 7 )
	{
		usage();
		exit(7);
	}
	
	printf( "mysql_get_client_info[%s]\n" , mysql_get_client_info() );
	
	conn = mysql_init(NULL) ;
	if( conn == NULL )
	{
		printf( "mysql_init failed\n" );
		return 1;
	}
	
	ip = argv[1] ;
	port = (unsigned int)atoi(argv[2]) ;
	user = argv[3] ;
	pass = argv[4] ;
	database = argv[5] ;
	if( mysql_real_connect( conn , ip , user , pass , database , port , NULL , 0 ) == NULL )
	{
		printf( "mysql_real_connect failed , mysql_errno[%d][%s]\n" , mysql_errno(conn) , mysql_error(conn) );
		return 1;
	}
	else
	{
		printf( "mysql_real_connect ok\n" );
	}
	
	memset( seqno_buffer , 0x00 , sizeof(seqno_buffer) );
	begin_seqno = atoi(argv[6]) ;
	end_seqno = atoi(argv[7]) ;
	for( seqno = begin_seqno ; seqno <= end_seqno ; seqno++ )
	{
		memset( sql , 0x00 , sizeof(sql) );
		snprintf( sql , sizeof(sql) , "select library %d" , seqno );
		nret = mysql_query( conn , sql ) ;
		if( nret )
		{
			printf( "mysql_query[%s] failed , mysql_errno[%d][%s]\n" , sql , mysql_errno(conn) , mysql_error(conn) );
			mysql_close( conn );
			return 1;
		}
		else
		{
			printf( "mysql_query[%s] ok\n" , sql );
		}
		
		memset( sql , 0x00 , sizeof(sql) );
		snprintf( sql , sizeof(sql) , "delete from test_table where name='%d'" , seqno );
		nret = mysql_query( conn , sql ) ;
		if( nret )
		{
			printf( "mysql_query[%s] failed , mysql_errno[%d][%s]\n" , sql , mysql_errno(conn) , mysql_error(conn) );
			mysql_close( conn );
			return 1;
		}
		else
		{
			printf( "mysql_query[%s] ok\n" , sql );
		}
	}
	
	mysql_close( conn );
	printf( "mysql_close\n" );
	
	return 0;
}

7. 其它注意事项

7.1. 用客户端mysql直连mysqlda

  • 可以用客户端mysql直接连接mysqlda,如"mysql --host 192.168.6.21 --port 3306 -u calvin -p",但暂时不支持命令行中直接指定数据库。
  • 不用指定数据库,mysqlda已经用配置文件中的参数use数据库了。
  • 登录成功后的第一条命令必须是选择后端MySQL归属库,后续才能执行DDL、DSL、DML等SQL。

8. 最后

8.1. 后续研发

  • 寻求JAVA环境测试
  • 现在只支持mysqlda单服务器,正在设计实现mysqlda服务器集群机制。
  • 内存使用优化
  • 更少的性能衰减
  • 更多功能...

8.2. 源码托管

8.3. 作者邮箱

C
1
https://gitee.com/calvinwilliams/mysqlda.git
git@gitee.com:calvinwilliams/mysqlda.git
calvinwilliams
mysqlda
mysqlda
master

搜索帮助