cobar源码浅析
来源:互联网 发布:软件项目管理标准 编辑:程序博客网 时间:2024/06/03 17:18
网路连接部分
网络连接部分是cobar非常重要的部分,它承接着前向连接(面向用户)和后向连接(面向mysql)。通过分析网络连接代码,可以大致看出cobar的数据流向。
我们先看看整个连接架构(省略心跳相关内容):
我们再来看看系统初始化代码
// startup processors LOGGER.info("Startup processors ..."); int handler = system.getProcessorHandler(); int executor = system.getProcessorExecutor(); int committer = system.getProcessorCommitter(); processors = new NIOProcessor[system.getProcessors()]; for (int i = 0; i < processors.length; i++) { processors[i] = new NIOProcessor("Processor" + i, handler, executor, committer); processors[i].startup(); } timer.schedule(processorCheck(), 0L, system.getProcessorCheckPeriod()); // startup connector LOGGER.info("Startup connector ..."); connector = new NIOConnector(NAME + "Connector"); connector.setProcessors(processors); connector.start(); // init dataNodes Map<String, MySQLDataNode> dataNodes = config.getDataNodes(); LOGGER.info("Initialize dataNodes ..."); for (MySQLDataNode node : dataNodes.values()) { node.init(1, 0); } timer.schedule(dataNodeIdleCheck(), 0L, system.getDataNodeIdleCheckPeriod()); timer.schedule(dataNodeHeartbeat(), 0L, system.getDataNodeHeartbeatPeriod()); // startup manager ManagerConnectionFactory mf = new ManagerConnectionFactory(); mf.setCharset(system.getCharset()); mf.setIdleTimeout(system.getIdleTimeout()); manager = new NIOAcceptor(NAME + "Manager", system.getManagerPort(), mf); manager.setProcessors(processors); manager.start(); LOGGER.info(manager.getName() + " is started and listening on " + manager.getPort()); // startup server ServerConnectionFactory sf = new ServerConnectionFactory(); sf.setCharset(system.getCharset()); sf.setIdleTimeout(system.getIdleTimeout()); server = new NIOAcceptor(NAME + "Server", system.getServerPort(), sf); server.setProcessors(processors); server.start(); timer.schedule(clusterHeartbeat(), 0L, system.getClusterHeartbeatPeriod());
从代码可以看出,系统首先根据系统内核数量,初始化相应数量的nio处理器,然后初始化连接器,节点初始化,管理线程启动,服务线程启动。可以看到,管理线程和服务线程共享处理器。此外就是一些定时器了,这里先暂时不讲解。管理器是内部人员管理使用,这里我们也先搁置。剩下NIOProcessor, MySQLDataNode, NIOAcceptor。 因为NIOProcessor是共用产品,我们先看看它的结构。
NIOProcessor
private final String name; private final NIOReactor reactor; private final BufferPool bufferPool; private final NameableExecutor handler; private final NameableExecutor executor; private final NameableExecutor committer; private final ConcurrentMap<Long, FrontendConnection> frontends; private final ConcurrentMap<Long, BackendConnection> backends; private final CommandCount commands; private long netInBytes; private long netOutBytes;
以上为NIOProcessor的字段信息,其中较为重要的为NIOReactor , BufferPool。 BufferPool是一个缓冲池,这里不多讲;我们看看NIOReactor 的内容
private final R reactorR; private final W reactorW; public NIOReactor(String name) throws IOException { this.name = name; this.reactorR = new R(); this.reactorW = new W(); } final void startup() { new Thread(reactorR, name + "-R").start(); new Thread(reactorW, name + "-W").start(); } final void postRegister(NIOConnection c) { reactorR.registerQueue.offer(c); reactorR.selector.wakeup(); } final void postWrite(NIOConnection c) { reactorW.writeQueue.offer(c); }
在reactor里面维护了两个线程,每个线程分配一个blockingqueue。R线程负责从队列中读取连接进行读操作或者写操作,W线程负责写操作。
NIOAcceptor
NIOAcceptor的主要功能是根据指定的端口,接收用户执行的mysql语句,轮询选择NIOProcessor进行处理,我们看一看它的accept代码。首先接收channel,利用工厂(ServerConnectionFactory)创建FrontendConnection 。
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 = nextProcessor(); c.setProcessor(processor); processor.postRegister(c); } catch (Throwable e) { closeChannel(channel); LOGGER.warn(getName(), e); } } protected FrontendConnection getConnection(SocketChannel channel) { SystemConfig sys = CobarServer.getInstance().getConfig().getSystem(); ServerConnection c = new ServerConnection(channel); c.setPrivileges(new CobarPrivileges()); c.setQueryHandler(new ServerQueryHandler(c)); // c.setPrepareHandler(new ServerPrepareHandler(c)); TODO prepare c.setTxIsolation(sys.getTxIsolation()); c.setSession(new BlockingSession(c)); c.setSession2(new NonBlockingSession(c)); return c; }
通过代码可以知道,对每一个请求,cobar会创建一个ServerConnection,选择一个NIOProcessor 处理,ServerConnection就是FrontendConnection 的一个子类实现。接下来我们具体分析下ServerConnection。
前向连接
前向连接主要实现是ServerConnection(这里不包括管理部分)。 我们看到accept()函数里面有processor.postRegister(c)语句,我们看看这条语句做了什么事
将连接提交到队列中 final void postRegister(NIOConnection c) { reactorR.registerQueue.offer(c); reactorR.selector.wakeup(); } 注册事件类型,注意到这里将key保存起来了 public void register(Selector selector) throws IOException { try { processKey = channel.register(selector, SelectionKey.OP_READ, this); isRegistered = true; } finally { if (isClosed.get()) { clearSelectionKey(); } } } 读数据进行处理 public void read() throws IOException { ByteBuffer buffer = this.readBuffer; int got = channel.read(buffer); lastReadTime = TimeUtil.currentTimeMillis(); if (got < 0) { throw new EOFException(); } netInBytes += got; processor.addNetInBytes(got); int offset = readBufferOffset, length = 0, position = buffer.position(); for (;;) { length = getPacketLength(buffer, offset); if (length == -1) { if (!buffer.hasRemaining()) { checkReadBuffer(buffer, offset, position); } break; } if (position >= offset + length) { buffer.position(offset); byte[] data = new byte[length]; buffer.get(data, 0, length); handle(data); offset += length; if (position == offset) { if (readBufferOffset != 0) { readBufferOffset = 0; } buffer.clear(); break; } else { readBufferOffset = offset; buffer.position(position); continue; } } else { if (!buffer.hasRemaining()) { checkReadBuffer(buffer, offset, position); } break; } } } 重置命令处理类型 source.setHandler(new FrontendCommandHandler(source)); ByteBuffer buffer = source.allocate(); source.write(source.writeToBuffer(AUTH_OK, buffer)); 处理数据 public void handle(final byte[] data) { 这里异步处理前端数据 processor.getHandler().execute(new Runnable() { @Override public void run() { try { handler.handle(data); } catch (Throwable t) { error(ErrorCode.ERR_HANDLE_DATA, t); } } }); } 回复请求 public void write(ByteBuffer buffer) { if (isClosed.get()) { processor.getBufferPool().recycle(buffer); return; } if (isRegistered) { try { writeQueue.put(buffer); } catch (InterruptedException e) { error(ErrorCode.ERR_PUT_WRITE_QUEUE, e); return; } processor.postWrite(this); } else { processor.getBufferPool().recycle(buffer); close(); } } private void enableWrite() { final Lock lock = this.keyLock; lock.lock(); try { SelectionKey key = this.processKey; key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); } finally { lock.unlock(); } processKey.selector().wakeup(); } private void write(NIOConnection c) { try { c.writeByQueue(); } catch (Throwable e) { c.error(ErrorCode.ERR_WRITE_BY_QUEUE, e); } } 处理数据 public void handle(byte[] data) { 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(); 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"); } } 以query为例,我们看看往下怎么进行 public void query(byte[] data) { if (queryHandler != null) { MySQLMessage mm = new MySQLMessage(data); 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; } queryHandler.query(sql); } else { writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Query unsupported!"); } } @Override 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); switch (rs & 0xff) { case ServerParse.EXPLAIN: ExplainHandler.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: 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: 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; default: c.execute(sql, rs); } } 我们再以default为例,看看接下来怎么进行。可以看到从这里已经开始涉及路由选择。 public void execute(String sql, int type) { if (txInterrupted) { writeErrMessage(ErrorCode.ER_YES, "Transaction error, need to rollback."); return; } String db = this.schema; if (db == null) { writeErrMessage(ErrorCode.ER_NO_DB_ERROR, "No database selected"); return; } SchemaConfig schema = CobarServer.getInstance().getConfig().getSchemas().get(db); if (schema == null) { writeErrMessage(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '" + db + "'"); return; } RouteResultset rrs = null; try { rrs = ServerRouter.route(schema, sql, this.charset, this); } catch (SQLNonTransientException e) { StringBuilder s = new StringBuilder(); LOGGER.warn(s.append(this).append(sql).toString(), e); String msg = e.getMessage(); writeErrMessage(ErrorCode.ER_PARSE_ERROR, msg == null ? e.getClass().getSimpleName() : msg); return; } session.execute(rrs, type); } 对路由选择的每个节点执行sql操作 public void execute(RouteResultset rrs, int type) { if (LOGGER.isDebugEnabled()) { StringBuilder s = new StringBuilder(); LOGGER.debug(s.append(source).append(rrs).toString()); } RouteResultsetNode[] nodes = rrs.getNodes(); if (nodes == null || nodes.length == 0) { source.writeErrMessage(ErrorCode.ER_NO_DB_ERROR, "No dataNode selected"); return; } if (nodes.length == 1) { singleNodeExecutor.execute(nodes[0], this, rrs.getFlag()); } else { boolean autocommit = source.isAutocommit(); if (autocommit && isModifySQL(type)) { autocommit = false; } multiNodeExecutor.execute(nodes, autocommit, this, rrs.getFlag()); } } 执行计算并等待返回 BinaryPacket bin = ((MySQLChannel) c).execute(rrn, sc, autocommit); 接下来我们看看后端是怎么执行的。
后向连接
后向连接有两种方式,一种bio,一种nio;分别对应着BlockingSession与NonBlockingSession;在代码中,我下载的是版本默认使用BlockingSession,因此我们先分析BlockingSession,如下
封装成执行数据包 CommandPacket packet = new CommandPacket(); packet.packetId = 0; packet.command = MySQLPacket.COM_QUERY; packet.arg = rrn.getStatement().getBytes(charset); lastActiveTime = TimeUtil.currentTimeMillis(); packet.write(out); out.flush(); 这里是阻塞等待 BinaryPacket bin = receive(); long now = TimeUtil.currentTimeMillis(); if (now > lastActiveTime) { recordSql(sc.getHost(), sc.getSchema(), rrn.getStatement()); } lastActiveTime = now; return bin; 直接使用的socket连接mysql后端 socket = new Socket(); socket.setTcpNoDelay(true); socket.setTrafficClass(0x04 | 0x10); socket.setPerformancePreferences(0, 2, 1); socket.setReceiveBufferSize(RECV_BUFFER_SIZE); socket.setSendBufferSize(SEND_BUFFER_SIZE); socket.connect(new InetSocketAddress(dsc.getHost(), dsc.getPort()), SOCKET_CONNECT_TIMEOUT);
那么NonBlockingSession是怎样操作的呢,我们看看它的内部实现
RouteResultsetNode[] nodes = rrs.getNodes(); if (nodes == null || nodes.length == 0) { source.writeErrMessage(ErrorCode.ER_NO_DB_ERROR, "No dataNode selected"); return; } if (nodes.length == 1) { singleNodeHandler = new SingleNodeHandler(nodes[0], this); // singleNodeHandler.execute(); } else { boolean autocommit = source.isAutocommit(); if (autocommit && isModifySQL(type)) { autocommit = false; } multiNodeHandler = new MultiNodeQueryHandler(nodes, autocommit, this); // multiNodeHandler.execute(); } MultiNodeQueryHandler中的处理逻辑 ThreadPoolExecutor executor = session.getSource().getProcessor().getExecutor(); for (final RouteResultsetNode node : route) { final MySQLConnection conn = session.getTarget(node); if (conn != null) { conn.setAttachment(node); executor.execute(new Runnable() { @Override public void run() { _execute(conn, node); } }); } else { CobarConfig conf = CobarServer.getInstance().getConfig(); MySQLDataNode dn = conf.getDataNodes().get(node.getName()); dn.getConnection(this, node); } } 这里出现了MySQLConnection,前面提到的nio中的后端连接子类 private void _execute(MySQLConnection conn, RouteResultsetNode node) { conn.setResponseHandler(this); if (session.closed()) { backendConnError(conn, "failed or cancelled by other thread"); return; } try { conn.execute(node, session.getSource(), autocommit); } catch (IOException e) { connectionError(e, conn); } } public void execute(RouteResultsetNode rrn, ServerConnection sc, boolean autocommit) throws UnsupportedEncodingException { StatusSync sync = new StatusSync(this, rrn, sc, autocommit); statusSync = sync; if (sync.isSync() || !sync.sync()) { sync.execute(); } } public void execute() throws UnsupportedEncodingException { executed = true; CommandPacket packet = new CommandPacket(); packet.packetId = 0; packet.command = MySQLPacket.COM_QUERY; packet.arg = rrn.getStatement().getBytes(conn.getCharset()); conn.lastTime = TimeUtil.currentTimeMillis(); packet.write(conn); }
未完,待续。。。
- cobar源码浅析
- Cobar源码笔记--Heartbeat
- cobar
- cobar
- Cobar源码笔记--从加载配置文件到服务器启动
- cobar介绍
- Cobar介绍
- cobar简介
- cobar介绍
- Atlas 、cobar
- cobar介绍
- nginx源码浅析--源码编译
- Hibernate源码浅析
- TProactor源码浅析一
- LinkedList源码浅析
- Spring FactoryBean源码浅析
- Hessian源码浅析-HessianSkeleton
- Hessian源码浅析-HessianProxy
- 蓝桥杯 ADV-102 算法提高 单词个数统计
- C语言-深度理解
- 手指触摸GetTouch
- 求斐波那契数列第三十个数
- 新建STS打开原来可运行的项目出错解决办法
- cobar源码浅析
- 嵌入式学习(前言)
- Servlet上下文
- iOS创建xib关联view之后创建view
- C编程(六)流程控制之选择switch
- 蓝桥杯 ADV-91 算法提高 素数判断
- Java通过串口读取数据例子
- linux串口驱动(1)
- 蓝桥杯 ADV-79 算法提高 时间转换