MyCat - 源代码篇(1)

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式。然后,针对每一个重点模块进行分析。
MyCat整体框架图:

1. 整体通信与业务框架:

前端与后端通信框架都为NIO/AIO,因为目前生产上用的linux发行版内核都没有真正实现网络上的AIO,如果应用用AIO的话可能比NIO还要慢一些,所以,我们这里只分析NIO相关的通信模块。
相关类图:

  1. NIOAcceptor:作为服务器接受客户端连接(前端NIO通信)
  2. NIOConnector:作为客户端去连接后台数据库(MySql,后端NIO通信)
  3. NIOReactor:Reactor模式的NIO,处理并转发请求到RW线程,其实就是把对应AbstractConnection(就是NIO的channel的封装)注册到RW线程的selector上,只注册读标记;原因之后细讲
  4. NIOReactorPool:一般高性能网络通信框架采用多Reactor(多dispatcher)模式,这里将NIOReactor池化;每次NIOConnector接受一个连接或者NIOAcceptor请求一个连接,都会封装成AbstractConnection,同时请求NIOReactorPool每次轮询出一个NIOReactor,之后AbstractConnection与这个NIOReactor绑定(就是3之中说的注册)。
  5. RW:RW线程,负责执行NIO的channel读写,这里channel封装成了AbstractConnection
  6. NIOSocketWR:每个前端和后端连接都有一个对应的缓冲区,对连接读写操作具体如何操作的方法和缓存方式,封装到了这个类里面。

通过上面的分析,我们大致知道了通信是由谁负责的了,但是为什么NIOReactor只注册读标记?还有网络通信channel(之后的文章我们就都用AbstractConnection代替了)读写有线程执行了,但是中间的业务步骤,比如SQL拦截,SQL解析还有结果合并是谁执行呢?然后,还有些定时的任务,比如检查心跳连接等,如何执行呢?
首先,Reactor不会主动驱动写请求,写请求只会由业务步骤和定时任务触发。首先看,Reactor与前端AbstractConnection还有后端AbstractConnection,接收到的请求有两种,前端的SQL请求,还有后端的结果。但是这两种都不能直接转发,前端的SQL请求需要经过SQL解析等业务步骤才能写到后端,后端的结果也需要经过业务处理才能写到前端。所以只要执行业务步骤的线程去注册写标记,Reactor只要在检查到写标记后去写之后取消标记即可。定时任务同理。
那么谁去执行业务请求呢?MyCat会初始化一个BusinessExecutor线程池去处理业务请求,这个BusinessExecutor接受Reactor调度,定时任务由一个Timer线程调度并由一个TimerExecutor线程池执行。
整体结构如下所示,所有椭圆形的图形是线程或者进程(省略了很多,比如缓冲、缓存、连接以及对应的管理,这些之后会细细介绍):

2. 前端连接建立与认证

mysql客户端连接mysql服务器抓包:


流程是:

Created with Raphaël 2.1.0MySql连接建立以及认证过程clientclientMySqlMySql1.TCP连接请求2.接受TCP连接3.TCP连接建立4.握手包HandshakePacket5.认证包AuthPacket6.如果验证成功,则返回OkPacket7.默认会发送查询版本信息的包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;
                }
            }
        }
  • 26
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值