更多MyCat源码分析,请戳MyCat源码分析系列


MyCat前端验证

MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -proot -P8066 db_test触发的一系列行为。

验证的过程分为几个步骤:

1)应用与MyCat建立TCP连接;

2)MyCat发送握手包,其中带有为密码加密的种子(seed);

3)应用接收握手包,使用种子进行密码加密,随后将包含用户名、加密密码、字符集和需连接的数据库(可选)等的验证包发送给MyCat;

4)MyCat依次验证信息,并将验证结果回发给应用

MyCat中,前端应用与MyCat之间建立的连接称为FrontendConnection(其子类为ServerConnection和ManagerConnection,分别由ServerConnectionFactory和ManagerConnectionFactory负责创建),在构造函数的时候绑定了一个NIOHandler类型的FrontendAuthenticator用于后续的验证

public FrontendConnection(NetworkChannel channel) throws IOException {
super(channel);
InetSocketAddress localAddr = (InetSocketAddress) channel.getLocalAddress();
InetSocketAddress remoteAddr = null;
if (channel instanceof SocketChannel) {
remoteAddr = (InetSocketAddress) ((SocketChannel) channel).getRemoteAddress(); } else if (channel instanceof AsynchronousSocketChannel) {
remoteAddr = (InetSocketAddress) ((AsynchronousSocketChannel) channel).getRemoteAddress();
} this.host = remoteAddr.getHostString();
this.port = localAddr.getPort();
this.localPort = remoteAddr.getPort();
this.handler = new FrontendAuthenticator(this);
}

首先,应用与MyCat的TCP连接建立过程由NIOAcceptor负责完成,当之前注册的OP_ACCEPT事件得到响应时调用accept()方法:

private void accept() {
SocketChannel channel = null;
try {
channel = serverChannel.accept();
channel.configureBlocking(false);
FrontendConnection c = factory.make(channel);
c.setAccepted(true);
c.setId(ID_GENERATOR.getId());
NIOProcessor processor = (NIOProcessor) MycatServer.getInstance()
.nextProcessor();
c.setProcessor(processor); NIOReactor reactor = reactorPool.getNextReactor();
reactor.postRegister(c); } catch (Exception e) {
LOGGER.warn(getName(), e);
closeChannel(channel);
}
}

该TCP连接建立完成后创建一个FrontendConnection实例,并交由NIOReactor负责后续的读写工作(reactor.postRegister(c);),而NIOReactor中的内部类RW会调用其register()方法:

private void register(Selector selector) {
AbstractConnection c = null;
if (registerQueue.isEmpty()) {
return;
}
while ((c = registerQueue.poll()) != null) {
try {
((NIOSocketWR) c.getSocketWR()).register(selector);
c.register();
} catch (Exception e) {
c.close("register err" + e.toString());
}
}
}

其中,NIOSocketWR会调用其register()方法注册OP_READ事件,随后FrontendConnection调用其register()方法发送握手包HandshakePacket,等待应用回发验证包:

public void register() throws IOException {
if (!isClosed.get()) { // 生成认证数据
byte[] rand1 = RandomUtil.randomBytes(8);
byte[] rand2 = RandomUtil.randomBytes(12); // 保存认证数据
byte[] seed = new byte[rand1.length + rand2.length];
System.arraycopy(rand1, 0, seed, 0, rand1.length);
System.arraycopy(rand2, 0, seed, rand1.length, rand2.length);
this.seed = seed; // 发送握手数据包
HandshakePacket hs = new HandshakePacket();
hs.packetId = 0;
hs.protocolVersion = Versions.PROTOCOL_VERSION;
hs.serverVersion = Versions.SERVER_VERSION;
hs.threadId = id;
hs.seed = rand1;
hs.serverCapabilities = getServerCapabilities();
hs.serverCharsetIndex = (byte) (charsetIndex & 0xff);
hs.serverStatus = 2;
hs.restOfScrambleBuff = rand2;
hs.write(this); // asynread response
this.asynRead();
}
}

当收到应用的验证包AuthPacket时,之前绑定在FrontendConnection上的FrontendAuthenticator会调用其handle()方法:

public void handle(byte[] data) {
// check quit packet
if (data.length == QuitPacket.QUIT.length && data[4] == MySQLPacket.COM_QUIT) {
source.close("quit packet");
return;
} AuthPacket auth = new AuthPacket();
auth.read(data); // check user
if (!checkUser(auth.user, source.getHost())) {
failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "' with host '" + source.getHost()+ "'");
return;
} // check password
if (!checkPassword(auth.password, auth.user)) {
failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "', because password is error ");
return;
} // check degrade
if ( isDegrade( auth.user ) ) {
failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "', because service be degraded ");
return;
} // check schema
switch (checkSchema(auth.database, auth.user)) {
case ErrorCode.ER_BAD_DB_ERROR:
failure(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '" + auth.database + "'");
break;
case ErrorCode.ER_DBACCESS_DENIED_ERROR:
String s = "Access denied for user '" + auth.user + "' to database '" + auth.database + "'";
failure(ErrorCode.ER_DBACCESS_DENIED_ERROR, s);
break;
default:
success(auth);
}
}

依次验证用户名、密码、用户负载、数据库权限,验证成功后调用success()方法向应用发送OK包:

protected void success(AuthPacket auth) {
source.setAuthenticated(true);
source.setUser(auth.user);
source.setSchema(auth.database);
source.setCharsetIndex(auth.charsetIndex);
source.setHandler(new FrontendCommandHandler(source)); if (LOGGER.isInfoEnabled()) {
StringBuilder s = new StringBuilder();
s.append(source).append('\'').append(auth.user).append("' login success");
byte[] extra = auth.extra;
if (extra != null && extra.length > 0) {
s.append(",extra:").append(new String(extra));
}
LOGGER.info(s.toString());
} ByteBuffer buffer = source.allocate();
source.write(source.writeToBuffer(AUTH_OK, buffer));
boolean clientCompress = Capabilities.CLIENT_COMPRESS==(Capabilities.CLIENT_COMPRESS & auth.clientFlags);
boolean usingCompress= MycatServer.getInstance().getConfig().getSystem().getUseCompression()==1 ;
if(clientCompress&&usingCompress)
{
source.setSupportCompress(true);
}
}

这里最重要的就是将FrontendConnection的handler重新绑定为FrontendCommandHandler对象,用于后续各类命令的接收和处理分发。


后端验证

后端验证过程类似于前端验证过程,区别就在于此时MyCat是作为客户端,向后端MySQL数据库发起连接与验证请求。

MyCat中,MyCat与后端MySQL的连接称为MySQLConnection,由MySQLConnectionFactory创建,并绑定了一个NIOHandler类型的MySQLConnectionAuthenticator,用于完成与MySQL的验证过程:

public MySQLConnection make(MySQLDataSource pool, ResponseHandler handler,
String schema) throws IOException { DBHostConfig dsc = pool.getConfig();
NetworkChannel channel = openSocketChannel(MycatServer.getInstance()
.isAIO()); MySQLConnection c = new MySQLConnection(channel, pool.isReadNode());
MycatServer.getInstance().getConfig().setSocketParams(c, false);
c.setHost(dsc.getIp());
c.setPort(dsc.getPort());
c.setUser(dsc.getUser());
c.setPassword(dsc.getPassword());
c.setSchema(schema);
c.setHandler(new MySQLConnectionAuthenticator(c, handler));
c.setPool(pool);
c.setIdleTimeout(pool.getConfig().getIdleTimeout());
if (channel instanceof AsynchronousSocketChannel) {
((AsynchronousSocketChannel) channel).connect(
new InetSocketAddress(dsc.getIp(), dsc.getPort()), c,
(CompletionHandler) MycatServer.getInstance()
.getConnector());
} else {
((NIOConnector) MycatServer.getInstance().getConnector())
.postConnect(c);
}
return c;
}

MySQLConnection创建完成后,交由NIOConnector完成TCP连接,它会调用其connect()方法注册OP_CONNECT事件,并发送连接请求至MySQL:

private void connect(Selector selector) {
AbstractConnection c = null;
while ((c = connectQueue.poll()) != null) {
try {
SocketChannel channel = (SocketChannel) c.getChannel();
channel.register(selector, SelectionKey.OP_CONNECT, c);
channel.connect(new InetSocketAddress(c.host, c.port));
} catch (Exception e) {
c.close(e.toString());
}
}
}

TCP连接建立后同样交由NIOReactor负责后续的读写工作,最终之前绑定在MySQLConnection上的MySQLConnectionAuthenticator会调用其handle()方法:

public void handle(byte[] data) {
try {
switch (data[4]) {
case OkPacket.FIELD_COUNT:
HandshakePacket packet = source.getHandshake();
if (packet == null) {
processHandShakePacket(data);
// 发送认证数据包
source.authenticate();
break;
}
// 处理认证结果
source.setHandler(new MySQLConnectionHandler(source));
source.setAuthenticated(true);
boolean clientCompress = Capabilities.CLIENT_COMPRESS==(Capabilities.CLIENT_COMPRESS & packet.serverCapabilities);
boolean usingCompress= MycatServer.getInstance().getConfig().getSystem().getUseCompression()==1 ;
if(clientCompress&&usingCompress)
{
source.setSupportCompress(true);
}
if (listener != null) {
listener.connectionAcquired(source);
}
break;
case ErrorPacket.FIELD_COUNT:
ErrorPacket err = new ErrorPacket();
err.read(data);
String errMsg = new String(err.message);
LOGGER.warn("can't connect to mysql server ,errmsg:"+errMsg+" "+source);
//source.close(errMsg);
throw new ConnectionException(err.errno, errMsg); case EOFPacket.FIELD_COUNT:
auth323(data[3]);
break;
default:
packet = source.getHandshake();
if (packet == null) {
processHandShakePacket(data);
// 发送认证数据包
source.authenticate();
break;
} else {
throw new RuntimeException("Unknown Packet!");
} } } catch (RuntimeException e) {
if (listener != null) {
listener.connectionError(e, source);
return;
}
throw e;
}
}

与前端验证一样,MySQL数据库作为服务端会在TCP连接建立完成后向应用发送一个握手包HandshakePacket,在此MySQLConnectionAuthenticator负责读取此包,并通过source.authenticate();调用MySQLConnection的authenticate()方法向MySQL发送验证包AuthPacket:

public void authenticate() {
AuthPacket packet = new AuthPacket();
packet.packetId = 1;
packet.clientFlags = clientFlags;
packet.maxPacketSize = maxPacketSize;
packet.charsetIndex = this.charsetIndex;
packet.user = user;
try {
packet.password = passwd(password, handshake);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e.getMessage());
}
packet.database = schema;
packet.write(this);
}

验证通过后,将MySQLConnection的handler重新绑定为MySQLConnectionHandler对象,用于后续接收MySQL数据库发送过来的各类数据和处理分发


参考资料

[1] http://sonymoon.iteye.com/blog/2245141


为尊重原创成果,如需转载烦请注明本文出处:http://www.cnblogs.com/fernandolee24/p/5196332.html,特此感谢

MyCat源码分析系列之——前后端验证的更多相关文章

  1. 开源分布式数据库中间件MyCat源码分析系列

    MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...

  2. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  3. MyCat源码分析系列之——SQL下发

    更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...

  4. MyCat源码分析系列之——配置信息和启动流程

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...

  5. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  6. Spring mvc源码分析系列--前言

    Spring mvc源码分析系列--前言 前言 距离上次写文章已经过去接近两个月了,Spring mvc系列其实一直都想写,但是却不知道如何下笔,原因有如下几点: 现在项目开发前后端分离的趋势不可阻挡 ...

  7. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  8. spring源码分析系列 (5) spring BeanFactoryPostProcessor拓展类PropertyPlaceholderConfigurer、PropertySourcesPlaceholderConfigurer解析

    更多文章点击--spring源码分析系列 主要分析内容: 1.拓展类简述: 拓展类使用demo和自定义替换符号 2.继承图UML解析和源码分析 (源码基于spring 5.1.3.RELEASE分析) ...

  9. spring源码分析系列 (3) spring拓展接口InstantiationAwareBeanPostProcessor

    更多文章点击--spring源码分析系列 主要分析内容: 一.InstantiationAwareBeanPostProcessor简述与demo示例 二.InstantiationAwareBean ...

随机推荐

  1. SQLServer执行命令出现“目录无效的提示”

    异常处理汇总-数据库系列  http://www.cnblogs.com/dunitian/p/4522990.html 一般都是清理垃圾清理过头了,把不该删的目录删了 网上说法: 问题描述: 1.s ...

  2. 一道返回num值的小题目

    题目描述: 实现fizzBuzz函数,参数num与返回值的关系如下: .如果num能同时被3和5整除,返回字符串fizzbuzz .如果num能被3整除,返回字符串fizz .如果num能被5整除,返 ...

  3. Oracle碎碎念~2

    1. 如何查看表的列名及类型 SQL> select column_name,data_type,data_length from all_tab_columns where owner='SC ...

  4. DBA成长路线

    从开发转为数据库管理,即人们称为DBA的已经有好几年,有了与当初不一样的体会.数据是企业的血液,数据是石油,数据是一切大数据.云计算的基础.作为DBA是数据的保卫者.管理者,是企业非常重要的角色.对于 ...

  5. FullCalendar日历插件说明文档

    FullCalendar提供了丰富的属性设置和方法调用,开发者可以根据FullCalendar提供的API快速完成一个日历日程的开发,本文将FullCalendar的常用属性和方法.回调函数等整理成中 ...

  6. linux中kvm的安装及快照管理

    一.kvm的安装及状态查看 1.安装软件 yum -y install kvm virt-manager libvirt2.启动libvirtd 报错,升级device-mapper-libs yum ...

  7. MMORPG大型游戏设计与开发(攻击区域 扇形)

    距离上次发布已经有了很长一段时间,期间由于各种原因没有更新这方面的技术分享,在这里深表遗憾.在MMO或其他的游戏中,会有针对各种形状的计算,通常在攻击区域里不会很复杂,常见的为矩形.圆形.扇形.今天分 ...

  8. Linux下部署ASP.NET服务连接oracle遇到的问题记录

    一.如何卸载MONO Q:mono是linux系统上跨平台软件,卸载它有两种方式: 1.知道mono安装路径,安装原来的路径直接覆盖安装(最为简单): 2.不知道mono安装路径,首先通过sudo f ...

  9. 最牛的打字效果JS插件 typing.js

    最新在做公司的一个项目,需要实现一个敲打代码的动画效果,粗意味比较简单,果断自己直接开写,写着写着发现是一个坑.需要支持语法高亮,并不能直接简单的用setTimeout来动态附件innerHTML.苦 ...

  10. ABP源码分析八:Logger集成

    ABP使用Castle日志记录工具,并且可以使用不同的日志类库,比如:Log4Net, NLog, Serilog... 等等.对于所有的日志类库,Castle提供了一个通用的接口来实现,我们可以很方 ...