三、使用MySQL Proxy

  MySQL Proxy的使用其实是非常简单的,启动MySQL Proxy服务对应的命令行是mysql-proxy,如上节所示该命令行的参数就那么几类,常用的参数更是屈指可数。

  一般来讲,下列几个参数是必须指定的:

  • admin-username:指定管理员用户
  • admin-password:指定管理员密码
  • admin-lua-script:指定管理模块处理脚本
  • daemon:指定以守护模式运行
  • proxy-backend-addresses:指定代理实际连接的MySQL数据库

  例如,执行mysql-proxy命令并指定参数:

    [root@rhel5u3 ~]# /usr/local/mysql-proxy/bin/mysql-proxy --admin-username=jss --admin-password=123 --admin-lua-script=/usr/local/mysql-proxy/lib/mysql-proxy/lua/admin.lua -b 172.16.1.113:3306 --daemon

  而后可以通过netstat -lnt查询相应端口是否已经处于监听状态:

    [root@rhel5u3 ~]# netstat -lnt

    Active Internet connections (only servers)

    Proto Recv-Q Send-Q Local Address               Foreign Address             State      

    tcp        0      0 127.0.0.1:2208              0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:11111               0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:4040                0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:4041                0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:3306                0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:111                 0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:16851               0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:21                  0.0.0.0:*                   LISTEN      

    tcp        0      0 127.0.0.1:631               0.0.0.0:*                   LISTEN      

    tcp        0      0 0.0.0.0:666                 0.0.0.0:*                   LISTEN      

    tcp        0      0 127.0.0.1:2207              0.0.0.0:*                   LISTEN      

    tcp        0      0 :::1521                     :::*                        LISTEN      

    tcp        0      0 :::60918                    :::*                        LISTEN      

    tcp        0      0 :::22                       :::*                        LISTEN      

    tcp        0      0 ::1:631                     :::*                        LISTEN      

    [root@rhel5u3 ~]# 

  可以看到默认的管理端口4041和监听端口4040均已启动。服务启动无误,下面可以通过这台代理服务器来连接172.16.1.113:3307数据库了。

3.1 初次连接

  首先登录到目标数据库,创建相应用户并授予权限,为了能够清晰了表现mysql-proxy的工作,我们在创建用户时,指定仅允许MySQL Proxy所在服务器连接:

    [root@mysqldb2 ~]# mysql -uroot -p¨verysafe¨ -S /data/mysqldata/3306/mysql.sock

    Welcome to the MySQL monitor.  Commands end with ; or \g.

    Your MySQL connection id is 11

    Server version: 5.1.51-junsansi-edition-log Source distribution

    Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

    This software comes with ABSOLUTELY NO WARRANTY. This is free software,

    and you are welcome to modify and redistribute it under the GPL v2 license

    Type ¨help;¨ or ¨\h¨ for help. Type ¨\c¨ to clear the current input statement.

    mysql> grant all on jssdb.* to jss@¨172.16.1.110¨ identified by ¨jss¨;

    Query OK, 0 rows affected (0.00 sec)

  如上所示,我们在创建用户时仅允许来自172.16.1.110(即mysql-proxy所在服务器)的服务器连接,这个时候,我们转到一台client端执行连接,首先直接连接目标数据库服务器(由于服务器端jss用户限制了连接IP,直接连接应该是连接不上的):

    [root@mysqldb1 ~]# mysql -ujss -pjss -h 172.16.1.113 -P 3306

    ERROR 1045 (28000): Access denied for user ¨jss¨@¨172.16.1.112¨ (using password: YES)

  连接果然被拒绝,而后再尝试通过MySQL-Proxy代理连接MySQL服务器:

    [root@mysqldb1 ~]# mysql -ujss -pjss -h 172.16.1.110 -P 4040

    Welcome to the MySQL monitor.  Commands end with ; or \g.

    Your MySQL connection id is 12

    Server version: 5.1.51-junsansi-edition-log Source distribution

    Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

    This software comes with ABSOLUTELY NO WARRANTY. This is free software,

    and you are welcome to modify and redistribute it under the GPL v2 license

    Type ¨help;¨ or ¨\h¨ for help. Type ¨\c¨ to clear the current input statement.

    mysql> show databases;

    +--------------------+

    | Database           |

    +--------------------+

    | information_schema |

    | jssdb              |

    | test               |

    +--------------------+

    3 rows in set (0.00 sec) 

  顺利连接,表明mysql-proxy代理的工作正常。

3.2 管理mysql-proxy

  Mysql-proxy自己维护了一个可通过mysql命令行模式管理的操作界面,连接时按照mysql命令行格式指定用户名/密码/服务器IP,而后指定代理服务器的4040端口访问,例如:

    [root@mysqldb1 ~]# mysql -ujss -p123 -h 172.16.1.110 -P 4041

    Welcome to the MySQL monitor.  Commands end with ; or \g.

    Your MySQL connection id is 1

    Server version: 5.0.99-agent-admin

    Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.

    This software comes with ABSOLUTELY NO WARRANTY. This is free software,

    and you are welcome to modify and redistribute it under the GPL v2 license

    Type ¨help;¨ or ¨\h¨ for help. Type ¨\c¨ to clear the current input statement.

  那么这个界面是怎么来的,支持的命令有哪些,功能是什么,应该如何扩展?这些都不是问题,看一下启动mysql-proxy命令时指定的lua脚本文件即可明了,如下:

    [root@rhel5u3 ~]# more /usr/local/mysql-proxy/lib/mysql-proxy/lua/admin.lua

    ..........

    ..........

    function set_error(errmsg) 

            proxy.response = {

                    type = proxy.MYSQLD_PACKET_ERR,

                    errmsg = errmsg or "error"

            }

    end

    function read_query(packet)

            if packet:byte() ~= proxy.COM_QUERY then

                    set_error("[admin] we only handle text-based queries (COM_QUERY)")

                    return proxy.PROXY_SEND_RESULT

            end

            local query = packet:sub(2)

            local rows = { }

            local fields = { }

            if query:lower() == "select * from backends" then

                    fields = { 

                            { name = "backend_ndx", 

                              type = proxy.MYSQL_TYPE_LONG },

                            { name = "address",

                              type = proxy.MYSQL_TYPE_STRING },

                            { name = "state",

                              type = proxy.MYSQL_TYPE_STRING },

                            { name = "type",

                              type = proxy.MYSQL_TYPE_STRING },

                            { name = "uuid",

                              type = proxy.MYSQL_TYPE_STRING },

                            { name = "connected_clients", 

                              type = proxy.MYSQL_TYPE_LONG },

                    }

                    for i = 1, #proxy.global.backends do

                            local states = {

                                    "unknown",

                                    "up",

                                    "down"

                            }

                            local types = {

                                    "unknown",

                                    "rw",

                                    "ro"

                            }

                            local b = proxy.global.backends[i]

                            rows[#rows + 1] = {

                                    i,

                                    b.dst.name,          -- configured backend address

                                    states[b.state + 1], -- the C-id is pushed down starting at 0

                                    types[b.type + 1],   -- the C-id is pushed down starting at 0

                                    b.uuid,              -- the MySQL Server¨s UUID if it is managed

                                    b.connected_clients  -- currently connected clients

                            }

                    end

            elseif query:lower() == "select * from help" then

                    fields = { 

                            { name = "command", 

                              type = proxy.MYSQL_TYPE_STRING },

                            { name = "description", 

                              type = proxy.MYSQL_TYPE_STRING },

                    }

                    rows[#rows + 1] = { "SELECT * FROM help", "shows this help" }

                    rows[#rows + 1] = { "SELECT * FROM backends", "lists the backends and their state" }

            else

                    set_error("use ¨SELECT * FROM help¨ to see the supported commands")

                    return proxy.PROXY_SEND_RESULT

            end

            proxy.response = {

                    type = proxy.MYSQLD_PACKET_OK,

                    resultset = {

                            fields = fields,

                            rows = rows

                    }

            }

            return proxy.PROXY_SEND_RESULT

    end

   这个脚本是mysql-proxy自带文件,功能非常简单,语法对于初接触者会感到有些生疏,但逻辑结构还是比较容易理解的,从上述的这段脚本可以看出,当前仅支持select * from help/backends两条语句,其中select * from help会返回两条记录,显示支持的语句及简述,select * from backends则显示提供服务的后台数据库服务器列表,及各服务器的状态等信息。

3.3 lua脚本的应用

  MySQL Proxy主要基于lua脚本来扩展代理服务器的功能,并且从设定上就将lua分成两类:

  • 一类负责管理模块的控制,对应参数admin-lua-script,我们上小节看到的脚本就属于这类。
  • 另一类负责代理模块的控制,对应参数proxy-lua-script。

  两类脚本的编码规则完全相同,只是对应功能有差异,管理模块侧重于代理服务器相关状态的控制,代理模块则侧重于客户端的CRUD操作。

MySQL Proxy支持通过LUA脚本定制下列几个函数,来处理客户端与mysql数据库之间的交互:

  • connect_server():当客户端发起连接请求到mysql代理时,就会调用该函数,DBA可以定制该函数实现负载均衡,以及更复制的客户端-服务器的连接条件。
  • read_handshake():当服务器返回握手(handshake)信息时调用该函数,DBA可以定制该函数提供附加返回信息,或者在验证用户身份前首先执行自定义的其它检查。
  • read_auth():当客户端提交认证包(含用户名、密码、默认数据库)到服务器时调用该函数。
  • read_auth_result():当服务器返回认证包给客户端时会调用该函数。
  • read_query():当客户端发起查询请求到mysql服务器时会调用该函数,DBA可以通过定制该函数以修改和维护客户端要执行的查询,甚至跳过mysql数据库直接向客户端返回结果。
  • read_query_result():当mysql服务器返回查询结果到客户端时调用该函数,DBA可以通过定制该函数,修改或过滤结果集。

  我感觉非常重要的一点在于,这几个函数是用来控制客户端与mysql服务器之间的交互过程,如果不设定完全可以,则按照默认逻辑执行查询或返回记录到在客户端与mysql服务器之间,而通过这些函数的定制,则基本可以比较随意的控制查询结果,如前面所说,甚至可以不发查询语句到mysql服务器,而直接返回记录给客户端。

  Mysql-proxy代理在客户端与mysql服务器之间的交互控制逻辑图如下:

  更多关于lua脚本方面的内容参考lua官网:http://www.lua.org,mysql-proxy处理脚本的内部结构与脚本调用示例可参考mysql官网:http://dev.mysql.com/doc/refman/5.1/en/mysql-proxy-scripting.html。

3.4 测试性能

  Mysql的性能测试范围太广,这里仅是使用mysql自带的mysqlslap命令行工具进行简单测试,测试流程为使用mysqlslap命令指定相同参数,分别连接代理和mysql服务器,对比响应时间。

  测试前要先创建测试用户,连接到目标数据库执行下列语句。

    mysql> grant all on testdb.* to test@¨172.16.1.%¨ identified by ¨test¨;

    Query OK, 0 rows affected (0.00 sec)

  接下来就可以执行测试了,先是直接连接mysql服务器,执行命令如下:

    [root@mysqldb1 ~]# mysqlslap -utest -ptest -h 172.16.1.113 -P 3306 --create-schema=testdb -x 2 -y 3 -a -c 20 -i 50

    Benchmark

            Average number of seconds to run all queries: 0.424 seconds

            Minimum number of seconds to run all queries: 0.354 seconds

            Maximum number of seconds to run all queries: 0.605 seconds

            Number of clients running queries: 20

            Average number of queries per client: 0

  上述命令执行时将模拟20个用户的并发连接,模拟创建5列(2列字符型,3列数值型)的表,每个用户各执行指定的语句50次,执行的语句由mysqlslap自动生成。

  几个生疏参数的简要说明:

  • --create-schema:指定运行测试的schema,建议单独指定一个,不要与现有业务库混淆。
  • -x:--number-char-cols的简写形式,与-a配合使用,用来指定测试语句中含有的varchar类型列的数量。
  • -y:--number-int-cols的简写形式,与-a配合使用,用来指定测试语句中含有的int类型列的数量
  • -a:--auto-generate-sql的简写形式,当没有通过命令行选项或附加文件指定要执行的操作时,就自动生成SQL语句。与此相关的还有其它一些参数,可用来设定执行语句的查询数量,写入条件,执行次数等等,详见官方文档mysqlslap命令小节。
  • -c:--concurrency的简写形式,指定模拟的并发用户数。
  • -i:--iterations的简写形式,指定执行测试语句的次数。

  而后再尝试通过mysql代理连接mysql服务器,执行命令如下:

    [root@mysqldb1 ~]# mysqlslap -utest -ptest -h 172.16.1.110 -P 4040 --create-schema=testdb -x 2 -y 3 -a -c 20 -i 50

    Benchmark

            Average number of seconds to run all queries: 0.428 seconds

            Minimum number of seconds to run all queries: 0.344 seconds

            Maximum number of seconds to run all queries: 1.385 seconds

            Number of clients running queries: 20

            Average number of queries per client: 0

  首先必须要强调一条,因为mysqlslap测试的环节较有为限,因此上述测试结果并不严谨,结果仅供参考。从有限的结果可以看出,应用代理后,一般的处理操作会受到些许影响,从前后数值对比来看,影响还是比较轻微。

  不过考虑到官方文档开篇的那段话:MySQL Proxy is currently an Alpha release and should not be used within production environments. 因此三思建议在正式应用Mysql Proxy前还是需要做更严谨和完善的测试,以确保整个系统性能的高效和稳定。