学了那么久的mycat使用,也该对 mycat 内部的架构与原理做一番了解。
简单理解起来,mycat就是数据库的中间件,就是做了数据报文的透传功能(数据库路由是另外一块内容),主要还使用NIO Reactor模式,先来看下mycat启动的时候做了哪些事情。
看了一下mycat 1.6版本的源码作理初步理解,不正确的地方还要大家指出。
io.mycat.MycatServer 初始化系统运行环境与服务配置参数
public void startup() throws IOException {
SystemConfig system = config.getSystem();
int processorCount = system.getProcessors();
// server startup
LOGGER.info("===============================================");
LOGGER.info(NAME + " is ready to startup ...");
String inf = "Startup processors ...,total processors:"
+ system.getProcessors() + ",aio thread pool size:"
+ system.getProcessorExecutor()
+ " \r\n each process allocated socket buffer pool "
+ " bytes ,buffer chunk size:"
+ system.getProcessorBufferChunk()
+ " buffer pool's capacity(buferPool/bufferChunk) is:"
+ system.getProcessorBufferPool()
/ system.getProcessorBufferChunk();
LOGGER.info(inf);
LOGGER.info("sysconfig params:" + system.toString());
// startup manager
ManagerConnectionFactory mf = new ManagerConnectionFactory();
ServerConnectionFactory sf = new ServerConnectionFactory();
SocketAcceptor manager = null;
SocketAcceptor server = null;
aio = (system.getUsingAIO() == 1);
// startup processors
int threadPoolSize = system.getProcessorExecutor();
processors = new NIOProcessor[processorCount];
long processBuferPool = system.getProcessorBufferPool();
int processBufferChunk = system.getProcessorBufferChunk();
int socketBufferLocalPercent = system.getProcessorBufferLocalPercent();
bufferPool = new BufferPool(processBuferPool, processBufferChunk, system.getFrontSocketSoRcvbuf(),
socketBufferLocalPercent / processorCount);
businessExecutor = ExecutorUtil.create("BusinessExecutor",
threadPoolSize);
timerExecutor = ExecutorUtil.create("Timer", system.getTimerExecutor());
listeningExecutorService = MoreExecutors.listeningDecorator(businessExecutor);
for (int i = 0; i < processors.length; i++) {
processors[i] = new NIOProcessor("Processor" + i, bufferPool,
businessExecutor);
}
AIO与NIO的选择,目前AIO还没有在Linux上得到底层实现,觉得这块AIO是否有实现或是选用的必要? 更多BIO AIO NIO 的东西看这里 戳
if (aio) {
LOGGER.info("using aio network handler ");
asyncChannelGroups = new AsynchronousChannelGroup[processorCount];
// startup connector
connector = new AIOConnector();
for (int i = 0; i < processors.length; i++) {
asyncChannelGroups[i] = AsynchronousChannelGroup
.withFixedThreadPool(processorCount,
new ThreadFactory() {
private int inx = 1;
@Override
public Thread newThread(Runnable r) {
Thread th = new Thread(r);
th.setName(BufferPool.LOCAL_BUF_THREAD_PREX
+ "AIO" + (inx++));
LOGGER.info("created new AIO thread "
+ th.getName());
return th;
}
});
}
manager = new AIOAcceptor(NAME + "Manager", system.getBindIp(),
system.getManagerPort(), mf, this.asyncChannelGroups[0]);
// startup server
server = new AIOAcceptor(NAME + "Server", system.getBindIp(),
system.getServerPort(), sf, this.asyncChannelGroups[0]);
} else {
LOGGER.info("using nio network handler ");
NIOReactorPool reactorPool = new NIOReactorPool(
BufferPool.LOCAL_BUF_THREAD_PREX + "NIOREACTOR",
processors.length);
connector = new NIOConnector(BufferPool.LOCAL_BUF_THREAD_PREX
+ "NIOConnector", reactorPool);
((NIOConnector) connector).start();
manager = new NIOAcceptor(BufferPool.LOCAL_BUF_THREAD_PREX + NAME
+ "Manager", system.getBindIp(), system.getManagerPort(),
mf, reactorPool);
server = new NIOAcceptor(BufferPool.LOCAL_BUF_THREAD_PREX + NAME
+ "Server", system.getBindIp(), system.getServerPort(), sf,
reactorPool);
}
上面两段是
MycatServer启动的主要内容,更多细节可以直接看源码。
另外再来看下 mycat 如何执行SQL操作 ,上面的源代码中有明显的两个变量 manager (默认9066端口) 和 server (默认8066端口)。
io.mycat.net.NIOAcceptor 是个独立的线程,run方法里面从socketServerChannel 拿取外部链接。
@Override
public void run() {
final Selector tSelector = this.selector;
for (;;) {
++acceptCount;
try {
tSelector.select(1000L);
Set 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);
}
}
}
在accept方法中make出一个抽像的前端链接,并设置具体的processer,同时指向对应的 reactor 进行选择处理
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);
}
}
io.mycat.net.NIOReactor 中接受前后端过来 reactor 注册事件,并唤醒 reactor 的 nio selector
public final class NIOReactor {
private static final Logger LOGGER = LoggerFactory.getLogger(NIOReactor.class);
private final String name;
private final RW reactorR;
public NIOReactor(String name) throws IOException {
this.name = name;
this.reactorR = new RW();
}
final void startup() {
new Thread(reactorR, name + "-RW").start();
}
final void postRegister(AbstractConnection c) {
reactorR.registerQueue.offer(c);
reactorR.selector.wakeup();
}
io.mycat.net.NIOReactor 的内部类 RW 又是一个独立线程,在run方法里面调用 前后端connection的asyncRead() 和 doNextWriteCheck()方法
for (;;) {
++reactCount;
try {
selector.select(500L);
register(selector);
keys = selector.selectedKeys();
for (SelectionKey key : keys) {
AbstractConnection con = null;
try {
Object att = key.attachment();
if (att != null) {
con = (AbstractConnection) att;
if (key.isValid() && key.isReadable()) {
try {
con.asynRead();
} catch (IOException e) {
con.close("program err:" + e.toString());
continue;
} catch (Exception e) {
LOGGER.warn("caught err:", e);
con.close("program err:" + e.toString());
continue;
}
}
if (key.isValid() && key.isWritable()) {
con.doNextWriteCheck();
}
} else {
key.cancel();
}
io.mycat.net.NIOSocketWR 是NIOReactor.RW背后的asynRead方法,做两件事件:
1)申请 byte buffer
2)读取 byte 报文
@Override
public void asynRead() throws IOException {
ByteBuffer theBuffer = con.readBuffer;
if (theBuffer == null) {
theBuffer = con.processor.getBufferPool().allocate();
con.readBuffer = theBuffer;
}
int got = channel.read(theBuffer);
con.onReadData(got);
}
在 connection.onReadData中会调用到 io.mycat.net.handler.FrontendCommandHandler 的 handle方法进行具体的前端命令操作
@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;
再逐步往下层调用进入 io.mycat.server.ServerQueryHandler 的 query() 方法
@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);
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;
由于这里是在看前端select操作行为,因此我们再来看下更具体的SelectHandler,里面区分了版本、数据库、用户、自增 以及最重要的默认操作形为——数据查询select.
public final class SelectHandler {
public static void handle(String stmt, ServerConnection c, int offs) {
int offset = offs;
switch (ServerParseSelect.parse(stmt, offs)) {
case ServerParseSelect.VERSION_COMMENT:
SelectVersionComment.response(c);
break;
case ServerParseSelect.DATABASE:
SelectDatabase.response(c);
break;
case ServerParseSelect.USER:
SelectUser.response(c);
break;
case ServerParseSelect.VERSION:
SelectVersion.response(c);
break;
case ServerParseSelect.SESSION_INCREMENT:
SessionIncrement.response(c);
break;
case ServerParseSelect.SESSION_ISOLATION:
SessionIsolation.response(c);
break;
case ServerParseSelect.LAST_INSERT_ID:
// offset = ParseUtil.move(stmt, 0, "select".length());
loop:for (int l=stmt.length(); offset < l; ++offset) {
switch (stmt.charAt(offset)) {
case ' ':
continue;
case '/':
case '#':
offset = ParseUtil.comment(stmt, offset);
continue;
case 'L':
case 'l':
break loop;
}
}
offset = ServerParseSelect.indexAfterLastInsertIdFunc(stmt, offset);
offset = ServerParseSelect.skipAs(stmt, offset);
SelectLastInsertId.response(c, stmt, offset);
break;
...
...
...
default:
c.execute(stmt, ServerParse.SELECT);
总结一下,整体上对mycat如何接收前端请求有一个了解,再整理一个时序图看得会更清晰一些
这篇先到这里,下篇接着讲重点哈~~