友情提示:非原文链接可能会影响您的阅读体验,欢迎查看原文。(http://blog.geekcome.com)
在上一篇中介绍了Cobar和client初次建立连接的过程,Cobar监听端口,client发起连接请求,Cobar发送握手数据包,client发送认证数据包最后依据认证的结果Cobar向client发送认证结果。
在认证成功后Cobar会将该连接的回调处理函数由FrontendAuthenticator(前端认证处理器)设置成FrontendCommandHanler(前端命令处理器)。
所以在client再次向Cobar发送请求报文的时候,前端命令处理器会处理该连接。以下详细分析一下简单select语句的运行过程。
1、事件的产生
NIOReactor的R线程一直在监听selector上的每一个连接的感兴趣事件是否发生,当client发送了一条select * from tb1,select函数会返回,然后获取到该连接SelectionKey,而且该SelectKey的兴趣事件是OP_READ。此时会调用read(NIOConnection)函数。
02 |
final Selector selector = this .selector; |
06 |
int res = selector.select(); |
07 |
LOGGER.debug(reactCount + ">>NIOReactor接受连接数:" + res); |
09 |
Set<SelectionKey> keys = selector.selectedKeys(); |
11 |
for (SelectionKey key : keys) { |
12 |
Object att = key.attachment(); |
13 |
if (att != null && key.isValid()) { |
14 |
int readyOps = key.readyOps(); |
15 |
if ((readyOps & SelectionKey.OP_READ) != 0 ) { |
16 |
LOGGER.debug( "select读事件" ); |
17 |
read((NIOConnection) att); |
18 |
.............................. |
20 |
........................... |
2、调用该连接的read函数进行处理
该函数在上一篇中提到过,该函数的实如今AbstractConnection中,实现从channel中读取数据到缓冲区,然后从缓冲区完整的取出整包数据交给FrontendConnection类的handle()函数处理。
该函数交给processor进行异步处理。从processor中的线程池获取一个线程来运行该任务。这里调用详细的handler来进行处理。
刚開始提到的,当认证成功后,Cobar将连接的回调处理函数设置为FrontendCommandHandler。所以这里会调用前端命令处理器的handler函数进行数据的处理。
在这里须要先了解MySQL数据包的格式:
MySQLclient命令请求报文
该处理函数例如以下:
01 |
public void handle( byte [] data) { |
02 |
LOGGER.info( "data[4]:" +data[ 4 ]); |
04 |
case MySQLPacket.COM_INIT_DB: |
08 |
case MySQLPacket.COM_QUERY: |
12 |
case MySQLPacket.COM_PING: |
16 |
case MySQLPacket.COM_QUIT: |
20 |
case MySQLPacket.COM_PROCESS_KILL: |
24 |
case MySQLPacket.COM_STMT_PREPARE: |
25 |
commands.doStmtPrepare(); |
26 |
source.stmtPrepare(data); |
28 |
case MySQLPacket.COM_STMT_EXECUTE: |
29 |
commands.doStmtExecute(); |
30 |
source.stmtExecute(data); |
32 |
case MySQLPacket.COM_STMT_CLOSE: |
33 |
commands.doStmtClose(); |
34 |
source.stmtClose(data); |
36 |
case MySQLPacket.COM_HEARTBEAT: |
37 |
commands.doHeartbeat(); |
38 |
source.heartbeat(data); |
42 |
source.writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Unknown command" ); |
由于每一个报文都有消息头,消息头固定的是4个字节,前3个字节是消息长度,后面的一个字节是报文序号,例如以下所看到的
所以data[4]是第五个字节。也就是消息体的第一个字节。client向Cobar端发送的是命令报文,第一个字节是详细的命令。
假设是select语句,那么data[4]就是COM_QUERY,然后会调用详细连接的query成员函数,其定义在FrontendConnection类中。
01 |
public void query( byte [] data) { |
02 |
if (queryHandler != null ) { |
04 |
MySQLMessage mm = new MySQLMessage(data); |
08 |
sql = mm.readString(charset); |
09 |
} catch (UnsupportedEncodingException e) { |
10 |
writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET, "Unknown charset '" + charset + "'" ); |
13 |
if (sql == null || sql.length() == 0 ) { |
14 |
writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL" ); |
17 |
LOGGER.debug( "解析的SQL语句:" +sql); |
19 |
queryHandler.query(sql); |
21 |
writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Query unsupported!" ); |
首先新建一个MySQLMessage对象,将数据包的索引位置定位到第6个字节位置处。然后将后面的全部的字节读取成指定编码格式的SQL语句,这里就形成了完整的SQL语句。
查询的时候Cobar控制台输出例如以下内容:
11:35:33,392 INFO data[4]:3
11:35:33,392 DEBUG 解析的SQL语句:select * from tb2
解析出SQL语句后交给queryHandler处理。该对象是在新建连接的时候设置的ServerQueryHandler类,事实上现的query函数例如以下:
01 |
public void query(String sql) { |
02 |
//这里就得到了完整的SQL语句,接收自client |
03 |
ServerConnection c = this .source; |
04 |
if (LOGGER.isDebugEnabled()) { |
05 |
LOGGER.debug( new StringBuilder().append(c).append(sql).toString()); |
07 |
//该函数对SQL语句的语法和语义进行分析,并返回SQL语句的对于类型,运行对应的操作 |
08 |
int rs = ServerParse.parse(sql); |
10 |
....................... |
11 |
case ServerParse.SELECT: |
13 |
SelectHandler.handle(sql, c, rs >>> 8 ); |
15 |
....................... |
首先对SQL语句进程解析,通过parse函数对语句解析后返回语句类型的编号。
假设语句没有语法错误,则直接交给SelectHandler进行处理。假设是一般的select语句,则直接调用ServerConnection的execute运行sql
c.execute(stmt, ServerParse.SELECT);
在ServerConnection中的execute函数中须要进行路由检查,由于select的数据不一定在一个数据库中,须要按拆分的规则进行路由的检查。
2 |
RouteResultset rrs = null ; |
4 |
rrs = ServerRouter.route(schema, sql, this .charset, this ); |
5 |
LOGGER.debug( "路由计算结果:" +rrs.toString()); |
详细的路由算法也是比較复杂,以后会专门分析。
Cobar的DEBUG控制台输出路由的计算结果例如以下:
11:35:33,392 DEBUG 路由计算结果:select * from tb2, route={
1 -> dnTest2.default{select * from tb2}
2 -> dnTest3.default{select * from tb2}
}
该条SQL语句的select内容分布在dnTset2和dnTest3中,所以要分别向这两个数据库进行查询。
经过比較复杂的资源处理最后在每一个后端数据库上运行函数execute0。
01 |
private void execute0(RouteResultsetNode rrn, Channel c, boolean autocommit, BlockingSession ss, int flag) { |
02 |
ServerConnection sc = ss.getSource(); |
03 |
......................... |
06 |
BinaryPacket bin = ((MySQLChannel) c).execute(rrn, sc, autocommit); |
07 |
// 接收和处理数据,运行到这里就说明上面的运行已经得到运行结果的返回 |
08 |
final ReentrantLock lock = MultiNodeExecutor. this .lock; |
11 |
switch (bin.data[ 0 ]) { |
12 |
case ErrorPacket.FIELD_COUNT: |
14 |
handleFailure(ss, rrn, new BinaryErrInfo((MySQLChannel) c, bin, sc, rrn)); |
16 |
case OkPacket.FIELD_COUNT: |
17 |
OkPacket ok = new OkPacket(); |
19 |
affectedRows += ok.affectedRows; |
21 |
if (ok.insertId > 0 ) { |
22 |
insertId = (insertId == 0 ) ? ok.insertId : Math.min(insertId, ok.insertId); |
25 |
handleSuccessOK(ss, rrn, autocommit, ok); |
27 |
default : // HEADER|FIELDS|FIELD_EOF|ROWS|LAST_EOF |
28 |
final MySQLChannel mc = (MySQLChannel) c; |
32 |
switch (bin.data[ 0 ]) { |
33 |
case ErrorPacket.FIELD_COUNT: |
35 |
handleFailure(ss, rrn, new BinaryErrInfo(mc, bin, sc, rrn)); |
37 |
case EOFPacket.FIELD_COUNT: |
38 |
handleRowData(rrn, c, ss); |
45 |
bin.packetId = ++packetId; // HEADER |
46 |
List<MySQLPacket> headerList = new LinkedList<MySQLPacket>(); |
50 |
switch (bin.data[ 0 ]) { |
51 |
case ErrorPacket.FIELD_COUNT: |
53 |
handleFailure(ss, rrn, new BinaryErrInfo(mc, bin, sc, rrn)); |
55 |
case EOFPacket.FIELD_COUNT: |
56 |
bin.packetId = ++packetId; // FIELD_EOF |
57 |
for (MySQLPacket packet : headerList) { |
58 |
buffer = packet.write(buffer, sc); |
61 |
buffer = bin.write(buffer, sc); |
63 |
handleRowData(rrn, c, ss); |
66 |
bin.packetId = ++packetId; // FIELDS |
68 |
case RouteResultset.REWRITE_FIELD: |
69 |
StringBuilder fieldName = new StringBuilder(); |
70 |
fieldName.append( "Tables_in_" ).append(ss.getSource().getSchema()); |
71 |
FieldPacket field = PacketUtil.getField(bin, fieldName.toString()); |
72 |
headerList.add(field); |
84 |
} //异常处理.................... |
这里真正的运行SQL语句,然后等待后端运行语句的返回数据,在成功获取后端Mysql返回的结果后,该函数返回的数据包是结果集数据包。
当client发起认证请求或命令请求后,server会返回对应的运行结果给client。client在收到响应报文后,须要首先检查第1个字节的值,来区分响应报文的类型。
响应报文类型 |
第1个字节取值范围 |
OK 响应报文 |
0×00 |
Error 响应报文 |
0xFF |
Result Set 报文 |
0×01 – 0xFA |
Field 报文 |
0×01 – 0xFA |
Row Data 报文 |
0×01 – 0xFA |
EOF 报文 |
0xFE |
注:响应报文的第1个字节在不同类型中含义不同,比方在OK报文中,该字节并没有实际意义,值恒为0×00;而在Result Set报文中,该字节又是长度编码的二进制数据结构(Length Coded Binary)中的第1字节。
Result Set 消息分为五部分,结构例如以下:
结构 |
说明 |
[Result Set Header] |
列数量 |
[Field] |
列信息(多个) |
[EOF] |
列结束 |
[Row Data] |
行数据(多个) |
[EOF] |
数据结束 |
函数运行完毕后,返回的结果都放入LinkedList中,当读取结果完毕后放入多节点运行器的缓冲区。假设buffer满了,就通过前端连接写出给client。
提示:本文版权归作者,欢迎转载,但未经作者允许必须保留此段声明,且在文章页面明显位置给出原文连接。
假设对文章有不论什么问题,都能够在评论中留言,我会尽可能的答复您,谢谢你的阅读
- 分布式数据库中间件–(2) Cobar与client握手身份验证
Cobar启动完毕,监听特定端口.整个认证的流程图: NIOAcceptor类继承自Thread类,该类的对象会以线程的方式执行,进行连接的监听. NIOAcceptor启动的初始化步骤例如以下: 1 ...
- 分布式数据库中间件–(1) Cobar初始化过程
Cobar-Server的源代码地址:GitHub 欢迎Fork. 官方文档描写叙述Cobar的网络通信模块见下图. Cobar使用了Java的NIO进行处理读写.NIO是Java中的IO复用.而不须 ...
- 分布式数据库中间件TDDL、Amoeba、Cobar、MyCAT架构比较分
比较了业界流行的MySQL分布式数据库中间件,关于每个产品的介绍,网上的资料比较多,本文只是对几款产品的架构进行比较,从中可以看出中间件发展和演进路线 框架比较 TDDL Amoeba Cobar M ...
- 分布式数据库中间件Mycat百亿级数据存储(转)
此文转自: https://www.jianshu.com/p/9f1347ef75dd 2013年阿里的Cobar在社区使用过程中发现存在一些比较严重的问题,如高并发下的假死,心跳连接的故障,只实现 ...
- 华为云分布式数据库中间件DDM和开源MyCAT对比
前言 华为云分布式数据库中间件(Distributed Database Middleware)是解决数据库容量.性能瓶颈和分布式扩展问题的中间件服务,提供分库分表.读写分离.弹性扩容等能力,应对海量 ...
- 浅析分布式数据库中间件DDM
前言 DDM是什么?这是华为云Paas推出的分布式数据库中间件,DDM(Distributed Database Middleware)是一个实现了Mysql协议栈的服务器,前端用户可以把它看做一个数 ...
- 对话DDM:分布式数据库中间件全解析
进入云计算时代,传统的数据库在性能和容量等方面已无法满足企业的要求,随着数据量的不断骤增,易于扩展.拆分的数据库解决方案对于企业的云化转型更是显得尤为重要.为使企业应用上云更简单,分布式数据库中间件D ...
- 分布式数据库中间件 MyCat | 分库分表实践
MyCat 简介 MyCat 是一个功能强大的分布式数据库中间件,是一个实现了 MySQL 协议的 Server,前端人员可以把它看做是一个数据库代理中间件,用 MySQL 客户端工具和命令行访问:而 ...
- 分布式数据库中间件DDM的实现原理
随着数据量不断增大,传统的架构模式难以解决业务量不断增长所带来的问题,特别是在业务成线性.甚至指数级上升的情况.此时我们不得不通过水平扩展,把数据库放到不同服务器上来解决问题,也就是我们说的数据库中间 ...
- POJ 3422 Kaka's Matrix Travels(最小费用最大流)
http://poj.org/problem?id=3422 题意 : 给你一个N*N的方格,每个格子有一个数字,让你从左上角开始走,只能往下往右走,走过的数字变为0,走K次,问最大能是多大,累加的. ...
- 游戏文字自动断行需要,还得从 UTF-8 讲起
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码. UTF-8使用一至六个字节为每个字符编码(尽管如此,2 ...
- 解决win8.1右键菜单出现在左边
这个问题估计很少有人遇到,当在桌面上单击鼠标右键时,如果正常情况下,应该是在鼠标光标的右侧弹出来,除非右边的空间不够了,才在左侧弹出.但遇到故障,就是不论在桌面的哪里点右键,菜单都在左侧弹出,虽然不影 ...
- ruby mysql数据库操作
require 'mysql' con=Mysql.new('localhost','root','root','test') con.query('set names utf8') rs=con.q ...
- /MD, /MDD, /ML, /MT,/MTD(使用运行时库)
1. VC编译选项 多线程(/MT)多线程调试(/MTd)多线程 DLL (/MD)多线程调试 DLL (/MDd) 2. C 运行时库 ...
- perl 正则匹配多个
Vsftp:/root# cat k1.pl my $_='upDaTe'; if( $_ =~ /^(SELECT|UPDATE|DELETE|INSERT|SET|COMMIT|ROLLBACK| ...
- wcf iis host 打开exe失败 不能显示界面
最近谷歌没法用了,我的freegate经常性的崩溃 无奈之下,用了必应,貌似也不错 http://stackoverflow.com/questions/8414514/iis7-does-not-s ...
- Android开发UI之Fragment-Tabbed Activity的使用
使用ADT新建的时候,可以选择Tabbed Activity,选择新建一个工程. 新建的工程中,选择不同的Tab页显示不同的内容,主要是通过SectionsPagerAdapter类中的Fragmen ...
- MapReduce——计算温度最大值 (基于全新2.2.0API)
MapReduce——计算温度最大值 (基于全新2.2.0API) deprecated: Job类的所有Constructors, 新的API用静态方法getInstance(conf)来去的Job ...
- 设置将 Microsoft Azure 的网络基础结构以支持设置为灾难恢复站点
Prateek Sharma 云 + Enterprise 高级项目经理 Azure SiteRecovery (ASR)可以将Microsoft Azure用作您的虚拟机的灾难恢复站点. 当管理 ...