数据库路由中间件MyCat - 源代码篇(1)
此文已由作者张镐薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式。然后,针对每一个重点模块进行分析。
1. 整体通信与业务框架:
前端与后端通信框架都为NIO/AIO,因为目前生产上用的linux发行版内核都没有真正实现网络上的AIO,如果应用用AIO的话可能比NIO还要慢一些,所以,我们这里只分析NIO相关的通信模块。
NIOAcceptor:作为服务器接受客户端连接(前端NIO通信)
NIOConnector:作为客户端去连接后台数据库(MySql,后端NIO通信)
NIOReactor:Reactor模式的NIO,处理并转发请求到RW线程,其实就是把对应AbstractConnection(就是NIO的channel的封装)注册到RW线程的selector上,只注册读标记;原因之后细讲
NIOReactorPool:一般高性能网络通信框架采用多Reactor(多dispatcher)模式,这里将NIOReactor池化;每次NIOConnector接受一个连接或者NIOAcceptor请求一个连接,都会封装成AbstractConnection,同时请求NIOReactorPool每次轮询出一个NIOReactor,之后AbstractConnection与这个NIOReactor绑定(就是3之中说的注册)。
RW:RW线程,负责执行NIO的channel读写,这里channel封装成了AbstractConnection
NIOSocketWR:每个前端和后端连接都有一个对应的缓冲区,对连接读写操作具体如何操作的方法和缓存方式,封装到了这个类里面。
通过上面的分析,我们大致知道了通信是由谁负责的了,但是为什么NIOReactor只注册读标记?还有网络通信channel(之后的文章我们就都用AbstractConnection代替了)读写有线程执行了,但是中间的业务步骤,比如SQL拦截,SQL解析还有结果合并是谁执行呢?然后,还有些定时的任务,比如检查心跳连接等,如何执行呢?
首先,Reactor不会主动驱动写请求,写请求只会由业务步骤和定时任务触发。首先看,Reactor与前端AbstractConnection还有后端AbstractConnection,接收到的请求有两种,前端的SQL请求,还有后端的结果。但是这两种都不能直接转发,前端的SQL请求需要经过SQL解析等业务步骤才能写到后端,后端的结果也需要经过业务处理才能写到前端。所以只要执行业务步骤的线程去注册写标记,Reactor只要在检查到写标记后去写之后取消标记即可。定时任务同理。
那么谁去执行业务请求呢?MyCat会初始化一个BusinessExecutor线程池去处理业务请求,这个BusinessExecutor接受Reactor调度,定时任务由一个Timer线程调度并由一个TimerExecutor线程池执行。
整体结构如下所示,所有椭圆形的图形是线程或者进程(省略了很多,比如缓冲、缓存、连接以及对应的管理,这些之后会细细介绍):
2. 前端连接建立与认证
mysql客户端连接mysql服务器抓包:流程是:
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.1 (1~3)TCP连接请求->接受TCP连接->TCP连接建立
首先,接受TCP连接(为了三次握手,上面流程的前三个包)需要通过NIOAcceptor实现,NIOAcceptor主要完成绑定端口,注册OP_ACCEPT监听客户端连接事件,有客户连接,则放接受连接,将返回的channel封装成为FrontendConnection(AbstarctConnection的子类),从NIOReactorPool中拿出一个NIOReactor并将FrontendConnection交给它绑定。
到此,NIOAcceptor就处理完一个客户端的连接请求。
public NIOAcceptor(String name, String bindIp, int port,
FrontendConnectionFactory factory, NIOReactorPool reactorPool)
throws IOException { super.setName(name); this.port = port; this.selector = Selector.open(); this.serverChannel = ServerSocketChannel.open(); this.serverChannel.configureBlocking(false); //设置TCP属性
serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
serverChannel.setOption(StandardSocketOptions.SO_RCVBUF, 1024 * 16 * 2); // backlog=100
serverChannel.bind(new InetSocketAddress(bindIp, port), 100); //注册OP_ACCEPT,监听客户端连接
this.serverChannel.register(selector, SelectionKey.OP_ACCEPT); //FrontendConnectionFactory,用来封装channel成为FrontendConnection
this.factory = factory; //NIOReactor池
this.reactorPool = reactorPool;
}
构造器读取ip,端口,前端连接工厂和NIOReactor池,初始化TCP参数,并bind,在selector上注册OP_ACCEPT。
在NIOAcceptor启动后:
@Override
public void run() { final Selector tSelector = this.selector; for (; ; ) {
++acceptCount; try { //轮询发现新连接请求
tSelector.select(1000L);
Set<SelectionKey> keys = tSelector.selectedKeys(); try { for (SelectionKey key : keys) { if (key.isValid() && key.isAcceptable()) { //接受连接操作
accept();
} else {
key.cancel();
}
}
} finally {
keys.clear();
}
} catch (Exception e) {
LOGGER.warn(getName(), e);
}
}
}
NIOAcceptor这个线程不断轮询接受新的客户端连接请求,接受连接操作:
private void accept() {
SocketChannel channel = null; try { //得到通信channel并设置为非阻塞
channel = serverChannel.accept();
channel.configureBlocking(false); //封装channel为FrontendConnection
FrontendConnection c = factory.make(channel);
c.setAccepted(true);
c.setId(ID_GENERATOR.getId()); //利用NIOProcessor管理前端链接,定期清除空闲连接,同时做写队列检查
NIOProcessor processor = (NIOProcessor) MycatServer.getInstance()
.nextProcessor();
c.setProcessor(processor); //和具体执行selector响应感兴趣事件的NIOReactor绑定
NIOReactor reactor = reactorPool.getNextReactor();
reactor.postRegister(c);
} catch (Exception e) {
LOGGER.warn(getName(), e);
closeChannel(channel);
}
}
NIOProcessor持有所有的前后端连接,里面有空闲检查和写队列检查。RW线程,TimerExecutor线程池会执行里面的方法来实现空闲写入和定时连接检查等等。之后我还会详细介绍。
关闭Channel方法以及生成连接id方法很简单,就不加注释和赘述了。下面是他们的源代码:
private static void closeChannel(SocketChannel channel) { if (channel == null) { return;
}
Socket socket = channel.socket(); if (socket != null) { try {
socket.close();
} catch (IOException e) {
LOGGER.error("closeChannelError", e);
}
} try {
channel.close();
} catch (IOException e) {
LOGGER.error("closeChannelError", e);
}
} /**
* 前端连接ID生成器
*
* @author mycat
*/
private static class AcceptIdGenerator { private static final long MAX_VALUE = 0xffffffffL; private long acceptId = 0L; private final Object lock = new Object(); private long getId() { synchronized (lock) { if (acceptId >= MAX_VALUE) {
acceptId = 0L;
} return ++acceptId;
}
}
}
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 一个小白的测试环境docker化之路
【推荐】 网易七鱼 Android 高性能日志写入方案
【推荐】 浅谈由管理者角色引出的B端产品设计思考点
数据库路由中间件MyCat - 源代码篇(1)的更多相关文章
- 数据库路由中间件MyCat - 源代码篇(13)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.配置模块 4.2 schema.xml 接上一篇,接下来载入每个schema的配置(也就是每个MyCat ...
- 数据库路由中间件MyCat - 源代码篇(7)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.4 FrontendConnection前端连接 构造方法: public Fronte ...
- 数据库路由中间件MyCat - 源代码篇(15)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. public static void handle(String stmt, ServerConnectio ...
- 数据库路由中间件MyCat - 源代码篇(17)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 调用processInsert(sc,schema,sqlType,origSQL,tableName,pr ...
- 数据库路由中间件MyCat - 源代码篇(14)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(d ...
- 数据库路由中间件MyCat - 源代码篇(4)
此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(2)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...
- 数据库路由中间件MyCat - 源代码篇(16)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5. 路由模块 真正取得RouteResultset的步骤:AbstractRouteStrategy的ro ...
- 数据库路由中间件MyCat - 源代码篇(10)
此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...
随机推荐
- transition_matrix Markov chain
- me12里更改信息记录的净价和有效价格,以及信息记录的条件价格
转自 http://blog.csdn.net/zeewjj/article/details/7941525REPORT ztest. DATA:l_kbetr LIKE konp-kbetr.l_k ...
- main方法的参数
敲例子的时候无意中把主方法的参数给落下了,当时没有发现,保存之后就去编译,运行了,通常情况下编译没有错误那胜利就在掌握之中了,没想到这次我竟然在"不一般"的行列中,编译无误,运行出 ...
- [IR课程笔记]Query Refinement and Relevance Feedback
相关反馈的两种类型: “真实”的相关反馈: 1. 系统返回结果 2. 用户提供一些反馈 3. 系统根据这些反馈,返回一些不同的,更好的结果 “假定”的相关反馈 1. 系统得到结果但是并不返回结果 2. ...
- CodeForces - 799B T-shirt buying 【贪心】
题目链接 http://codeforces.com/problemset/problem/799/B 题意 给出N件衣服 pi 表示 第i件衣服的价格 ai 表示 第i件衣服的前面的颜色 bi 表示 ...
- (KEILv5)使用matlab绘制从KEIL memory导出的内存数据
使用matlab绘制从KEIL memory导出的内存数据 如标题,某些时候我们需要分析keil调试过程中的数据,比如从I2S.SPI.I2C或者UART获取到的波形数据,这些数据时数字流,通过逻辑分 ...
- 51nod 1537
题目 神犇题解 证明好巧妙,给跪OTZ 题目的式子:$ {\left( {1{\rm{ + }}\sqrt 2 } \right)^{\rm{n}}} $,设其乘开之后为 $ {\rm{a + b}} ...
- Linux三种网络-vmware三种网络模式
Host-Only 桥接 NAT VMware虚拟机三种联网方法及原理 一.Brigde——桥接:默认使用VMnet0 1.原理: Bridge 桥"就是一个主机,这个机器拥有两块网卡,分别 ...
- Mac系统存储-其他存储无故增大
解决办法:打开Finder:安全倾倒废纸篓就会减少很大一部分存储.
- java数据的5种存储位置(转)
任何语言所编写的程序,其中的各类型的数据都需要一个存储位置,java中书的存储位置分为以下5种: 1.寄存器 最快的存储区,位于处理器内部,但是数量及其有限.所以寄存器根据需求自动分配,无序人为控制. ...