1. 名词解释
amoeba:amoeba是盛大的一位高级研究员编写的一个通用数据存储代理框架,它是一个基于Java编写的位于db服务器与客户端之间的代理,它可以用来监控,分析,传输db客户端与服务器端的通信信息。它实现了负载均衡,失败重连,读写分离等功能。
amoeba for mysql:amoeba框架中的一个子产品,专门提供支持mysql server与client间的代理服务,支持mysql4.1以上版本。简单的说amoeba for mysql就是Client连接Mysql Server的proxy。
hunter:hunter是寄生于amoeba中的一个扩展,通过它,能截取所有流经amoeba代理层的客户端及服务器端的命令消息,除此之外,它还能修改客户端的命令,服务器端的返回结果,甚至独立的与服务器或者客户端进行通信。
2. 简介
2.1. Mysql Hunter背景
自动化测试实施的过程中,我们通常都面临一个棘手的问题:数据的准备和恢复。即在成功执行一个自动化用例时,我们可能需要一定的数据前提,而为了使得整个前提不至于被其他的用例破坏,以至于我们有时不得不在自动化用例编写过程中手工的添加许多Insert、Update、Delete语句来保证数据被恢复到我们执行用例前的状态。
LAMP是各公司网站流行的一种开发模式,而使用phpunit等工具框架进行自动化单元测试的过程中也面临数据准备和恢复的问题。单元测试开发人员开发过程中不得不编码中显示编写数据准备,验证,恢复的代码,这样会带来以下几个缺点:
1. 用例阅读性降低,难于辨别主要的测试逻辑
2. 用例可维护性降低,当数据库的表结构略微有变化或者业务底层逻辑有变动,数据的准备和恢复的代码都将有可能面临修改甚至重写
3. 极大消耗开发人员开发成本,用例编写过程中不得不额外花很多的时间去思考数据的准备和恢复
基于以上三点,考虑设计实现一个mysql server及client的中间层,通过此中间层,可以将自动化Case编写人员的数据恢复操作透明化,且在数据环境稳定的前提下,适度的降低开发人员的数据准备工作。当然,我们希望这个中间层不限于此功能。
2.2. 为什么不是Mysql Proxy
通过上面的介绍读者可能会发现官方版本mysql proxy似乎也能实现代理和扩展的功能,但是我们为什么不使用mysql proxy呢,原因如下:
1. mysql proxy的读写分离等功能是使用lua脚本编写,lua不常用,有一定学习成本,阅读维护困难
2. 自定义开发和升级困难
2.3. Amoeba和Hunter的角色
Amoeba for mysql位于Application与数据库之间,为应用提供透明的代理服务。
Amoeba对外接口采用 mysql protocol,没有定义自己的协议和接口,使得任何mysql的应用都能方便的使用Amoeba,诸如php、java、c等等的 cennector。
Amoeba本身没有Build 任何 SQL语句,而是直接接收client的SQL、命令等进行代理转发,Amoeba采用 SQL、命令等 mysql protocol规范作为统一的接口,完备性得到了保证,能够完成mysql的所有功能操作。
3. 功能介绍
3.1. Amoeba for mysql
Mysql-hunter是基于amoeba for mysql做的mysql代理扩展,首先了解amoeba for mysql,如图:
amoeba for mysql本身实现的功能包括:
1. 读写/分离
2. 失败重连
3. 负载均衡
4. 连接池
5. 身份认证
3.2. Mysql hunter扩展
Mysql-Hunter‘寄生’于Amoeba,流经Amoeba的query也都将流经Mysql Hunter,而它的功能主要在于SQL Query or SQL Result的inject、rewrite以及reverse,如图:
从上图可以看出,mysql hunter可以实现如下功能:
1. Client连接server的阶段进行自定义命令发送或返回
2. Client发送query cmd至server的阶段,进行query截取,修改
3. 同理,Server返回给Client的结果集也能被hunter监控,修改甚至重写
Mysql Hunter对于它不感兴趣的query将直接返回给Amoeba继续处理。
3.3. 基本扩展BasicMysqlHunter
BasicMysqlHunter是一个基本且实用的扩展实现,继承自Hunter类(参考系统设计自定义扩展部分),实现了Hunter的抽象方法,可以在client发送cmd至server前对query进行截获处理,也可以对server返回的结果集进行修改。 BasicMysqlHunter目前定位于对DML query进行如下操作:
1. 分析识别
2. 逆向语句及序列生成
3. 逆向语句的回放,即显示命令恢复数据库数据
举例说明
Client要通过mysql hunter向server发送一条query:
INSERT INTO tblLemma SET title = ‘北京’, content = ‘我爱北京天安门’;
开始Mysql Hunter的情况下,整个流程将这么进行:
通过顺序执行stack中的语句,数据库中的数据就将恢复到insert命令之前的状态。
4. 使用示例
BasicMysqlHunter目前提供三条自定制命令:
1. select start:开始启用扩展,扩展将开始监控select start执行之后流经Hunter的DML命令并求逆;Hunter直接返回Ok packet
2. select print_stack:打印当前Hunter上下文中的逆命令堆栈
3. select end:结束扩展监控,并将Hunter上下文中的逆向命令栈依顺序pop出栈并发送Server恢复DB数据;Hunter直接返回Ok packet
以PHP代码为例:
其他语言的mysql connector同理。
5. 系统总体设计
5.1. 设计思路
5.1.1. 自定义扩展
Amoeba for mysql提供了监控、截取client与server间通信数据的条件,测试开发人员可以根据自己的需求充分利用该条件。为了让需求更灵活的实现,考虑将扩展设计得更灵活,以便将来其他需求到来时可以快速的实现。我们把client与server端的交互功能抽象为一个抽象类——Hunter:
那么,这里所说的自定义扩展是指具体实现Hunter抽象类的子类可以是任意的,但必须实现Hunter的两个基本抽象方法:beforeSendQuery、afterReceiveResult。同时Hunter还需提供更多的接口:afterSendQuery、 beforeReceiveResult、afterConnected,分别代表不同时间点待实现的方法。
在实例化过程中根据配置项进行具体的Hunter子类的加载,从而实现灵活的自定义扩展。
5.1.2. 并发问题考虑
如果有多个Client同时连接mysql hunter并发送DML命令,系统改如何处理?
目前mysql hunter采用锁机制来处理并发,即Client A,Client B同时分别发送一条query到hunter的时候,先到达的连接将获得一个可重入的锁,即当前连接同一线程可以获得锁,而在此锁未释放前其他连接到来无法获得该锁
5.1.3. 结果集解析及拼装
Amoeba本身未封装mysql协议中结果集的解析存储,以及字符串至二进制结果集的转换,需要扩展本身提供工具类进行解析的封装。
解决办法,依照mysql10协议,获知每个字节的含义,然后解析生成结果集对象。
5.2. 扩展包结构
Mysql Hunter扩展包结构
扩展子包说明
datatype
query结果集(ResultSet)对应字段及属性的定义,用于封装并存储ResultSet解析后的记录
handler
HunterRouter的抽象成员,BasicHandler(包括Insert、Delete、Update)目前是它的实现,负责DML语句求逆处理流程
utils
通用工具类,如结果集的处理,字符串的处理等
factory
Hunter子类的工厂类,可以根据配置文件对具体扩展类的定义实例化hunter子类
state
定义hunter扩展类的状态及相应状态下query的处理调度
其他
Context是一个单例,它负责记录Hunter的状态,命令栈,与client、server的连接等等全局对象,供Hunter各阶段注入方法set、get。
HuntRouter是一个“路由器”,解析query并将其分发至具体负责处理的Handler。
6. 小结
目前在项目中已经有所使用mysql hunter作为代理,它带来了以下收益:保证了case在执行后能自动恢复到case执行前(对于case代码透明);case可重复执行;尽可能的避免了case之间的数据依赖等影响。
基于Hunter的这样一种架构,通过实现自定义扩展,将来还可能带来:
自定义Mysql桩(模拟、修改结果集)
实时监控Query
性能测试辅助,集成query性能瓶颈分析反馈
研发人员单元自测辅助
当然,Mysql Hunter在目前的版本中还有一些功能尚未支持:
DML语句的多表操作,例如一个query中同时修改update表A和表B的字段,暂时没有实现逆语句生成,仅能转发
没有实现UPDATE语句中包含limit的语句的逆语句生成,仅能转发
暂不支持改变表结构和用户属性权限的语句,ALTER TABLE、TRUNCATE TABLE、CREATE TABLE、DROP TABLE、GRANT、ALTER USER等,仅能转发
不支持非DML语句,仅能转发
暂不支持DML语句中的Mysql内置函数,如now()(正在开发中)
后续的升级会逐步考虑实现上述的限制功能,因为毕竟这些限制并不常出现在提测代码中,低概率的丢失这部分query的逆语句不会对case造成大的影响,并且知道了这些限制我们也可以在代码里自行做一些处理。
【本文转自百度测试技术空间】http://hi.baidu.com/baiduqa/blog/item/badb09ceb202fa1d01e92896.html