package org.opencloudb.net; import io.netty.buffer.ByteBuf; import io.netty.buffer.CompositeByteBuf; import io.netty.channel.ChannelHandlerContext; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.apache.log4j.Logger; import org.opencloudb.MycatConfig; import org.opencloudb.MycatSystem; import org.opencloudb.backend.BackendConnection; import org.opencloudb.backend.ConnectionMeta; import org.opencloudb.backend.PhysicalDBNode; import org.opencloudb.config.ErrorCode; import org.opencloudb.config.model.SchemaConfig; import org.opencloudb.mpp.DataMergeService; import org.opencloudb.mysql.nio.handler.CommitNodeHandler; import org.opencloudb.mysql.nio.handler.MultiNodeCoordinator; import org.opencloudb.mysql.nio.handler.MultiNodeQueryHandler; import org.opencloudb.mysql.nio.handler.RollbackNodeHandler; import org.opencloudb.mysql.nio.handler.RollbackReleaseHandler; import org.opencloudb.mysql.nio.handler.SingleNodeHandler; import org.opencloudb.net.mysql.ErrorPacket; import org.opencloudb.net.mysql.KillConnectionHandler; import org.opencloudb.net.mysql.NettyUtil; import org.opencloudb.net.mysql.OkPacket; import org.opencloudb.route.RouteResultset; import org.opencloudb.route.RouteResultsetNode; import org.opencloudb.server.parser.ServerParse; import org.opencloudb.sqlcmd.SQLCmdConstant; public class FrontSession implements Session { private static final Logger LOGGER = Logger.getLogger(FrontSession.class); private volatile boolean autocommit; private final ChannelHandlerContext ctx; private final ConnectionInfo conInf; private final boolean readOnly; private final ConcurrentHashMap<RouteResultsetNode, BackendConnection> target = new ConcurrentHashMap<RouteResultsetNode, BackendConnection>(); final MultiNodeCoordinator multiNodeCoordinator; final CommitNodeHandler commitHandler; private RollbackNodeHandler rollbackHandler; private SingleNodeHandler singleNodeHandler; private MultiNodeQueryHandler multiNodeHandler; private final AtomicBoolean isClosed = new AtomicBoolean(false); private volatile boolean txInterrupted; private volatile String txInterrputMsg = ""; private long lastInsertId; public FrontSession(ChannelHandlerContext frontCtx, boolean isReadOnly, ConnectionInfo conInf) { super(); this.ctx = frontCtx; this.readOnly = isReadOnly; this.conInf = conInf; multiNodeCoordinator = new MultiNodeCoordinator(this); commitHandler = new CommitNodeHandler(this); } @Override public int getTargetCount() { return target.size(); } public Set<RouteResultsetNode> getTargetKeys() { return target.keySet(); } public BackendConnection getTarget(RouteResultsetNode key) { return target.get(key); } public Map<RouteResultsetNode, BackendConnection> getTargetMap() { return this.target; } public BackendConnection removeTarget(RouteResultsetNode key) { return target.remove(key); } /** * 提交事务 */ public void commit() { if (txInterrupted) { writeErrMessage(ErrorCode.ER_YES, "Transaction error, need to rollback."); } else { final int initCount = target.size(); if (initCount <= 0) { this.writeOK(); return; } else if (initCount == 1) { BackendConnection con = target.elements().nextElement(); commitHandler.commit(con); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("multi node commit to send ,total " + initCount); } multiNodeCoordinator .executeBatchNodeCmd(SQLCmdConstant.COMMIT_CMD); } } } /** * 回滚事务 */ public void rollback() { // 状态检查 if (txInterrupted) { txInterrupted = false; } // 执行回滚 final int initCount = target.size(); if (initCount <= 0) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("no session bound connections found ,no need send rollback cmd "); } this.writeOK(); return; } rollbackHandler = new RollbackNodeHandler(this); rollbackHandler.rollback(); } public long getLastInsertId() { return lastInsertId; } public void setLastInsertId(long lastInsertId) { this.lastInsertId = lastInsertId; } /** * 设置是否需要中断当前事务 */ public void setTxInterrupt(String txInterrputMsg) { if (!autocommit && !txInterrupted) { txInterrupted = true; this.txInterrputMsg = txInterrputMsg; } } public boolean isTxInterrupted() { return txInterrupted; } @Override public void execute(RouteResultset rrs, int type) { // clear prev execute resources clearHandlesResources(); if (LOGGER.isDebugEnabled()) { StringBuilder s = new StringBuilder(); LOGGER.debug(s.append(this.conInf.toString()).append(rrs) .toString() + " rrs "); } // 检查路由结果是否为空 RouteResultsetNode[] nodes = rrs.getNodes(); if (nodes == null || nodes.length == 0 || nodes[0].getName() == null || nodes[0].getName().equals("")) { writeErrMessage(ErrorCode.ER_NO_DB_ERROR, "No dataNode found ,please check tables defined in schema:" + this.conInf.getSchema()); return; } if (nodes.length == 1) { singleNodeHandler = new SingleNodeHandler(rrs, this); try { singleNodeHandler.execute(); } catch (Exception e) { LOGGER.warn(new StringBuilder().append(this.conInf.toString()) .append(rrs), e); writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString()); } } else { boolean autocommit = this.autocommit; DataMergeService dataMergeSvr = null; if (ServerParse.SELECT == type && rrs.needMerge()) { dataMergeSvr = new DataMergeService(rrs); } multiNodeHandler = new MultiNodeQueryHandler(rrs, autocommit, this, dataMergeSvr); try { multiNodeHandler.execute(); } catch (Exception e) { LOGGER.warn(new StringBuilder().append(this.conInf.toString()) .append(rrs), e); writeErrMessage(ErrorCode.ERR_HANDLE_DATA, e.toString()); } } } /** * {@link ServerConnection#isClosed()} must be true before invoking this */ public void terminate() { for (BackendConnection node : target.values()) { node.close("client closed "); } clearHandlesResources(); } public void releaseConnectionIfSafe(BackendConnection conn, boolean debug, boolean needRollback) { RouteResultsetNode node = (RouteResultsetNode) conn.getAttachment(); if (node != null) { if (this.autocommit || conn.isFromSlaveDB() || !conn.isModifiedSQLExecuted()) { releaseConnection((RouteResultsetNode) conn.getAttachment(), LOGGER.isDebugEnabled(), needRollback); } } } public void releaseConnection(RouteResultsetNode rrn, boolean debug, final boolean needRollback) { BackendConnection c = target.remove(rrn); if (c != null) { if (debug) { LOGGER.debug("release connection " + c); } if (c.getAttachment() != null) { c.setAttachment(null); } if (!c.isClosed()) { if (c.isAutocommit()) { c.release(); } else if (needRollback) { c.setResponseHandler(new RollbackReleaseHandler()); c.rollback(); } else { c.release(); } } } } public void releaseConnections(final boolean needRollback) { boolean debug = LOGGER.isDebugEnabled(); for (RouteResultsetNode rrn : target.keySet()) { releaseConnection(rrn, debug, needRollback); } } public void releaseConnection(BackendConnection con) { Iterator<Entry<RouteResultsetNode, BackendConnection>> itor = target .entrySet().iterator(); while (itor.hasNext()) { BackendConnection theCon = itor.next().getValue(); if (theCon == con) { itor.remove(); con.release(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("realse connection " + con); } break; } } } /** * @return previous bound connection */ public BackendConnection bindConnection(RouteResultsetNode key, BackendConnection conn) { // System.out.println("bind connection "+conn+ // " to key "+key.getName()+" on sesion "+this); return target.put(key, conn); } public boolean tryExistsCon(final BackendConnection conn, RouteResultsetNode node) { if (conn == null) { return false; } if (!conn.isFromSlaveDB() || node.canRunnINReadDB(this.autocommit)) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("found connections in session to use " + conn + " for " + node); } conn.setAttachment(node); return true; } else { // slavedb connection and can't use anymore ,release it if (LOGGER.isDebugEnabled()) { LOGGER.debug("release slave connection,can't be used in trasaction " + conn + " for " + node); } releaseConnection(node, LOGGER.isDebugEnabled(), false); } return false; } protected void kill() { boolean hooked = false; AtomicInteger count = null; Map<RouteResultsetNode, BackendConnection> killees = null; for (RouteResultsetNode node : target.keySet()) { BackendConnection c = target.get(node); if (c != null) { if (!hooked) { hooked = true; killees = new HashMap<RouteResultsetNode, BackendConnection>(); count = new AtomicInteger(0); } killees.put(node, c); count.incrementAndGet(); } } if (hooked) { ConnectionMeta conMeta = new ConnectionMeta(null, null, -1, true); for (Entry<RouteResultsetNode, BackendConnection> en : killees .entrySet()) { KillConnectionHandler kill = new KillConnectionHandler( en.getValue(), this); MycatConfig conf = MycatSystem.getInstance().getConfig(); PhysicalDBNode dn = conf.getDataNodes().get( en.getKey().getName()); try { dn.getConnectionFromSameSource(conMeta, en.getValue(), kill, en.getKey()); } catch (Exception e) { LOGGER.error( "get killer connection failed for " + en.getKey(), e); kill.connectionError(e, null); } } } } private void clearHandlesResources() { SingleNodeHandler singleHander = singleNodeHandler; if (singleHander != null) { singleHander.clearResources(); singleNodeHandler = null; } MultiNodeQueryHandler multiHandler = multiNodeHandler; if (multiHandler != null) { multiHandler.clearResources(); multiNodeHandler = null; } } public void clearResources(final boolean needRollback) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("clear session resources " + this); } this.releaseConnections(needRollback); clearHandlesResources(); } public boolean closed() { return isClosed.get(); } /** * allowcate small * * @return */ public ByteBuf allocate(int initSize, int maxSize) { return ctx.alloc().ioBuffer(initSize, maxSize); } public ByteBuf allocate(int size) { return ctx.alloc().ioBuffer(size); } public boolean isAutocommit() { return autocommit; } public void setAutocommit(boolean autocommit) { this.autocommit = autocommit; } /** * content.addComponent(chunk.content()); need folowing code * content.writerIndex(content.writerIndex() + * chunk.content().readableBytes()); * * @return */ public CompositeByteBuf allocateCompositeByteBuf(int maxCount) { return ctx.alloc().compositeBuffer(maxCount); } public ChannelHandlerContext getCtx() { return ctx; } public boolean isReadOnly() { return readOnly; } @Override public void close(String reason) { if (isClosed.compareAndSet(false, true)) { for (BackendConnection node : target.values()) { node.close("client closed "); } clearHandlesResources(); } } public void writeOK() { NettyUtil.writeBytes(ctx, OkPacket.OK); } public void writeBytes(byte[] bytes) { NettyUtil.writeBytes(ctx, bytes); } public void writeErrMessage(int errno, String msg) { NettyUtil.writeErrMessage(ctx, errno, msg); } public void write(ByteBuf buffer) { ctx.writeAndFlush(buffer); } public void writeNoFlush(ByteBuf buffer) { ctx.write(buffer); } public void writeNoFlush(byte[] buffer) { NettyUtil.writeBytesNoFlush(ctx, buffer); } public void writeErrorPkg(ErrorPacket pkg) { ByteBuf buf = ctx.alloc().ioBuffer(pkg.calcPacketSize() + 4); pkg.write(buf); ctx.writeAndFlush(buf); } public void execute(String sql, int type) { if (this.isClosed.get()) { LOGGER.warn("ignore execute ,server connection is closed " + this); return; } // 状态检查 if (txInterrupted) { writeErrMessage(ErrorCode.ER_YES, "Transaction error, need to rollback." + txInterrputMsg); return; } // 检查当前使用的DB String db = this.getConInfo().getSchema(); if (db == null) { writeErrMessage(ErrorCode.ERR_BAD_LOGICDB, "No MyCAT Database selected"); return; } SchemaConfig schema = MycatSystem.getInstance().getConfig() .getSchemas().get(db); if (schema == null) { writeErrMessage(ErrorCode.ERR_BAD_LOGICDB, "Unknown MyCAT Database '" + db + "'"); return; } routeEndExecuteSQL(sql, type, schema); } public void routeEndExecuteSQL(String sql, int type, SchemaConfig schema) { // 路由计算 RouteResultset rrs = null; try { rrs = MycatSystem .getInstance() .getRouterService() .route(MycatSystem.getInstance().getConfig().getSystem(), schema, type, sql, this.getConInfo().getCharset(), this); } catch (Exception e) { StringBuilder s = new StringBuilder(); LOGGER.warn(s.append(this).append(sql).toString() + " err:" + e.toString()); String msg = e.getMessage(); writeErrMessage(ErrorCode.ER_PARSE_ERROR, msg == null ? e .getClass().getSimpleName() : msg); return; } if (rrs != null) { // session执行 execute(rrs, type); } } @Override public ConnectionInfo getConInfo() { return this.conInf; } public void writeOK(OkPacket ok) { writeOK(); // ByteBuf buf=ctx.alloc().ioBuffer(ok.calcPacketSize() + 4); // ok.write(buffer, c, writeSocketIfFull) // ctx.writeAndFlush(OkPacket.OK); } }