/* * Copyright 1999-2012 Alibaba Group. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package fm.liu.timo; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import org.pmw.tinylog.Logger; import fm.liu.messenger.Mail; import fm.liu.messenger.User; import fm.liu.timo.backend.Node; import fm.liu.timo.config.Versions; import fm.liu.timo.config.model.SystemConfig; import fm.liu.timo.manager.ManagerConnectionFactory; import fm.liu.timo.mysql.connection.MySQLConnection; import fm.liu.timo.mysql.handler.xa.XARecoverHandler; import fm.liu.timo.mysql.packet.CommandPacket; import fm.liu.timo.net.NIOAcceptor; import fm.liu.timo.net.NIOConnector; import fm.liu.timo.net.NIOProcessor; import fm.liu.timo.net.connection.Variables; import fm.liu.timo.parser.recognizer.mysql.lexer.MySQLLexer; import fm.liu.timo.server.ServerConnectionFactory; import fm.liu.timo.server.session.handler.ResultHandler; import fm.liu.timo.statistic.SQLRecorder; import fm.liu.timo.util.ExecutorUtil; import fm.liu.timo.util.NameableExecutor; import fm.liu.timo.util.TimeUtil; /** * @author xianmao.hexm 2011-4-19 下午02:58:59 */ public class TimoServer { public static final String NAME = "Timo"; private static final long TIME_UPDATE_PERIOD = 20L; private static final SQLRecorder RECORDER = new SQLRecorder(); private static final User SENDER = new User() { @Override public void receive(Mail<?> mail) {} }; private static final TimoServer INSTANCE = new TimoServer(); public static final TimoServer getInstance() { return INSTANCE; } private final AtomicLong xid = new AtomicLong(); private final TimoConfig config; private final Timer timer; private final NameableExecutor timerExecutor; private final AtomicBoolean isOnline; private final long startupTime; private NIOProcessor[] processors; private NIOConnector connector; private NIOAcceptor manager; private NIOAcceptor server; private User starter; private Set<File> xaLogs; private volatile boolean xaCommiting; private volatile boolean xaStarting; private TimoServer() { this.config = new TimoConfig(); SystemConfig system = config.getSystem(); MySQLLexer.setCStyleCommentVersion(system.getParserCommentVersion()); this.timer = new Timer(NAME + "Timer", true); this.timerExecutor = ExecutorUtil.create("TimerExecutor", system.getTimerExecutor()); this.isOnline = new AtomicBoolean(true); this.startupTime = TimeUtil.currentTimeMillis(); RECORDER.register(); } public static User getSender() { return SENDER; } public static SQLRecorder getRecorder() { return RECORDER; } public User getStarter() { return starter; } public TimoConfig getConfig() { return config; } public String nextXID() { long id = this.xid.incrementAndGet(); if (id < 0) { synchronized (xid) { if (xid.get() < 0) { xid.set(0); } id = xid.incrementAndGet(); } } return "'TimoXA" + id + "'"; } public void startup() throws IOException { Logger.info("==============================================="); Logger.info("{} v{} is ready to startup ...", NAME, Versions.version); // 初始化配置 SystemConfig system = config.getSystem(); // 启动线程池 Logger.info("Startup processors ..."); int handler = system.getProcessorHandler(); int executor = system.getProcessorExecutor(); processors = new NIOProcessor[system.getProcessors()]; for (int i = 0; i < processors.length; i++) { processors[i] = new NIOProcessor("Processor" + i, handler, executor, system.getQueryTimeout()); processors[i].startup(); } Logger.info("Startup connector ..."); connector = new NIOConnector(NAME + "Connector"); connector.start(); // 初始化数据节点 Map<Integer, Node> nodes = config.getNodes(); Logger.info("Initialize dataNodes ..."); for (Node node : nodes.values()) { if (!node.init()) { Logger.error("Node:{} init failed, check your config", node); System.exit(-1); } } starter = new User() { @Override public void receive(Mail<?> mail) { try { xaLogs.forEach(log -> log.delete()); TimoServer.getInstance().lisen(system); } catch (IOException e) { e.printStackTrace(); } } }; starter.register(); // XA恢复 Logger.info("Checking XA transaction recover ..."); xaRecover(nodes); } private void xaRecover(Map<Integer, Node> nodes) { HashMap<String, ConcurrentHashMap<Integer, Boolean>> recoveryLog = new HashMap<>(); File dir = new File("."); if (dir.isDirectory()) { this.xaLogs = new HashSet<>(); File[] files = dir.listFiles(); for (File f : files) { String name = f.getName(); if (name.startsWith("TimoXA")) { try { FileInputStream in = new FileInputStream(f); ObjectInputStream stream = new ObjectInputStream(in); @SuppressWarnings("unchecked") ConcurrentHashMap<Integer, Boolean> result = (ConcurrentHashMap<Integer, Boolean>) stream.readObject(); stream.close(); in.close(); recoveryLog.put(name, result); this.xaLogs.add(f); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } } } } ResultHandler handler = new XARecoverHandler(recoveryLog, nodes); nodes.values().parallelStream().forEach(n -> { MySQLConnection con = (MySQLConnection) n.getSource().notNullGet(); con.setResultHandler(handler); CommandPacket packet = new CommandPacket(CommandPacket.COM_QUERY); packet.arg = "XA RECOVER".getBytes(); packet.write(con); }); } private void lisen(SystemConfig system) throws IOException { // 初始化定时任务 timer.schedule(updateTime(), 0L, TIME_UPDATE_PERIOD); timer.schedule(processorCheck(), 0L, system.getProcessorCheckPeriod()); timer.schedule(dataNodeIdleCheck(), 0L, system.getDataNodeIdleCheckPeriod()); timer.schedule(dataNodeHeartbeat(), 0L, system.getHeartbeatTimeout()); // 初始化管理端和服务端 Variables variables = new Variables(); variables.setCharset(system.getCharset()); ManagerConnectionFactory mf = new ManagerConnectionFactory(variables); manager = new NIOAcceptor(NAME + "Manager", system.getManagerPort(), mf); manager.start(); Logger.info("{} is started and listening on {}", manager.getName(), manager.getPort()); ServerConnectionFactory sf = new ServerConnectionFactory(variables); server = new NIOAcceptor(NAME + "Server", system.getServerPort(), sf); server.start(); // 启动完成 Logger.info("{} is started and listening on {}", server.getName(), server.getPort()); Logger.info("==============================================="); } public NIOProcessor[] getProcessors() { return processors; } public NIOConnector getConnector() { return connector; } public NameableExecutor getTimerExecutor() { return timerExecutor; } public long getStartupTime() { return startupTime; } public boolean isOnline() { return isOnline.get(); } public void offline() { isOnline.set(false); } public void online() { isOnline.set(true); } // 系统时间定时更新任务 private TimerTask updateTime() { return new TimerTask() { @Override public void run() { TimeUtil.update(); } }; } // 处理器定时检查任务 private TimerTask processorCheck() { return new TimerTask() { @Override public void run() { timerExecutor.execute(new Runnable() { @Override public void run() { for (NIOProcessor p : processors) { p.check(); } } }); } }; } // 数据节点定时连接空闲超时检查任务 private TimerTask dataNodeIdleCheck() { return new TimerTask() { @Override public void run() { timerExecutor.execute( () -> config.getNodes().values().forEach(node -> node.idleCheck())); } }; } // 数据节点定时心跳任务 private TimerTask dataNodeHeartbeat() { return new TimerTask() { @Override public void run() { timerExecutor.execute( () -> config.getNodes().values().forEach(node -> node.heartbeat())); } }; } private int processorIndex = 0; public NIOProcessor nextProcessor() { if (processors.length == 1) { return processors[0]; } else { return processors[(++processorIndex) % processors.length]; } } public boolean isXACommiting() { return xaCommiting; } public void setXACommiting(boolean xaCommiting) { this.xaCommiting = xaCommiting; } public boolean isXAStarting() { return xaStarting; } public void setXAStarting(boolean xaStarting) { this.xaStarting = xaStarting; } }