此文已由作者易国强授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
2. 前端连接建立与认证
Title:MySql连接建立以及认证过程client->MySql:1.TCP连接请求 MySql->client:2.接受TCP连接client->MySql:3.TCP连接建立MySql->client:4.握手包HandshakePacketclient->MySql:5.认证包AuthPacketMySql->client:6.如果验证成功,则返回OkPacketclient->MySql:7.默认会发送查询版本信息的包MySql->client:8.返回结果包
2.5 (7~8) 默认会发送查询版本信息的包,返回结果包
MySql客户端在连接建立后,默认会发送查询版本信息的包,这其实就是一个SQL查询请求了。只不过这个请求不用路由到后台某个数据库^_^。 连接成功建立后,连接绑定的RW线程会监听上面的读事件。在客户端发送查询版本信息的包之后,会触发RW线程去读取对应连接,过程与之前接收AuthPacket类似: RW类代码片段
//监听到有效读if (key.isValid() && key.isReadable()) { try { //异步读取数据并处理数据 con.asynRead(); } catch (IOException e) { con.close("program err:" + e.toString()); continue; } catch (Exception e) { LOGGER.debug("caught err:", e); con.close("program err:" + e.toString()); continue; } }
之后的读取过程也是调用AbstractConnection的asynRead()方法,进行异步读取。过程就不再赘述,读取到的数据交由FrontendCommandHandler处理。 查询版本信息的包(是一种CommandPacket)内容: CommandPacket:
packet length (3)
packet number (1)
command (1)
statement (null terminated string)
FrontendCommandHandler的处理方法:
@Override public void handle(byte[] data) { if(source.getLoadDataInfileHandler()!=null&&source.getLoadDataInfileHandler().isStartLoadData()) { MySQLMessage mm = new MySQLMessage(data); int packetLength = mm.readUB3(); if(packetLength+4==data.length) { source.loadDataInfileData(data); } return; } switch (data[4]) { case MySQLPacket.COM_INIT_DB: commands.doInitDB(); source.initDB(data); break; case MySQLPacket.COM_QUERY: commands.doQuery(); source.query(data); break; case MySQLPacket.COM_PING: commands.doPing(); source.ping(); break; case MySQLPacket.COM_QUIT: commands.doQuit(); source.close("quit cmd"); break; case MySQLPacket.COM_PROCESS_KILL: commands.doKill(); source.kill(data); break; case MySQLPacket.COM_STMT_PREPARE: commands.doStmtPrepare(); source.stmtPrepare(data); break; case MySQLPacket.COM_STMT_EXECUTE: commands.doStmtExecute(); source.stmtExecute(data); break; case MySQLPacket.COM_STMT_CLOSE: commands.doStmtClose(); source.stmtClose(data); break; case MySQLPacket.COM_HEARTBEAT: commands.doHeartbeat(); source.heartbeat(data); break; default: commands.doOther(); source.writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Unknown command"); } }
根据CommandPacket的第五字节判断command类型,不同类型有不同的处理。 首先querycommand计数加1,之后调用对应FrontendConnection的query(byte[])方法:
public void query(byte[] data) { if (queryHandler != null) { // 取得语句|get sql MySQLMessage mm = new MySQLMessage(data); //从第六字节开始读取|read from the 6th byte mm.position(5); String sql = null; try { sql = mm.readString(charset); } catch (UnsupportedEncodingException e) { writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET, "Unknown charset '" + charset + "'"); return; } if (sql == null || sql.length() == 0) { writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL"); return; } // sql = StringUtil.replace(sql, "`", ""); // 移除末尾';'|remove last ';' if (sql.endsWith(";")) { sql = sql.substring(0, sql.length() - 1); } // 记录SQL|record SQL this.setExecuteSql(sql); // 执行查询 queryHandler.setReadOnly(privileges.isReadOnly(user)); queryHandler.query(sql); } else { writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Query unsupported!"); } }
执行查询,调用对应的FrontendQueryHandler: 这里,很明显,是ServerQueryHandler。
public void query(String sql) { ServerConnection c = this.source; if (LOGGER.isDebugEnabled()) { LOGGER.debug(new StringBuilder().append(c).append(sql).toString()); } // int rs = ServerParse.parse(sql); int sqlType = rs & 0xff; switch (sqlType) { case ServerParse.EXPLAIN: ExplainHandler.handle(sql, c, rs >>> 8); break; case ServerParse.EXPLAIN2: Explain2Handler.handle(sql, c, rs >>> 8); break; case ServerParse.SET: SetHandler.handle(sql, c, rs >>> 8); break; case ServerParse.SHOW: ShowHandler.handle(sql, c, rs >>> 8); break; case ServerParse.SELECT: if(QuarantineHandler.handle(sql, c)){ SelectHandler.handle(sql, c, rs >>> 8); } break; case ServerParse.START: StartHandler.handle(sql, c, rs >>> 8); break; case ServerParse.BEGIN: BeginHandler.handle(sql, c); break; case ServerParse.SAVEPOINT: SavepointHandler.handle(sql, c); break; case ServerParse.KILL: KillHandler.handle(sql, rs >>> 8, c); break; case ServerParse.KILL_QUERY: LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString()); c.writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,"Unsupported command"); break; case ServerParse.USE: UseHandler.handle(sql, c, rs >>> 8); break; case ServerParse.COMMIT: c.commit(); break; case ServerParse.ROLLBACK: c.rollback(); break; case ServerParse.HELP: LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString()); c.writeErrMessage(ErrorCode.ER_SYNTAX_ERROR, "Unsupported command"); break; case ServerParse.MYSQL_CMD_COMMENT: c.write(c.writeToBuffer(OkPacket.OK, c.allocate())); break; case ServerParse.MYSQL_COMMENT: c.write(c.writeToBuffer(OkPacket.OK, c.allocate())); break; case ServerParse.LOAD_DATA_INFILE_SQL: c.loadDataInfileStart(sql); break; default: if(readOnly){ LOGGER.warn(new StringBuilder().append("User readonly:").append(sql).toString()); c.writeErrMessage(ErrorCode.ER_USER_READ_ONLY, "User readonly"); break; } if(QuarantineHandler.handle(sql, c)){ c.execute(sql, rs & 0xff); } } }
针对每种command,都有不同的handler和处理方式。之后如何处理,就在之后的SQL解析器等章节进行分析。
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 记录解决自动部署系统的发布不成功的问题