综述

要实现一个mysql proxy,首先需要做的就是理解并实现mysql通讯协议。这样才能通过proxy架起client到server之间的桥梁。

mixer的mysql协议实现主要参考mysql官方的internal manual,并用Wireshark同时进行验证。在实现的过程中,当然踩了很多坑,这里记录一下,算是对协议分析的一个总结。

需要注意的是,mixer并没有支持所有的mysql协议,譬如备份,存储过程等,主要在于精力有限,同时也为了实现简单。

数据类型

mysql协议只有两种基本的数据类型,integer和string。

integer

integer包括fixed length integer和length encoded integer两种,对于length encoded integer,用的地方比较多,这里详细说明一下。

对于一个integer,我们按照如下的方式将其转成length encoded integer:

  • 如果value < 251,使用1 byte
  • 如果value >= 251 同时 value < 2 ** 16,使用fc + 2 byte
  • 如果value >= 2 ** 16 同时 value < 2 ** 24,使用fd + 3 byte
  • 如果value >= 2 ** 24 同时 value < 2 ** 64,使用fe + 8 byte

相应的,对于一个length encoded integer,我们可以通过判断第一个byte的值来转成相应的integer。

string

string包括:

  • fixed length string,固定长度string
  • null terminated string,以null结尾的string
  • variable length string,通过另一个值决定长度的string
  • length encoded string,通过起始length encoded integer决定长度的string
  • rest of packet string,从当前位置到包结尾的string

Packet

在mysql中,如果client或server要发送数据,它需要将数据按照(2 ** 24 - 1)拆分成packet,给每一个packet添加header,然后再以此发送。

对于一个packet,格式如下:

3              payload length
1 sequence id
string[len] payload

前面3个字节表明的是该packet的长度,每个packet最大不超过16MB。第4个字节表明的是该packet的序列号,从0开始,对于多个packet依次递增,等到下一个新的命令发送数据的时候才重置为0。前面4个字节组成了一个packet的header,后面就是该packet实际的数据。

因为一个packet最大能发送的数据位16MB,所以如果需要发送大于16MB的数据,就需要拆分成多个packet进行发送。

通常,server会回给client三种类型的packet

  • OK Packet,操作成功
  • Err Packet,操作失败
  • EOF Packet,end of file

登陆交互

要实现proxy,首先需要解决的就是登陆问题,包括proxy模拟server处理client的登陆,proxy模拟client登陆server。

为了简单,mixer只支持username + password的方式进行登陆,这应该也是最通用的登陆方式。同时不支持ssl以及compression。

一个完整的登陆流程如下:

  • client首先connect到server
  • server发送initial handshake packet,包括支持的capability,一个用于加密的随机salt等
  • client返回handshake结果,包括自己支持的capability,以及用salt加密的密码
  • server验证,如果成功,返回ok packet,否则返回err packet并关闭连接

这里,不得不说实现登陆协议的时候踩过的一个很大的坑,因为我使用的是HandshakeV10协议,在文档里面,协议有这样的规定:

if capabilities & CLIENT_SECURE_CONNECTION {
string[$len] auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
}

如果根据文档的说明,算出来auth-plugin-data-part-2的长度是13,因为auth-plugin-data的长度是20。但是,实际情况是,auth-plugin-data-part-2的长度应该为12,第13位一直为0。只有这样,我们才能根据salt算出正确的加密密码。这一点,在mysql-proxy官方的文档,以及多个msyql client driver上面,Wireshark的分析中都是如此,在go-sql-driver中,作者都直接写了如下的注释:

// second part of the password cipher [12? bytes]
// The documentation is ambiguous about the length.
// The official Python library uses the fixed length 12
// which is not documented but seems to work.

可想而知,这个坑有多坑爹。至少我开始是栽在上面了。加密老是不对。

Command

搞定了登陆,剩下的就是mysql的命令支持,mixer只实现了基本的命令。主要集中在text protocol以及prepared statment里面。

COM_PING

最基本的ping实现,用来检查mysql是否存活。

COM_INIT_DB

虽然叫init db,其实压根干的事情就跟use db一样,用来切换使用db的。

COM_QUERY

可以算是最重要的一个命令,我们在命令行使用的多数mysql语句,都是通过该命令发送的。

在COM_QUERY中,mixer主要支持了select,update,insert,delete,replace等基本的操作语句,同时支持begin,commit,rollback事物操作,还支持set names和set autocommit。

COM_QUERY有4中返回packet

  • OK Packet
  • Err Packet
  • Local In File(不支持)
  • Text Resultset

这里重点说明一下text resultset,因为它包含的就是我们最常用的select的结果集。

一个text resultset,包括如下几个包:

  • 一个以length encoded integer编码的column-count packet
  • column-count个column定义packet
  • eof packet
  • 一个或者多个row packet,每个row packet有column-count个数据
  • eof packet或者err packet

对于一个row packet的里面的数据,我们通过如下方式获取:

  • 如果值为NULL,那么就是0xfb
  • 否则,任何值都是用length encoded string表示

COM_STMT_*

COM_STMT_族协议就是通常的prepared statement,当我在atlas群里面说支持prepared statement的时候,很多人以为我支持的是在COM_QUERY中使用的prepare,execute和deallocate prepare这组语句。其实这两个还是很有区别的。

为什么我不现在不想支持COM_QUERY的prepare,主要在于这种prepare需要进行变量设置,mixer在后端跟server是维护的一个连接池,所以对于client设置的变量,proxy维护起来特别麻烦,并且每次跟server使用新的连接的时候,还需要将所有的变量重设,这增大了复杂度。所以我不支持变量的设置,这点看cobar也是如此。既然不支持变量,所以COM_QUERY的prepare我也不会支持了。

COM_STMT_*这组命令,主要用在各个语言的client driver中,所以我觉得只支持这种的prepare就够了。

对于COM_STMT_EXECUTE的返回结果,因为prepare的语句可能是select,所以会返回binary resultset,binary resultset组成跟前面text resultset差不多,唯一需要注意的就是row packet采用的是binary row packet。

对于每一个binary row packet,第一个byte为0,后面紧跟着一个null bitmap,然后才是实际的数据。

在binary row packet中,使用null bitmap来表明该行某一列的数据为NULL。null bitmap长度通过 (column-count + 7 + 2) / 8计算得到,而对于每列数据,如果为NULL,那么它在null bitmap中的位置通过如下方式计算:

NULL-bitmap-byte = ((field-pos + offset) / 8)
NULL-bitmap-bit = ((field-pos + offset) % 8)

offset在binary resultset中为2,field-pos为该列的位置。

对于实际非NULL数据,则是根据每列定义的数据类型来获取,譬如如果type为MYSQL_TYPE_LONGLONG,那么该数据值的长度就是8字节,如果type为MYSQL_TYPE_STRING,那么该数据值就是一个length encoded string。

后记

我通过Wireshark分析了一些mysql protocol,主要在这里,这里不得不强烈推荐wireshark,它让我在学习mysql protocol过程中事半功倍。

mixer的代码在这里https://github.com/siddontang/mixer,欢迎反馈。

mixer: mysql协议分析的更多相关文章

  1. MySQL协议分析

    MySQL协议分析 标签: mysql 2015-02-27 10:22 1807人阅读 评论(1) 收藏 举报  分类: 数据库(19)    目录(?)[+]   1 交互过程 MySQL客户端与 ...

  2. MySQL协议分析2

    MySQL协议分析 议程 协议头 协议类型 网络协议相关函数 NET缓冲 VIO缓冲 MySQL API 协议头 ● 数据变成在网络里传输的数据,需要额外的在头部添加4 个字节的包头. . packe ...

  3. MySQL协议分析(2)

    MySQL协议分析(2) 此阶段是在压缩传输无加密条件下进行的协议分析 思路 结合Oracle官网的说明和自己用wireshark加python进行数据包分析 步骤 客户端与服务器端是否压缩的协商阶段 ...

  4. MySQL协议分析(1)

    MySQL协议分析 此阶段的协议分析是在未压缩未加密情况下的协议分析 思路: 结合Oracle官网和自己用wireshark抓的网络数据包进行协议分析 官网说明 mysql包共分为4段,格式如下: 第 ...

  5. mysql 协议分析

    MYSQL Binlog协议分析 此处不讨论建立连接,验证和handshake的交互协议 Binlog协议 一个MYSQL 通信包由包头包体组成 包体根据具体的交互协议有自身的组成结构, 在binlo ...

  6. mysql协议分析2---认证包

    主人看到navicat和mysql在那嘻嘻哈哈,眉来眼去的,好不快乐,忽然也想自己写个程序,直接去访问Mysql,虽然现在已经有很多现成的中间件可以直接拿来用了,程序只要负责写sql语句就行了,但是主 ...

  7. mysql协议分析1---报文的格式和基本类型

    navicat 和 mysql 是一对好基友,每天都有非常频繁的交流,主人在navicat上写下每条sql语句,轻轻的点了下执行按钮,navicat就飞快的把主人的指令传送到mysql那里,mysql ...

  8. mysql协议简析

    前言 如果要在命令行中连接mysql,最常用的便是 mysql -u root -p 这样指定用户名和密码 当然还可以使用远程连接 mysql -h 127.0.0.1 -u root -p 还有一种 ...

  9. Memcache的使用和协议分析详解

    Memcache的使用和协议分析详解 作者:heiyeluren博客:http://blog.csdn.NET/heiyeshuwu时间:2006-11-12关键字:PHP Memcache Linu ...

随机推荐

  1. 73. Set Matrix Zeroes(中等)

    Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place. 重点是空间复 ...

  2. Jupyter Notebook 快速入门

    Jupyter Notebook(此前被称为 IPython notebook)是一个交互式笔记本,支持运行 40 多种编程语言.在本文中,我们将介绍 Jupyter notebook 的主要特性,以 ...

  3. MySQL PHP 语法

    MySQL PHP 语法 MySQL 可应用于多种语言,包括 PERL, C, C++, JAVA 和 PHP. 在这些语言中,MySQL在PHP的web开发中是应用最广泛. 在本教程中我们大部分实例 ...

  4. MongoDB 索引限制

    额外开销 每个索引占据一定的存储空间,在进行插入,更新和删除操作时也需要对索引进行操作.所以,如果你很少对集合进行读取操作,建议不使用索引. 内存(RAM)使用 由于索引是存储在内存(RAM)中,你应 ...

  5. 学习笔记:Zookeeper选举机制

    1.Zookeeper选举机制 Zookeeper虽然在配置文件中并没有指定master和slave 但是,zookeeper工作时,是有一个节点为leader,其他则为follower Leader ...

  6. 编写高性能的Lua代码

    编写高性能的Lua代码 Posted on2014/04/18· 10 Comments 前言 Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面,尤其是游戏.像<魔兽世界>的插件, ...

  7. Bootstrap3 栅格系统-Less mixin 和变量

    除了用于快速布局的预定义栅格类,Bootstrap 还包含了一组 Less 变量和 mixin 用于帮你生成简单.语义化的布局. 变量 通过变量来定义列数.槽(gutter)宽.媒体查询阈值(用于确定 ...

  8. 数据库查询优化——Mysql索引

    工作一年了,也是第一次使用Mysql的索引.添加了索引之后的速度的提升,让我惊叹不已.隔壁的老员工看到我的大惊小怪,平淡地回了一句"那肯定啊". 对于任何DBMS,索引都是进行优化 ...

  9. MT8127:改变安卓系统权限问题

    找到对应的文件: system/extras/su/su.c 在main函数中,开头我们可以看下以下代码: uid_t current_uid = getuid(); if (current_uid ...

  10. syslog(),closelog()与openlog()--日志操作函数

    在典型的 LINUX 安装中,/var/log/messages 包含所有的系统消息,/var/log/mail 包含来自邮件系统的其它日志消息,/var/log/debug 可能包含调试消息.根据你 ...