order
This paper focuses on my SQL connection of canal
ErosaConnection
canal-1.1.4/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/ErosaConnection.java
public interface ErosaConnection { public void connect() throws IOException; public void reconnect() throws IOException; public void disconnect() throws IOException; /** * For fast data lookup, the difference between seek and dump is that seek only gives part of the data */ public void seek(String binlogfilename, Long binlogPosition, String gtid, SinkFunction func) throws IOException; public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException; public void dump(long timestamp, SinkFunction func) throws IOException; /** * Synchronizing binlog with GTID */ public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException; // ------------- public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException; public void dump(long timestamp, MultiStageCoprocessor coprocessor) throws IOException; public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException; ErosaConnection fork(); public long queryServerId() throws IOException; }
- The ErosaConnection interface defines the connect, reconnect, disconnect, seek, dump, fork, queryServerId methods
MysqlConnection
canal-1.1.4/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java
public class MysqlConnection implements ErosaConnection { private static final Logger logger = LoggerFactory.getLogger(MysqlConnection.class); private MysqlConnector connector; private long slaveId; private Charset charset = Charset.forName("UTF-8"); private BinlogFormat binlogFormat; private BinlogImage binlogImage; // tsdb releated private AuthenticationInfo authInfo; protected int connTimeout = 5 * 1000; // 5 seconds protected int soTimeout = 60 * 60 * 1000; // 1 hour private int binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF; // dump binlog bytes, excluding meta and TSDB temporarily private AtomicLong receivedBinlogBytes; public MysqlConnection(){ } public MysqlConnection(InetSocketAddress address, String username, String password){ authInfo = new AuthenticationInfo(); authInfo.setAddress(address); authInfo.setUsername(username); authInfo.setPassword(password); connector = new MysqlConnector(address, username, password); // Pass through the parameters in the connection connector.setSoTimeout(soTimeout); connector.setConnTimeout(connTimeout); } public MysqlConnection(InetSocketAddress address, String username, String password, byte charsetNumber, String defaultSchema){ authInfo = new AuthenticationInfo(); authInfo.setAddress(address); authInfo.setUsername(username); authInfo.setPassword(password); authInfo.setDefaultDatabaseName(defaultSchema); connector = new MysqlConnector(address, username, password, charsetNumber, defaultSchema); // Pass through the parameters in the connection connector.setSoTimeout(soTimeout); connector.setConnTimeout(connTimeout); } public void connect() throws IOException { connector.connect(); } public void reconnect() throws IOException { connector.reconnect(); } public void disconnect() throws IOException { connector.disconnect(); } public boolean isConnected() { return connector.isConnected(); } public MysqlConnection fork() { MysqlConnection connection = new MysqlConnection(); connection.setCharset(getCharset()); connection.setSlaveId(getSlaveId()); connection.setConnector(connector.fork()); // set authInfo connection.setAuthInfo(authInfo); return connection; } @Override public long queryServerId() throws IOException { ResultSetPacket resultSetPacket = query("show variables like 'server_id'"); List<String> fieldValues = resultSetPacket.getFieldValues(); if (fieldValues == null || fieldValues.size() != 2) { return 0; } return NumberUtils.toLong(fieldValues.get(1)); } public ResultSetPacket query(String cmd) throws IOException { MysqlQueryExecutor exector = new MysqlQueryExecutor(connector); return exector.query(cmd); } //...... }
- MysqlConnection implements the ErosaConnection interface, and its constructor will build AuthenticationInfo and MysqlConnector; its connect, reconnect and disconnect methods are directly delegated to MysqlConnector; its fork method will use connector.fork() to re create a MysqlConnection; its queryServerId method will use show variables like 'server' ID 'to query
seek
canal-1.1.4/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java
public class MysqlConnection implements ErosaConnection { //...... public void seek(String binlogfilename, Long binlogPosition, String gtid, SinkFunction func) throws IOException { updateSettings(); loadBinlogChecksum(); sendBinlogDump(binlogfilename, binlogPosition); DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize()); fetcher.start(connector.getChannel()); LogDecoder decoder = new LogDecoder(); decoder.handle(LogEvent.ROTATE_EVENT); decoder.handle(LogEvent.FORMAT_DESCRIPTION_EVENT); decoder.handle(LogEvent.QUERY_EVENT); decoder.handle(LogEvent.XID_EVENT); LogContext context = new LogContext(); // If gtid exists in entry position, the gtid passed in is used as gtidSet // Splicing standard. Otherwise, when gtid and tsdb are turned on at the same time, gtid will be lost // When the gtid of the source database has been purged, an error will be reported as follows // 'errno = 1236, sqlstate = HY000 errmsg = The slave is connecting // using CHANGE MASTER TO MASTER_AUTO_POSITION = 1 ... if (StringUtils.isNotEmpty(gtid)) { decoder.handle(LogEvent.GTID_LOG_EVENT); context.setGtidSet(MysqlGTIDSet.parse(gtid)); } context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum)); while (fetcher.fetch()) { accumulateReceivedBytes(fetcher.limit()); LogEvent event = null; event = decoder.decode(fetcher, context); if (event == null) { throw new CanalParseException("parse failed"); } if (!func.sink(event)) { break; } } } private void updateSettings() throws IOException { try { update("set wait_timeout=9999999"); } catch (Exception e) { logger.warn("update wait_timeout failed", e); } try { update("set net_write_timeout=1800"); } catch (Exception e) { logger.warn("update net_write_timeout failed", e); } try { update("set net_read_timeout=1800"); } catch (Exception e) { logger.warn("update net_read_timeout failed", e); } try { // When setting the result returned by the server, no coding conversion will be done. It will be sent directly according to the binary code of the database, and the client will conduct coding conversion according to its own needs update("set names 'binary'"); } catch (Exception e) { logger.warn("update names failed", e); } try { // mysql5.6 needs to set session variable for checksum support // If not, an error will occur: Slave can not handle replication events with the // checksum that master is configured to log // However, it can't be set randomly. It needs to be consistent with the checksum configuration of mysql server, or the RotateLogEvent will be garbled // '@ @ global.binlog'checksum' needs to remove the single quotation mark, causing the master to exit under mysql 5.6.29 update("set @master_binlog_checksum= @@global.binlog_checksum"); } catch (Exception e) { if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) { logger.warn("update master_binlog_checksum failed", e); } } try { // Reference: https://github.com/alibaba/canal/issues/284 // MySQL 5.6 needs to set slave UUID to avoid being linked by server kill update("set @slave_uuid=uuid()"); } catch (Exception e) { if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) { logger.warn("update slave_uuid failed", e); } } try { // mariadb needs to set session variable for special type update("SET @mariadb_slave_capability='" + LogEvent.MARIA_SLAVE_CAPABILITY_MINE + "'"); } catch (Exception e) { if (!StringUtils.contains(e.getMessage(), "Unknown system variable")) { logger.warn("update mariadb_slave_capability failed", e); } } /** * MASTER_HEARTBEAT_PERIOD sets the interval in seconds between * replication heartbeats. Whenever the master's binary log is updated * with an event, the waiting period for the next heartbeat is reset. * interval is a decimal value having the range 0 to 4294967 seconds and * a resolution in milliseconds; the smallest nonzero value is 0.001. * Heartbeats are sent by the master only if there are no unsent events * in the binary log file for a period longer than interval. */ try { long periodNano = TimeUnit.SECONDS.toNanos(MASTER_HEARTBEAT_PERIOD_SECONDS); update("SET @master_heartbeat_period=" + periodNano); } catch (Exception e) { logger.warn("update master_heartbeat_period failed", e); } } private void loadBinlogChecksum() { ResultSetPacket rs = null; try { rs = query("select @@global.binlog_checksum"); List<String> columnValues = rs.getFieldValues(); if (columnValues != null && columnValues.size() >= 1 && columnValues.get(0) != null && columnValues.get(0).toUpperCase().equals("CRC32")) { binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_CRC32; } else { binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF; } } catch (Throwable e) { // logger.error("", e); binlogChecksum = LogEvent.BINLOG_CHECKSUM_ALG_OFF; } } private void sendBinlogDump(String binlogfilename, Long binlogPosition) throws IOException { BinlogDumpCommandPacket binlogDumpCmd = new BinlogDumpCommandPacket(); binlogDumpCmd.binlogFileName = binlogfilename; binlogDumpCmd.binlogPosition = binlogPosition; binlogDumpCmd.slaveServerId = this.slaveId; byte[] cmdBody = binlogDumpCmd.toBytes(); logger.info("COM_BINLOG_DUMP with position:{}", binlogDumpCmd); HeaderPacket binlogDumpHeader = new HeaderPacket(); binlogDumpHeader.setPacketBodyLength(cmdBody.length); binlogDumpHeader.setPacketSequenceNumber((byte) 0x00); PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody); connector.setDumping(true); } //...... }
- The seek method first executes the updateSettings, loadBinlogChecksum, sendBinlogDump methods, and then creates a DirectLogFetcher to fetch the data; the updateSettings method will set parameters such as wait ﹣ timeout, net ﹣ write ﹣ timeout, net ﹣ read ﹣ timeout, and master ﹣ heartbeat ﹣ period; the loadBinlogChecksum method will verify the binlogChecksum of the master; the sendBinlogDump method will send binlogdumpcomma ndPacket
dump
canal-1.1.4/parse/src/main/java/com/alibaba/otter/canal/parse/inbound/mysql/MysqlConnection.java
public class MysqlConnection implements ErosaConnection { //...... public void dump(String binlogfilename, Long binlogPosition, SinkFunction func) throws IOException { updateSettings(); loadBinlogChecksum(); sendRegisterSlave(); sendBinlogDump(binlogfilename, binlogPosition); DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize()); fetcher.start(connector.getChannel()); LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); LogContext context = new LogContext(); context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum)); while (fetcher.fetch()) { accumulateReceivedBytes(fetcher.limit()); LogEvent event = null; event = decoder.decode(fetcher, context); if (event == null) { throw new CanalParseException("parse failed"); } if (!func.sink(event)) { break; } if (event.getSemival() == 1) { sendSemiAck(context.getLogPosition().getFileName(), context.getLogPosition().getPosition()); } } } public void dump(GTIDSet gtidSet, SinkFunction func) throws IOException { updateSettings(); loadBinlogChecksum(); sendBinlogDumpGTID(gtidSet); DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize()); try { fetcher.start(connector.getChannel()); LogDecoder decoder = new LogDecoder(LogEvent.UNKNOWN_EVENT, LogEvent.ENUM_END_EVENT); LogContext context = new LogContext(); context.setFormatDescription(new FormatDescriptionLogEvent(4, binlogChecksum)); // Fix bug: transfer gtid to context for decode context.setGtidSet(gtidSet); while (fetcher.fetch()) { accumulateReceivedBytes(fetcher.limit()); LogEvent event = null; event = decoder.decode(fetcher, context); if (event == null) { throw new CanalParseException("parse failed"); } if (!func.sink(event)) { break; } } } finally { fetcher.close(); } } public void dump(String binlogfilename, Long binlogPosition, MultiStageCoprocessor coprocessor) throws IOException { updateSettings(); loadBinlogChecksum(); sendRegisterSlave(); sendBinlogDump(binlogfilename, binlogPosition); ((MysqlMultiStageCoprocessor) coprocessor).setConnection(this); ((MysqlMultiStageCoprocessor) coprocessor).setBinlogChecksum(binlogChecksum); DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize()); try { fetcher.start(connector.getChannel()); while (fetcher.fetch()) { accumulateReceivedBytes(fetcher.limit()); LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); if (!coprocessor.publish(buffer)) { break; } } } finally { fetcher.close(); } } public void dump(GTIDSet gtidSet, MultiStageCoprocessor coprocessor) throws IOException { updateSettings(); loadBinlogChecksum(); sendBinlogDumpGTID(gtidSet); ((MysqlMultiStageCoprocessor) coprocessor).setConnection(this); ((MysqlMultiStageCoprocessor) coprocessor).setBinlogChecksum(binlogChecksum); DirectLogFetcher fetcher = new DirectLogFetcher(connector.getReceiveBufferSize()); try { fetcher.start(connector.getChannel()); while (fetcher.fetch()) { accumulateReceivedBytes(fetcher.limit()); LogBuffer buffer = fetcher.duplicate(); fetcher.consume(fetcher.limit()); if (!coprocessor.publish(buffer)) { break; } } } finally { fetcher.close(); } } private void sendRegisterSlave() throws IOException { RegisterSlaveCommandPacket cmd = new RegisterSlaveCommandPacket(); SocketAddress socketAddress = connector.getChannel().getLocalSocketAddress(); if (socketAddress == null || !(socketAddress instanceof InetSocketAddress)) { return; } InetSocketAddress address = (InetSocketAddress) socketAddress; String host = address.getHostString(); int port = address.getPort(); cmd.reportHost = host; cmd.reportPort = port; cmd.reportPasswd = authInfo.getPassword(); cmd.reportUser = authInfo.getUsername(); cmd.serverId = this.slaveId; byte[] cmdBody = cmd.toBytes(); logger.info("Register slave {}", cmd); HeaderPacket header = new HeaderPacket(); header.setPacketBodyLength(cmdBody.length); header.setPacketSequenceNumber((byte) 0x00); PacketManager.writePkg(connector.getChannel(), header.toBytes(), cmdBody); header = PacketManager.readHeader(connector.getChannel(), 4); byte[] body = PacketManager.readBytes(connector.getChannel(), header.getPacketBodyLength()); assert body != null; if (body[0] < 0) { if (body[0] == -1) { ErrorPacket err = new ErrorPacket(); err.fromBytes(body); throw new IOException("Error When doing Register slave:" + err.toString()); } else { throw new IOException("unpexpected packet with field_count=" + body[0]); } } } private void sendBinlogDumpGTID(GTIDSet gtidSet) throws IOException { BinlogDumpGTIDCommandPacket binlogDumpCmd = new BinlogDumpGTIDCommandPacket(); binlogDumpCmd.slaveServerId = this.slaveId; binlogDumpCmd.gtidSet = gtidSet; byte[] cmdBody = binlogDumpCmd.toBytes(); logger.info("COM_BINLOG_DUMP_GTID:{}", binlogDumpCmd); HeaderPacket binlogDumpHeader = new HeaderPacket(); binlogDumpHeader.setPacketBodyLength(cmdBody.length); binlogDumpHeader.setPacketSequenceNumber((byte) 0x00); PacketManager.writePkg(connector.getChannel(), binlogDumpHeader.toBytes(), cmdBody); connector.setDumping(true); } //...... }
- MysqlConnection provides multiple dump methods, which are mainly divided into those based on binlog file name, gtidSet, and those using SinkFunction or multistage coprocessor
- According to the dump method of binlogfilename, first execute updateSettings, loadBinlogChecksum, sendRegisterSlave, sendBinlogDump, then create DirectLogFetcher to pull data, trigger the SinkFunction or multistage coprocessor; according to the dump method of gtidSet, first execute updateSettings, loadBinlogChecksum, sendBinlogDumpGTID, then create DirectLogFetcher to pull data, touch Send SinkFunction or multistage coprocessor
- sendRegisterSlave method constructs and sends RegisterSlaveCommandPacket; sendBinlogDumpGTID method constructs and sends BinlogDumpGTIDCommandPacket
Summary
MysqlConnection implements the ErosaConnection interface, and its constructor will build AuthenticationInfo and MysqlConnector; its connect, connect and disconnect methods are directly delegated to MysqlConnector; its fork method will re create a MysqlConnection using connector.fork(); its queryServerId method will use show variables like 'server [ID' query; it provides multiple dump methods, which are mainly divided into those based on binlog file name, gtidSet, and those based on SinkFunction or multistage coprocessor