内容纲要

mysql-proxy 是 mysql 官方提供的 mysql 中间件服务,上游可接入若干个 mysql-client,后端可连接若干个 mysql-server,它使用 mysql 协议,任何连接 mysql 的上游无需任何更改即可迁移至 mysql-proxy 上。

mysql-proxy 最基本的用法,就是作为一个请求拦截,请求中转的中间层。

进一步的,mysql-proxy 可以分析与修改请求。拦截查询和修改结果,需要通过编写 Lua 脚本来完成。mysql-proxy 允许用户指定 Lua 脚本对请求进行拦截,对请求进行分析与修改,它还允许用户指定 Lua 脚本对服务器的返回结果进行修改,加入一些结果集或者去除一些结果集均可。

SHENJIAN@58.COM 画外音:SQL 拦截与修改,性能分析与监控,读写分离,请求路由等各种功能都是通过编写 LUA 脚本来完成的,MYSQL-PROXY 是个框架,具备很好的扩展性。这个框架提供了 6 个 HOOK 点,能够让用户能够动态的介入到 CLIENT 与 SERVER 中的通讯中去。

需要注意:
(1)mysql-proxy 目前仍处于 Alpha 版本,没有 release;
(2)mysql-proxy 只支持 mysql5.0 及以后的服务端;

mysql-proxy 命令参数

版本显示
使用–version 参数即可:
./mysql-proxy –version
从输出可以看到 mysql-proxy 的版本,以及依赖的 glib,libevent,lua 的版本
1 mysql-proxy 0.8.3
2 chassis: mysql-proxy 0.8.3
3 glib2: 2.16.6
4 libevent: 1.4.13-stable
5 LUA: Lua 5.1.4
8 — modules
9 proxy: 0.8.3

简单启动
mysql-proxy 启动至少需要指定一个后端 mysql 的 ip 和端口号,此时带上–proxy-backend-addresses 参数即可:
./mysql-proxy –proxy-backend-addresses=127.0.0.1:3306

指定配置文件启动
mysql-proxy 亦可以指定配置文件启动,此时带上–defaults-file 来指定配置文件即可:
./mysql-proxy –defaults-file=./mysql-proxy.cnf
mysql-proxy.cnf 的格式如下:
1 [mysql-proxy]
2 proxy-backend-addresses = 127.0.0.1:3306
需要注意,在命令行中参数前需要加入 “–”,而在配置文件中则不需要。

寻求帮助
可以使用–help,或者–help-all
./mysql-proxy –help
./mysql-proxy –help-all

Proxy 常用选项
选项 说明
proxy-backend-addresses=$host:$port 后端 mysql 的 ip 和 port,多个以逗号分隔
proxy-read-only-backend-addresses=$host:$port 后端只读 mysql 的 ip 和 port,多个以逗号分隔
proxy-skip-profiling=$bool 是否禁用查询性能剖析
proxy-lua-script=$file_name lua 文件

注意:
如果设置了多个后端 mysql,负载均衡策略为 round-robin。例如设了 A 和 B 两台后端,第一个请求转发到 A,第二个请求转发到 B,第三个请求转发到 A,以此类推。

Proxy 服务常用选项
选项 说明
proxy-address=$host:$port mysql-proxy 的 ip 和 port,默认 port 是 4040
daemon=$bool 是否使用 daemon 模式启动
event-threads=$count event-handing 线程数,默认值是 1
keep-alive=$bool proxy 服务崩溃后自动重启
log-file=$file_name 日志文件
log-level=$level 日志级别:error|warning|info|message|debug
log-use-syslog=$bool 日志使用 syslog
max-open-files=$count 最大文件句柄数
pid-file=$file_name pid 文件

典型配置文件示例
1 [mysql-proxy]
2
3 proxy-address = 127.0.0.1:4040
4 daemon = true
5 event-threads = 2
6 keep-alive = true
7 log-file = ./mysql-proxy.log
8 log-level = debug
9 max-open-files = 1024
10 pid-file = ./mysql-proxy.pid
11
12 proxy-lua-script = ./ro-balance.lua
13 proxy-backend-addresses = 127.0.0.1:3306
14 proxy-skip-profiling = false

启动过程中提示:
2013-12-28 15:15:49: (critical) mysql-proxy-cli.c:326: loading config from ‘./mysql-proxy.cnf’ failed: permissions of ./mysql-proxy.cnf aren’t secure (0660 or stricter required)
由于安全要求,必须将配置文件权限设为 660(创建人可读写,同组人可读),否则不允许启动。

 

mysql-proxy 脚本编程

 

如 “简介” 中所述,mysql-proxy 向用户提供了 6 个 hook 点,让用户实现 Lua 脚本来完成各种功能,这些 hook 点是以函数的形式提供的,用户可以实现这些函数,在不同事件、不同操作发生时,做我们期望的事情。
connect_server()
mysql-client 向 proxy 发起连接时,proxy 会调用这个函数。用户可以实现该函数,来做一些负载均衡的事情,例如选择将要连向那个 mysql-server。假设有多个 mysql-server 后端,而用户又没有实现这个函数,proxy 默认采用轮询(round-robin)策略。
read_handshake()
mysql-server 向 proxy 返回 “初始握手信息” 时,proxy 会调用这个函数。用户可以实现这个函数,来做更多的权限验证工作。
read_auth()
mysql-client 向 proxy 发送认证报文(user_name, password, database)时,proxy 会调用这个函数。
read_auth_result()
mysql-server 向 proxy 返回认证结果时,proxy 会调用这个函数。
read_query()
认证完成后,mysql-client 每次经过 proxy 向 mysql-server 发送 query 报文时,proxy 会调用这个函数。用户如果要拦截请求,就可以模拟 mysql-server 直接返回了,当然用户亦可以实现各种策略,修改请求,路由请求等各种不同的业务逻辑。
read_query_result()
认证完成后,mysql-server 每次经过 proxy 向 mysql-client 返回 query 结果时,proxy 会调用这个函数。需要注意,如果用户没有显示实现 read_query() 函数,则 read_query_result() 函数是不会被调用的。用户可以在此处实现各种合并策略,或者对结果集进行修改。

SHENJIAN@58.COM 画外音:下图是一个各 HOOK 函数的触发图(请注意请求方向):

继续 画外音:可以发现,最重要的两个函数其实是 READ_QUERY() 和 READ_QUERY_RESULT(),各种 SQL 的改写与结果集的改写逻辑,都是在这两个函数中实现的,更细节的 QUERY 过程如下图:

案例一: sql 时间统计分析
不妨设 mysql-client 提交的原 sql 为:SELECT * FROM City;
proxy 可以在 read_query() 里将其改写为:SELECT NOW(); SELECT * FROM City; SELECT NOW();
这样在返回结果集时,就可以在应用层对 sql 时间进行记录,以方便统计分析。

案例二:sql 性能统计分析
不妨设 mysql-client 提交的原 sql 为:SELECT * FROM City;
proxy 可以在 read_query() 里将其改写为: SELECT * FROM City; EXPLAIN SELECT * FROM City;
这样在返回结果集时,就可以在应用层对 sql 性能进行记录,以方便统计分析。

需要强调的是,这两个案例,由于 proxy 在 read_query() 时对 sql 进行了改写,故在 read_query_result() 时,mysql-server 其实返回了比原请求更多的信息,proxy 一定要将多余的信息去掉,再返回 mysql-client。多说一句,可以加入一个唯一 ID,来对请求 sql 和返回结果进行配对。

demo
需求:在业务层统计 sql 日志
实现:tutorial-basic.lua
1 — 如果是 COM_QUERY,就将内容打印出来
2 function read_query(packet)
3 if string.byte(packet) == proxy.COM_QUERY then
4 print(“we got a normal query:” .. string.sub(packet, 2))
5 end
6 end

修改配置并重启 proxy:
proxy-lua-script = tutorial-basic.lua

客户端使用黑黑的窗口连接 4040 端口的 proxy,并进行一系列 sql 操作,操作序列如下:
mysql -h127.0.0.1 -uroot -P4040
show databases;
use im;
show tables;
select * from user;
quit

通过 tutorial-basic.lua,会将上述操作都记录到日志中,日志序列如下:
we got a normal query: select @@version_comment limit 1
we got a normal query: show databases
we got a normal query: SELECT DATABASE()
we got a normal query: show tables
we got a normal query: select * from user
SHENJIAN@58.COM 画外音:咦,通过这个日志我才知道,连上数据库会默认发一个 SELECT @@VERSION_COMMENT LIMIT 1 的请求呢。USE IM; 这个请求,为啥变成 SELECT DATABASE() 了呢?

 

mysql-proxy 常见问题 FAQ

 

(1)如何实现最简单的读写分离?
shell> mysql-proxy \
–proxy-backend-addresses=10.0.1.2:3306 \
–proxy-read-only-backend-addresses=10.0.1.3:3306

(2)mysql proxy 支持所有版本的 mysql 么?
只支持 mysql5.0 + 的 mysql 协议。

(3)如果开启负载均衡,那事务怎么办?所有的 query 会发往同一台 mysql 么?
如果用户不专门定制 Lua 脚本,会发往同一台 mysql,以保证其完整性。

(4)系统上下文切换代价大么?Lua 脚本引入的额外开销有多大?
Lua 很快,对于大部分应用来说,额外开销很小,原始包(raw packet)开销大概在 400 微秒左右。
SHENJIAN@58.COM 画外音:这,,,

(5)Lua 脚本可以动态加载么?
升级了 Lua 脚本,连接建立后才会读取新的哟。

(6)如果 proxy 和 mysql 部署在一台机器上,有什么需要建议的呢?
proxy 单独部署也可以,和 mysql 部署在同一台机器上也可以。相比 mysql 而言,proxy 不怎么占 CPU 和内存,其性能损耗可以忽略不计。
SHENJIAN@58.COM 画外音:这,,,

(7)Lua 脚本是预读到内存里的吧?还是说每次都要到文件系统里读?
客户端连接过来时,或者脚本更新时会读取,其他的时候都读内存哟。

(8)加入客户端连上来,出发了 connect_server() 函数,Lua 脚本能连接多个 mysql 么?
可以,使用指引里有样例代码哟。

(9)proxy 可以处理 SSL 连接么?
不可以,作为中间人,不能处理加密信息。

(10)proxy 不会获取和保存我的明文密码吧?
不会,也获取不到。mysql 协议不允许密码以明文传输,传输的都是加密后的密文。

(11)有隔离问题,调试问题的工具么?如果请求出错了,我怎么知道错误发生在 mysql 客户端,还是 mysql 服务端,还是 proxy 呢?
你可以自己在 proxy 里设置 debug 脚本进行调试,例如设置断点什么的。

(12)瞧你官网吹的,有哪个大网站用了 mysql-proxy 么?请求量是什么级别?
这问题问的,作为官网我,,,盖亚在线(http://www.gaiaonline.com/) 就是用的 mysql-proxy 哟,qps 可以到 2400。
SHENJIAN@58.COM 画外音:这是个外国的游戏网站,页面好丑啊。

(13)如果我在 Lua 脚本用使用 LuaSocket 可以么?
大哥,这可能引起阻塞的,强烈不建议这样。

发表评论

电子邮件地址不会被公开。 必填项已用*标注